I'm playing with actor-based asynchronous system for a while. Being big fan of actor model, I found some inconveniences in current Actor framework implementation, which led me to prototype alternative, however I'm stuck with some Fantom related issues. Let me please dump some thoughts off my head here just to understand if I'm doing something reasonable as well as I'm correct in understanding.
Actor state
I guess this is obvious, but I'd like to explicitly mention the fact that many of real-life Actors are stateful ones. In Fantom actor state maintained with Actor.locals(), in other languages/frameworks there are also some mechanism to maintain Actor state in safe manner. One of core responsibilities of a framework is to ensure that state changes are safe (e.g. by no allowing to process messages on the same actor in parallel -- like in Fantom… This is not ideal as well in terms of balancing at least because we can send messages to really stateless actor from multiple threads).
So (Fantom) actors are stageful by nature but const by design. This design is to ensure we have no shared mutable state. However maintaing state with locals() is not convenient (it's OK when you have a few types of actors, but becomes very boring if you have tens to hundreds different type of actors communicating in your system). Life with actor locals becomes similar to programming in dynamically types languages with explicit casts, runtime problems, as well as blow my code fast.
What alternatives could be for more convenient state maintenance.
Maintain state within object. This is like we are maintain object state in OO languages. Like:
class StatefulActor : Actor
{
private Int counter := 0
override Obj? receive(Obj? msg) { return counter = counter + (msg as Int) }
}
The code above is safe (from Actor model perspective) because counter field is not accessible from anywhere outside of that receive code. However it looks to be impossible to implement such case without special support in compiler, which is definitively sucks.
Going further I was trying to limit scope of Actor's state, and keep it within receive method local variables, which led to following design:
const class StatefulActor : Actor
{
override Obj? receive(Obj? msg)
{
counter := 0
loop |Int i| { counter += i }
}
}
so far so good, however |Int i| { counter += i } function passed to loop should not be Immutable (in current Fantom understanding), which again break the idea: if we'd allow to use mutable functions in such continuation-passing styled agents, developer would be able to modify shared state in unsafe manner…
One solution is to allow using mutable functions, which have limited access to the state. For example they can only modify local variables of the defining method (receive in the code above). But this will work until I want following
In this case it is not clear, which formal requirements closure passed to loop should fulfill to be safe.
At the moment I'm thinking if this will work if we will introduce another kind of function, like synchronized or serial (compiler can set such marks), which do not access/modify anything outside of some scope (for example outside of defining method), but there should be additional requirements to handle latest case above…
Any thoughts, is it reasonable to go is this direction at all?
Kind regards, Andrey
brianFri 27 May 2011
I definitely agree there is a next step to take for Fantom actors. After writing what is probably a hundred of different actors, I have started to settle on a pattern I use in practically all my actors:
const class FooActor : Actor
{
X commandA(X) { send(Msg(Msg.a, X).get(timeout) }
X commandB(X) { send(Msg(Msg.b, X).get(timeout) }
Void receive(Obj? msg)
{
state := Actor.locals["state"] as FooState
if (state == null) Actor.locals["state"] = state = FooState(this)
return state.dispatch(msg)
}
}
internal class FooState
{
Obj? dispatch(Msg msg)
{
switch(msg.id)
{
case Msg.a: return a(msg)
case Msg.b: return b(msg)
default: throw Err("Unknown msg $msg.id")
}
}
// mutable state fields
}
internal const Msg
{
static const Int a
static const Int b
new make(Int id, Obj? argA, Obj? argB)
const Int id
const Obj? argA
const Obj? argB
}
Often I omit a message class, but for complicated actors I find it is needed. I've found this pattern works really well for separating the public API of the actor from the mutable state held by Actor.locals. However, it borders on Java-esque in the amount of boiler plate required.
I haven't spent much time thinking about solution, but I know the boiler plate code above is not a good long term solution. I've increasingly been thinking about problems like this as something like Groovy's macro annotations might solve well.
andrey Fri 27 May 2011
Hi folks,
I'm playing with actor-based asynchronous system for a while. Being big fan of actor model, I found some inconveniences in current Actor framework implementation, which led me to prototype alternative, however I'm stuck with some Fantom related issues. Let me please dump some thoughts off my head here just to understand if I'm doing something reasonable as well as I'm correct in understanding.
Actor state
I guess this is obvious, but I'd like to explicitly mention the fact that many of real-life
Actors
are stateful ones. In Fantom actor state maintained withActor.locals()
, in other languages/frameworks there are also some mechanism to maintain Actor state in safe manner. One of core responsibilities of a framework is to ensure that state changes are safe (e.g. by no allowing to process messages on the same actor in parallel -- like in Fantom… This is not ideal as well in terms of balancing at least because we can send messages to really stateless actor from multiple threads).So (Fantom) actors are stageful by nature but
const
by design. This design is to ensure we have no shared mutable state. However maintaing state withlocals()
is not convenient (it's OK when you have a few types of actors, but becomes very boring if you have tens to hundreds different type of actors communicating in your system). Life with actor locals becomes similar to programming in dynamically types languages with explicit casts, runtime problems, as well as blow my code fast.What alternatives could be for more convenient state maintenance.
Maintain state within object. This is like we are maintain object state in OO languages. Like:
The code above is safe (from Actor model perspective) because counter field is not accessible from anywhere outside of that
receive
code. However it looks to be impossible to implement such case without special support in compiler, which is definitively sucks.Going further I was trying to limit scope of Actor's state, and keep it within
receive
method local variables, which led to following design:so far so good, however
|Int i| { counter += i }
function passed toloop
should not be Immutable (in current Fantom understanding), which again break the idea: if we'd allow to use mutable functions in such continuation-passing styled agents, developer would be able to modify shared state in unsafe manner…One solution is to allow using mutable functions, which have limited access to the state. For example they can only modify local variables of the defining method (
receive
in the code above). But this will work until I want followingIn this case it is not clear, which formal requirements closure passed to
loop
should fulfill to be safe.At the moment I'm thinking if this will work if we will introduce another kind of function, like
synchronized
orserial
(compiler can set such marks), which do not access/modify anything outside of some scope (for example outside of defining method), but there should be additional requirements to handle latest case above…Any thoughts, is it reasonable to go is this direction at all?
Kind regards, Andrey
brian Fri 27 May 2011
I definitely agree there is a next step to take for Fantom actors. After writing what is probably a hundred of different actors, I have started to settle on a pattern I use in practically all my actors:
Often I omit a message class, but for complicated actors I find it is needed. I've found this pattern works really well for separating the public API of the actor from the mutable state held by Actor.locals. However, it borders on Java-esque in the amount of boiler plate required.
I haven't spent much time thinking about solution, but I know the boiler plate code above is not a good long term solution. I've increasingly been thinking about problems like this as something like Groovy's macro annotations might solve well.