Bluish Coder

Programming Languages, Martials Arts and Computers. The Weblog of Chris Double.


2016-05-11

Exploring actors in Pony

Pony is an actor oriented programming language. In Pony Actors are objects that can send and receive messages asychronously while processing their received messages sequentially and in parallel with other actors processing their messages. They are the unit of concurrency in the language. Each actor is similar to a lightweight thread of execution in languages that support those.

For background on the Actor model of computation there are a lot of papers at the erights.org actor page. They make good background reading. In this post I'm going to go through some things I've learnt while learning Pony and using actors in some small projects.

An actor is defined very similar to a class. The following class definition creates a counter that can be incremented and decremented:

class Counter
  var count: U32

  new create(start: U32) =>
    count = 0

  fun ref inc() =>
    count = count + 1

  fun ref dec() =>
    count = count - 1

  fun get(): U32 =>
    count

actor Main
  new create(env: Env) =>
    let c1 = Counter(0)
    c1.inc()
    c1.inc()
    env.out.print(c1.get().string())

The first thing to note here is the actor called Main. Every Pony program has an actor called Main that is the entry point for the program. This actor is instantiated by the Pony runtime and the constructor is expected to perform the program operations in a similar manner to how the main functions works in the C programming language.

The Counter class is created in the constructor of Main and incremented a couple of times. All this happens in a single thread of control. Because it operates within a single thread there is no concurrent access to the state held by the counter. This makes it safe to call the inc, dec and get methods. The order of operations is well defined. We can only pass the counter instance to another thread if we give up any aliases to it so we can ensure that it can be safely used elsewhere or if we make it immutable so that nothing can change it at any time.

Behaviours

If we want to use a Counter from multiple threads but still allow modification then making it an actor is an option. This can be done by changing the class keyword to actor and the methods to behaviours:

actor Counter
  var count: U32

  new create(start: U32) =>
    count = 0

  be inc() =>
    count = count + 1

  be dec() =>
    count = count - 1

  be display(out:OutStream) =>
    out.print(count.string())

actor Main
  new create(env: Env) =>
    let c1 = Counter(0)
    c1.inc()
    c1.inc()
    c1.display(env.out)

A behaviour is introduced with the be keyword. It is like a function except that it is asynchronous. When a behaviour is called it is not executed immediately.

Internally each actor has a queue for holding messages. Each behaviour call on an actor puts a message in that queue to run that behaviour at some future point in time. The actor runs a message loop that pops a message off the queue and runs the associated behaviour. When the behaviour completes executing then it will run the next one in the queue for that actor. If there are none left to run the the actor is idle until a behaviour is called. During this idle period it can perform garbage collection. The Pony runtime has a scheduler that uses operating system threads to execute actor behaviours. In this way multiple behaviours for different actors can be running on many OS threads at the same time.

The behaviours that are queued for an individual actor are executed sequentially. Two behaviours for the same actor will never run concurrently. This means that within a behaviour the actor has exclusive access to its internal state. There is no need for locks or guards to control access. For this reason it helps to think of actors as a unit of sequentiality rather than of a parallelism. See the actors section of the tutorial for more on this.

The main change with the conversion of the counter class is there is no longer a get method. It's replaced by a display behaviour that outputs the string. get was removed because behaviours are executed asynchronously so they cannot return the result of the function - they've returned to the caller before the body of the behaviour is executed. They always return the object the behaviour was called on. This makes chaining behaviour calls possible:

    let c1 = Counter(0)
    c1.inc()
      .inc()
      .display(env.out)

tag reference capability

A class defaults to a reference capability of ref. An actor defaults to tag. A tag only allows object identification. No read or write operations are allowed but you can alias tag objects and you can pass them to other actors. This is safe since the holder of a tag alias can't view or modify the state. It can call behaviours on it though. This is safe because behaviours are queued for sequential processing at a future point in time - access to the state of the actor is serialized through behaviours.

Simulating return values

How do you deal with returning values from behaviours if they don't support return values? One approach is to pass an object to the behaviour that it uses as a callback with the result. For the counter example this could look like:

