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

Can we make concurrency in Scala safer?

Philipp Haller
September 13, 2016
290

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

    View Slide

  2. Concurrency is Difficult
    2

    View Slide

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

    View Slide

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

    View Slide

  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?

    View Slide

  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!

    View Slide

  7. Example
    Image
    data
    3

    View Slide

  8. Example
    Image
    data
    apply filter
    3

    View Slide

  9. Example
    Image
    data
    apply filter
    3

    View Slide

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

    View Slide

  11. Example
    Image
    data
    apply filter
    Image processing pipeline:
    filter 1 filter 2
    Pipeline
    stages run
    concurrently
    3

    View Slide

  12. Example: Implementation
    4

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  16. Preventing Data Races
    6

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  24. Ownership Transfer in Scala
    7

    View Slide

  25. Ownership Transfer in Scala
    • Enter LaCasa
    7

    View Slide

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

    View Slide

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

    View Slide

  28. Ownership Transfer in Scala
    • Enter LaCasa
    • LaCasa: Scala extension for affine references
    • “Transferable” references
    • At most one owner per transferable reference
    7

    View Slide

  29. Affine References in Scala
    8

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  33. Access Permissions
    9

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  40. Creating Boxes and
    Permissions
    11

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    11

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  53. Permissions and
    Continuations
    14

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  57. Continuation-Passing Style
    mkBox[Message] { packed =>
    implicit val access = packed.access
    val theBox = packed.box

    someActor.send(theBox) {
    // make `access` unavailable

    }
    }
    15

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  63. Spore Trait
    trait Spore[-T, +R] extends Function1[T, R] {
    type Captured
    type Excluded
    }
    18

    View Slide

  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) => { … }

    View Slide

  65. Recall Example
    mkBox[Message] { packed =>
    implicit val access = packed.access
    val theBox = packed.box

    someActor.send(theBox) {
    // `access` unavailable

    }
    }
    20

    View Slide

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

    View Slide

  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] = _
    }

    View Slide

  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

    View Slide

  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

    View Slide

  70. Object Capabilities in Scala
    • How common are object-capability safe classes
    in Scala?
    • Results from empirical study:
    23

    View Slide

  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

    View Slide

  72. More Details
    • Paper to appear at OOPSLA ’16
    • Technical report: arxiv.org/abs/1607.05609
    • Formal model mechanized in Coq
    25

    View Slide

  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

    View Slide

  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!

    View Slide