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

Can we make concurrency in Scala safer?

Philipp Haller
September 13, 2016
470

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 Hazards: • Race conditions • Deadlocks •

    Livelocks • etc. 2 Monitors Futures, promises Async/await STM Actors Join-calculus Agents CSP Reactive streams … Remedies?
  3. 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!
  4. Example Image data apply filter Image processing pipeline: filter 1

    filter 2 Pipeline stages run concurrently 3
  5. 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
  6. 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
  7. Preventing Data Races • Approach: safe transfer of ownership •

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

    Sending stage loses ownership • Compiler prevents sender from accessing objects that have been transferred • Advantages: 6
  9. 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
  10. 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
  11. 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
  12. Ownership Transfer in Scala • Enter LaCasa • LaCasa: Scala

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

    extension for affine references • “Transferable” references • At most one owner per transferable reference 7
  14. Affine References in Scala • LaCasa provides affine references by

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

    combining two concepts: • Access permissions • Encapsulated boxes 8
  16. Access Permissions • Access to transferable objects controlled by implicit

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

    permissions • Type member C uniquely identifies box CanAccess { type C } Box[T] { type C } 9
  18. 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
  19. 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
  20. Creating Boxes and Permissions mkBox[Message] { packed => } class

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

    Message { var arr: Array[Int] = _ } implicit val access = packed.access val theBox = packed.box … 11
  22. 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
  23. 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
  24. 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
  25. Consuming Permissions Example: transfering a box from one actor to

    another consumes its access permission 13
  26. 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
  27. 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
  28. Permissions and Continuations • Make implicit permission unavailable in continuation

    of permission-consuming call • Scala’s type system is flow-insensitive => use continuation passing 14
  29. 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
  30. Continuation-Passing Style mkBox[Message] { packed => implicit val access =

    packed.access val theBox = packed.box … someActor.send(theBox) { // make `access` unavailable … } } 15
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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) => { … }
  36. Recall Example mkBox[Message] { packed => implicit val access =

    packed.access val theBox = packed.box … someActor.send(theBox) { // `access` unavailable … } } 20
  37. 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] = _ }
  38. 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
  39. 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
  40. Object Capabilities in Scala • How common are object-capability safe

    classes in Scala? • Results from empirical study: 23
  41. 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
  42. More Details • Paper to appear at OOPSLA ’16 •

    Technical report: arxiv.org/abs/1607.05609 • Formal model mechanized in Coq 25
  43. 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
  44. 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!