|
| 1 | +# Actors |
| 2 | + |
| 3 | +Actors in ox enable invoking methods on an object serially, keeping the behavior as close as possible to a direct |
| 4 | +invocation. That is, even though invocations may happen from multiple threads, they are guaranteed to happen one after |
| 5 | +the other, not concurrently. |
| 6 | + |
| 7 | +Actor invocations are fully type-safe, with minimal overhead. They use [channels](index.md) and |
| 8 | +[scopes](../fork-join.md) behind the scenes. |
| 9 | + |
| 10 | +One of the use-cases is integrating with external APIs, which are represented by an object containing mutable state. |
| 11 | +Such integrations must be protected and cannot be accessed by multiple threads concurrently. |
| 12 | + |
| 13 | +```eval_rst |
| 14 | +.. note:: |
| 15 | +
|
| 16 | + Note that actors as described below are a very basic implementation, covering only some use cases for local |
| 17 | + concurrency. In general, actors are especially useful when working in distributedor clustered systems, or when |
| 18 | + implementing patterns such as event sourcing. For these use-cases, see the `Pekko <https://pekko.apache.org>`_ |
| 19 | + project. |
| 20 | +``` |
| 21 | + |
| 22 | +An actor can be created given any value (representing the actor's state) using `Actor.create`. This creates a fork in |
| 23 | +the current concurrency scope, and a channel (using the `StageCapacity` in scope) for scheduling invocations on the |
| 24 | +actor's logic. |
| 25 | + |
| 26 | +The result is an `ActorRef`, using which invocations can be scheduled using either the `ask` or `tell` methods. |
| 27 | + |
| 28 | +## Ask |
| 29 | + |
| 30 | +`ask` sends an invocation to the actor and awaits for a result. For example: |
| 31 | + |
| 32 | +```scala |
| 33 | +import ox.supervised |
| 34 | +import ox.channels.* |
| 35 | + |
| 36 | +class Stateful: |
| 37 | + private var counter: Int = 0 |
| 38 | + def increment(delta: Int): Int = |
| 39 | + counter += delta |
| 40 | + counter |
| 41 | + |
| 42 | +supervised { |
| 43 | + val ref = Actor.create(new Stateful) |
| 44 | + |
| 45 | + ref.ask(_.increment(5)) // blocks until the invocation completes |
| 46 | + ref.ask(_.increment(4)) // returns 9 |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +If a non-fatal exception is thrown by the invocation, it's propagated to the caller, and the actor continues processing |
| 51 | +other invocations. Fatal exceptions (e.g. interruptions) are propagated to the enclosing actor's scope, and the actor |
| 52 | +closes - trying to create another invocation will throw an exception. |
| 53 | + |
| 54 | +In this approach, actor's internal state usually has to be mutable. For a more functional style, an actor's |
| 55 | +implementation can contain a state machine with a single mutable field, containing the current state; each invocation of |
| 56 | +an actor's method can then match on the current state, and calculate the next one. |
| 57 | + |
| 58 | +## Tell |
| 59 | + |
| 60 | +It's also possible to schedule an invocation to be processed in the background using `.tell`. This method only blocks |
| 61 | +until the invocation can be sent to the actor's channel, but doesn't wait until it's processed. |
| 62 | + |
| 63 | +Note that any exceptions that occur when handling invocations scheduled using `.tell` will be propagated to the actor's |
| 64 | +enclosing scope, and will cause the actor to close. |
| 65 | + |
| 66 | +## Close |
| 67 | + |
| 68 | +When creating an actor, it's possible to specify a callback that will be called uninterruptedly before the actor closes. |
| 69 | +Such a callback can be used to release any resources held by the actor's logic. It's called when the actor closes, which |
| 70 | +includes closing of the enclosing scope: |
| 71 | + |
| 72 | +```scala |
| 73 | +import ox.supervised |
| 74 | +import ox.channels.* |
| 75 | + |
| 76 | +class Stateful: |
| 77 | + def work(howHard: Int): Unit = throw new RuntimeException("boom!") |
| 78 | + def close(): Unit = println("Closing") |
| 79 | + |
| 80 | +supervised { |
| 81 | + val ref = Actor.create(new Stateful, Some(_.close())) |
| 82 | + |
| 83 | + // fire-and-forget, exception causes the scope to close |
| 84 | + ref.tell(_.work(5)) |
| 85 | +} |
| 86 | +``` |
0 commit comments