Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Can we make concurrency in Scala safer?

3b84657fdb075382e3781310ca8a9a70?s=47 Philipp Haller
September 13, 2016
160

Can we make concurrency in Scala safer?

3b84657fdb075382e3781310ca8a9a70?s=128

Philipp Haller

September 13, 2016
Tweet

Transcript

  1. Can we make concurrency in Scala safer? 1 Philipp Haller

    KTH Royal Institute of Technology Scala World 2016 Rheged Centre, Lake District, UK
  2. Concurrency is Difficult 2

  3. Concurrency is Difficult Hazards: • Race conditions • Deadlocks •

    Livelocks • etc. 2
  4. Concurrency is Difficult Hazards: • Race conditions • Deadlocks •

    Livelocks • etc. 2 Remedies?
  5. Concurrency is Difficult Hazards: • Race conditions • Deadlocks •

    Livelocks • etc. 2 Monitors Futures, promises Async/await STM Actors Join-calculus Agents CSP Reactive streams … Remedies?
  6. Concurrency is Difficult Hazards: • Race conditions • Deadlocks •

    Livelocks • etc. 2 Monitors Futures, promises Async/await STM Actors Join-calculus Agents CSP Reactive streams … Remedies? Programming models as libraries typically can’t prevent these hazards statically!
  7. Example Image data 3

  8. Example Image data apply filter 3

  9. Example Image data apply filter 3

  10. Example Image data apply filter Image processing pipeline: filter 1

    filter 2 3
  11. Example Image data apply filter Image processing pipeline: filter 1

    filter 2 Pipeline stages run concurrently 3
  12. Example: Implementation 4

  13. Example: Implementation • Assumptions: • Image data large • Main

    memory expensive 4
  14. Example: Implementation • Assumptions: • Image data large • Main

    memory expensive • Approach for high performance: • In-place update of image buffers • Pass mutable buffers by-reference 4
  15. Example: Problem Easy to produce data races: 1. Stage 1

    sends a reference to a buffer to stage 2 2. Following the send, both stages have a reference to the same buffer 3. Stages can concurrently access the buffer 5
  16. Preventing Data Races 6

  17. Preventing Data Races • Approach: safe transfer of ownership 6

  18. Preventing Data Races • Approach: safe transfer of ownership •

    Sending stage loses ownership 6
  19. Preventing Data Races • Approach: safe transfer of ownership •

    Sending stage loses ownership • Compiler prevents sender from accessing objects that have been transferred 6
  20. Preventing Data Races • Approach: safe transfer of ownership •

    Sending stage loses ownership • Compiler prevents sender from accessing objects that have been transferred • Advantages: 6
  21. Preventing Data Races • Approach: safe transfer of ownership •

    Sending stage loses ownership • Compiler prevents sender from accessing objects that have been transferred • Advantages: • No run-time overhead 6
  22. Preventing Data Races • Approach: safe transfer of ownership •

    Sending stage loses ownership • Compiler prevents sender from accessing objects that have been transferred • Advantages: • No run-time overhead • Safety does not compromise performance 6
  23. Preventing Data Races • Approach: safe transfer of ownership •

    Sending stage loses ownership • Compiler prevents sender from accessing objects that have been transferred • Advantages: • No run-time overhead • Safety does not compromise performance • Errors caught at compile time 6
  24. Ownership Transfer in Scala 7

  25. Ownership Transfer in Scala • Enter LaCasa 7

  26. Ownership Transfer in Scala • Enter LaCasa • LaCasa: Scala

    extension for affine references 7
  27. Ownership Transfer in Scala • Enter LaCasa • LaCasa: Scala

    extension for affine references • “Transferable” references 7
  28. Ownership Transfer in Scala • Enter LaCasa • LaCasa: Scala

    extension for affine references • “Transferable” references • At most one owner per transferable reference 7
  29. Affine References in Scala 8

  30. Affine References in Scala • LaCasa provides affine references by

    combining two concepts: 8
  31. Affine References in Scala • LaCasa provides affine references by

    combining two concepts: • Access permissions 8
  32. Affine References in Scala • LaCasa provides affine references by

    combining two concepts: • Access permissions • Encapsulated boxes 8
  33. Access Permissions 9

  34. Access Permissions • Access to transferable objects controlled by implicit

    permissions 9
  35. Access Permissions • Access to transferable objects controlled by implicit

    permissions CanAccess { type C } 9
  36. Access Permissions • Access to transferable objects controlled by implicit

    permissions • Type member C uniquely identifies box CanAccess { type C } 9
  37. Access Permissions • Access to transferable objects controlled by implicit

    permissions • Type member C uniquely identifies box CanAccess { type C } Box[T] { type C } 9
  38. Matching Boxes and Permissions • Type members match permissions and

    boxes • Via path-dependent types • Example: def method(box: Box[T]) (implicit p: CanAccess { type C = box.C }): Unit 10
  39. Matching Boxes and Permissions • Type members match permissions and

    boxes • Via path-dependent types • Example: def method(box: Box[T]) (implicit p: CanAccess { type C = box.C }): Unit 10
  40. Creating Boxes and Permissions 11

  41. Creating Boxes and Permissions class Message { var arr: Array[Int]

    = _ } 11
  42. Creating Boxes and Permissions mkBox[Message] { packed => } class

    Message { var arr: Array[Int] = _ } 11
  43. Creating Boxes and Permissions mkBox[Message] { packed => } class

    Message { var arr: Array[Int] = _ } LaCasa library 11
  44. Creating Boxes and Permissions mkBox[Message] { packed => } class

    Message { var arr: Array[Int] = _ } 11
  45. Creating Boxes and Permissions mkBox[Message] { packed => } class

    Message { var arr: Array[Int] = _ } implicit val access = packed.access val theBox = packed.box … 11
  46. Creating Boxes and Permissions mkBox[Message] { packed => } class

    Message { var arr: Array[Int] = _ } sealed trait Packed[+T] { val box: Box[T] implicit val access: CanAccess { type C = box.C } } implicit val access = packed.access val theBox = packed.box … 11
  47. Accessing Boxes • Boxes are encapsulated • Boxes must be

    opened for access 12
  48. Accessing Boxes • Boxes are encapsulated • Boxes must be

    opened for access mkBox[Message] { packed => implicit val access = packed.access val theBox = packed.box theBox open { msg => msg.arr = Array(1, 2, 3, 4) } } 12
  49. Accessing Boxes • Boxes are encapsulated • Boxes must be

    opened for access mkBox[Message] { packed => implicit val access = packed.access val theBox = packed.box theBox open { msg => msg.arr = Array(1, 2, 3, 4) } } Requires implicit access permission 12
  50. Consuming Permissions Example: transfering a box from one actor to

    another consumes its access permission 13
  51. Consuming Permissions Example: transfering a box from one actor to

    another consumes its access permission mkBox[Message] { packed => implicit val access = packed.access val theBox = packed.box … someActor.send(theBox) // illegal to access `theBox` here! } 13
  52. Consuming Permissions Example: transfering a box from one actor to

    another consumes its access permission mkBox[Message] { packed => implicit val access = packed.access val theBox = packed.box … someActor.send(theBox) // illegal to access `theBox` here! } How to enforce this? 13
  53. Permissions and Continuations 14

  54. Permissions and Continuations • Make implicit permission unavailable in continuation

    of permission-consuming call 14
  55. Permissions and Continuations • Make implicit permission unavailable in continuation

    of permission-consuming call • Scala’s type system is flow-insensitive => use continuation passing 14
  56. Permissions and Continuations • Make implicit permission unavailable in continuation

    of permission-consuming call • Scala’s type system is flow-insensitive => use continuation passing • Restrict continuation to exclude consumed permission 14
  57. Continuation-Passing Style mkBox[Message] { packed => implicit val access =

    packed.access val theBox = packed.box … someActor.send(theBox) { // make `access` unavailable … } } 15
  58. Restricting Continuations • Continuation disallows capturing variables of the type

    of access • Leverage spores 16
  59. Restricting Continuations • Continuation disallows capturing variables of the type

    of access • Leverage spores def send(msg: Box[T]) (cont: NullarySpore[Unit] { type Excluded = msg.C }) (implicit p: CanAccess { type C = msg.C }): Nothing 16
  60. Restricting Continuations • Continuation disallows capturing variables of the type

    of access • Leverage spores def send(msg: Box[T]) (cont: NullarySpore[Unit] { type Excluded = msg.C }) (implicit p: CanAccess { type C = msg.C }): Nothing 16
  61. Intermezzo: Spores Spore = closure • with explicit environment •

    with function type refined by captured types val msg = "hello" spore { val s = msg (x: Int) => s”Message and param: ($s, $x)” } 17
  62. Intermezzo: Spores Spore = closure • with explicit environment •

    with function type refined by captured types val msg = "hello" spore { val s = msg (x: Int) => s”Message and param: ($s, $x)” } Spore[Int, String] { type Captured = String } 17
  63. Spore Trait trait Spore[-T, +R] extends Function1[T, R] { type

    Captured type Excluded } 18
  64. Implicit Conversions • Goal:
 Implicitly convert function literals to spores

    • Enables more lightweight syntax • Enables checking excluded types • Example: 19 type SafeSpore[A, B] = Spore[A, B] { type Excluded = SomeExcludedType } val s: SafeSpore[A, B] = (x: A) => { … }
  65. Recall Example mkBox[Message] { packed => implicit val access =

    packed.access val theBox = packed.box … someActor.send(theBox) { // `access` unavailable … } } 20
  66. Encapsulation Problem: not all types safe to transfer! 21

  67. Encapsulation Problem: not all types safe to transfer! 21 class

    Message { var arr: Array[Int] = _ def leak(): Unit = { SomeObject.fld = arr } } object SomeObject { var fld: Array[Int] = _ }
  68. Encapsulation • Ensuring absence of data races (“concurrency safety”) requires

    restricting types put into boxes • Requirements for “safe” classes:* • Methods only access parameters and this • Methods only instantiate “safe” classes • Types of fields are “safe” 22 * simplified
  69. Encapsulation • Ensuring absence of data races (“concurrency safety”) requires

    restricting types put into boxes • Requirements for “safe” classes:* • Methods only access parameters and this • Methods only instantiate “safe” classes • Types of fields are “safe” 22 “Safe” = conforms to object capability model * simplified
  70. Object Capabilities in Scala • How common are object-capability safe

    classes in Scala? • Results from empirical study: 23
  71. LaCasa: Summary • Encapsulated boxes • Insight: object capabilities for

    alias protection • Access permissions via implicits + path- dependent types • Can statically prevent data races • Not shown: storing boxes in unique fields 24
  72. More Details • Paper to appear at OOPSLA ’16 •

    Technical report: arxiv.org/abs/1607.05609 • Formal model mechanized in Coq 25
  73. Status and Plans • Prototype implementation: compiler plug-in for Scala

    2.11 and actor integration (straw man) • https://github.com/phaller/lacasa • Plans: • Adapters for Akka, Reactors, etc. • Port to Dotty Scala Compiler • Implicit functions for permissions 26
  74. Status and Plans • Prototype implementation: compiler plug-in for Scala

    2.11 and actor integration (straw man) • https://github.com/phaller/lacasa • Plans: • Adapters for Akka, Reactors, etc. • Port to Dotty Scala Compiler • Implicit functions for permissions 26 Thank you!