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

Can we make concurrency in Scala safer?

Philipp Haller
September 13, 2016
260

Can we make concurrency in Scala safer?

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!