actor Counter
  ...as before...

  be get(cb: {(U32)} iso) =>
    cb(count)

actor Main
  new create(env: Env) =>
    let c1 = Counter(0)
    c1.inc()
      .inc()
      .get(recover lambda (x:U32)(env) => env.out.print(x.string()) end end)

Here the get behaviour receives a closure as an argument. This is called passing a closure that prints the value out. When get is executed asynchronously it's safe for it to pass the count value to the closure. The closure can't modify it. The closure itself is an iso reference capability so nothing else but the behaviour is accessing it.

This approach leads to a very 'callback' style of programming. It can feel like programming in continuation passing style at times. It requires careful design when dealing with error handling. Pony includes a promises library to help manage this.

Promises

The promises library provides the ability to pass callbacks, handle errors and chain promises together to make it easier to manage callback style programming. The counter example converted to use promises looks like:

use "promises"

actor Counter
  ...as before...

  be get(p: Promise[U32]) =>
    p(count) 

actor Main
  new create(env: Env) =>
    let c1 = Counter(0)
    let p = Promise[U32]
    c1.inc()
      .inc()
      .get(p)
    p.next[None](recover lambda ref(x:U32)(env) => env.out.print(x.string()) end end)

The get method has been changed to take a Promise[U32]. The Promise type is a generic type and here it is indexed over the U32 value that it will be provided with. In the Main actor a promise is created and passed to get. Then the next method is called on the promise to tell it what to do when a value is provided to it. In this case it's the same closure as in the previous example so there's not much of a win here.

What promises do provide though is a way to handle failure. A callback used in the promise can raise an error and the promise will try the next operation in the chain. Chained promises can manipulate values as they're passed down the chain to form a pipeline of operations.

The boilerplate to create the promise and pass it to the behaviour can be hidden by a method on the actor:

actor Counter
  ...as before...

  be myget(p: Promise[U32]) =>
    p(count)

  fun tag get(): Promise[U32] =>
    let p = Promise[U32]
    myget(p)
    p

actor Main
  new create(env: Env) =>
    let c1 = Counter(0)
    c1.inc()
      .inc()
      .get().next[None](recover lambda ref(x:U32)(env) => env.out.print(x.string()) end end)

In this example the get method creates the promise and passes it to the behaviour then returns the promise. The caller can then use method chaining to call next on the promise to perform the action.

Notice that the get method has a tag reference capability. This is required to allow other actors to call it. A reference to an actor has the tag capability so only behaviours and tag methods can be called with it. A tag method can't modify internal state - all it can do is call behaviours on the actor - so this is safe to be called externally. It would be a compile error if the method attempted to view or modify actor state.

The following demonstrates promise chaining:

actor Counter
  ...as before...

  fun tag get_string(): Promise[String] =>
    get().next[String](object iso
                         fun ref apply(x:U32): String => x.string()
                       end)

actor Main
  new create(env: Env) =>
    let c1 = Counter(0)
    c1.inc()
      .inc()
      .get_string().next[Main](recover this~print(env.out) end)

  be print(out:OutStream, s: String) =>
    out.print(s)

In this case we want a String from the behaviour call. The get_string method on Counter calls get and chains the next callback to be one that returns a result of type `String. It just does a conversion by calling the string method. I use an object literal here instead of a closure for clarity.

The caller in Main calls get_string and chains the returned promise with another callback. This callback uses partial application to call the print behaviour on Main to print the string. The next call uses Main to parameterize the promise result as calling the print behaviour returns the receiver - in this case Main.

The result of this is that when the get behaviour is executed it calls the first promise in the chain to return the result. That converts the U32 to a String. The next promise in the chain is then called which calls print on the Main actor. That behaviour gets queued and eventually run to output the result.

Which is best to use, promises or callbacks? It depends on what the objects are doing. For single return values with an error case then promises are a good approach. For objects that need to callback multiple times then a callback or notifier object may be a better choice. For an example of the latter, see the net packages use of various notifier classes like TCPConnectionNotify to provide notification of different states in the TCP connection lifetime:

interface TCPConnectionNotify
  fun ref accepted(conn: TCPConnection ref)
  fun ref connecting(conn: TCPConnection ref, count: U32)
  fun ref connected(conn: TCPConnection ref)
  fun ref connect_failed(conn: TCPConnection ref)
  fun ref auth_failed(conn: TCPConnection ref)
  fun ref sent(conn: TCPConnection ref, data: ByteSeq): ByteSeq ?
  fun ref sentv(conn: TCPConnection ref, data: ByteSeqIter): ByteSeqIter ?
  fun ref received(conn: TCPConnection ref, data: Array[U8] iso)
  fun ref expect(conn: TCPConnection ref, qty: USize): USize
  fun ref closed(conn: TCPConnection ref)

Sendable objects

As behaviours are sent asycnhronously this means the arguments to those behaviours must be sharable. The passing and sharing section of the tutorial makes the distinction between 'passing' and 'sharing' objects.

In 'passing' an object from one actor to another the originating actor is giving up ownership. It can no longer access the object after giving it to the receiving actor. This is the iso reference capability. The sending actor must consume it when passing it to the receiver:

actor Main
  new create(env: Env) =>
    let a = recover iso String end
    let b = Something
    b.doit(consume a)

actor Something
  be doit(s: String iso) =>
    s.append("Hello World")

In 'sharing' an object you want both the originating actor and the receiver (and any others) to be able to read from the object. Nothing should be able to write to it. This is the val reference capability:

class Data
  var count: U32 = 0

  fun ref inc() =>
    count = count + 1

actor Main
  new create(env: Env) =>
    let a: Data trn = recover trn Data end
    a.inc()

    let d: Data val = consume a
    let s1 = Something(env.out)
    let s2 = Something(env.out)
    s1.doit(d)
    s2.doit(d)

actor Something
  let _out: OutStream

  new create(out: OutStream) =>
    _out = out

  be doit(d: Data val) =>
    _out.print("Got " + d.count.string())

This example has a Data class with an integer count field. In the Main actor we create an instance as a trn reference capability. This is used for objects that you want to write to initially but give out immutable access to later. While we hold the mutable trn reference we increment it and then consume it to get an immutable val reference capability for it. The old a alias is no longer usable at this point - no writeable aliases to the object exist. Because it is immutable we can now pass it to as many actors as we want and they get read only access to the objects fields and methods.

Another sharable type is the tag reference capability. This provides only identity access to an object. A receiver of a tag object can't read or write fields but it can call behaviours. It is the reference capability used for actors and is what you use to pass actors around. The previous sharing example uses this to pass the env.out object around. The OutStream is an actor.

It's important to keep in mind that creating aliases of objects doesn't copy the object. It's a new variable pointing to the same object. There is no copy operation involved in passing the Data val objects around. Although the capability is called 'val' it is not a 'value object'. The two Something actors have the same Data object in terms of object identity. The val only means 'immutable'.

The reference capabilities and the checking by the type system is what allows avoiding copies to be safe in the presence of multiple actors. This does mean that if you have a ref object you can't pass it to an actor. This is a compile error:

actor Something
  be doit(s: String ref) =>
  None

Only val, iso and tag can be used as an argument to a behaviour. This will also fail to compile:

actor Main
  new create(env: Env) =>
    let a = recover ref String end
    a.append("Hello World")
    let b = Something
    b.doit(a)

actor Something
  be doit(s: String val) =>
    None

Here we have a String ref and are trying to pass it to a behaviour expecting a String val. It is not possible to convert a ref to a val. A ref provides read and write access to the object. Multiple aliases to the same ref object can exist within a single actor. This is safe because behaviour execution within an actor is sequential. All of these aliases would need to be consumed to safely get a val alias. The type system doesn't (and probably couldn't) prove that all aliases are consumed at the time of converting to a val so it is not possible to do the conversion.

This is a case where a copy is needed:

actor Main
  new create(env: Env) =>
    let a = recover ref String end
    a.append("Hello World")
    let b = Something
    b.doit(a.clone())

The clone method on String returns a String iso^ which is convertable automatically to a String val by virtue of the fact that it has no aliases (The ^ part of the type). See capability subtyping for details

Cloning creates a copy distinct from the original. They have different identities and is a less efficient operation so it's worthwhile examining the data being passed around and seeing if it's possible to avoid holding multiple references to data and use the strictest reference capability to avoid aliasing.

Blocking operations

Pony has no blocking operations (outside of using the C FFI). In languages like Erlang it's common to do a blocking receive within a function to wait for a message and operate on it. In Pony this is implicitly done by actors in their event loop, hidden from the programmer. A behaviour call queues the message and it is executed when it's popped off the queue. You can't block for a message within the body of a behaviour itself.

This results in having to change the programming mode from "wait for data and do something" to "notify me of data when it's available". Instead of blocking for N seconds within a behaviour you create a timer to notify the actor of something 'N' seconds later.

The Pony standard library is structured in this way to use notifier objects, callbacks and promises to make programming in this style easier.

Causal Messaging

Data races can be difficult to avoid in the presence of asynchronous executation of threads. Pony has a message ordering guarantee to make the following type of code safe:

actor Counter
  let _out: OutStream
  var count: U32 = 0

  new create(out: OutStream) =>
    _out = out

  be inc() =>
    count = count + 1

  be dec() =>
    count = count - 1
    if count == 0 then _out.print("counter is destoyed") end

actor Something
  be doit(counter: Counter) =>
    counter.dec()

actor Main
  new create(env: Env) =>
    let c1 = Counter(env.out)
    let c2 = Something
    c1.inc()
    c2.doit(c1)

In this example the Counter object does something when the count is decremented to zero. In the Main actor a counter is created, incremented and passed to another actor where it is decremented. The inc and doit calls are on different actors and therefore are executed asynchronously. It's important that the inc call executes before the dec call in the doit behaviour of the other actor.

Pony has a message ordering guarantee, called 'causal messaging', to ensure this ordering happens. This is described in a forum thread discussion as:

[...] Pony makes a messaging order guarantee that's much stronger than is typical for the actor model. It guarantees causal messaging. That is, any message that is a "cause" of another message (i.e. was sent or received by an actor prior to the message in question) is guaranteed to arrive before the "effect" if they have the same destination.

For more information the paper Fully Concurrent Garbage Collection of Actors on Many-Core Machines goes into detail about how it works.

Garbage Collection

Actor's have their own garbage collection heap. Garbage collection occurs between behaviour calls on the actor. This allows GC to occur for an actor without interrupting execution of other actors. The runtime detects when it is no longer possible for an actor to receive messages and will garbage collect the actor itself. This can avoid the need to implement a 'poison pill' protocol whereby the actor receives a message to say it can terminate.

Even with this automatic actor garbage detection in place there are times when it is necessary to implement a shutdown protocol. An actor may be receiving notification callbacks - the actor sending the callbacks needs to be told to stop sending the messages so the system can detect that the receiver can be garbage collected. In my IMAP Idle monitor I use dispose methods to cancel timers or close TCP connections. A Pony library class called a Custodian holds a collection of actors to be disposed and calls dispose on each one when its own dispose behaviour is called. This results in the runtime detecting that none of the actors in the application can receive messages anymore and the entire application terminates.

One thing to be careful of with garbage collection only happening between behaviour calls is that a long running behaviour will not GC during its execution. Simple benchmark applications that do everything in the Main actors constructor exhibit this. They use large amounts of memory due to no GC happening if the benchmark doesn't call a behaviour on the actor.

Tags


This site is accessable over tor as hidden service 6vp5u25g4izec5c37wv52skvecikld6kysvsivnl6sdg6q7wy25lixad.onion, or Freenet using key:
USK@1ORdIvjL2H1bZblJcP8hu2LjjKtVB-rVzp8mLty~5N4,8hL85otZBbq0geDsSKkBK4sKESL2SrNVecFZz9NxGVQ,AQACAAE/bluishcoder/-61/


Tags

Archives
Links