#2262 Fantom, Actor.locals, mutability

tomcl Tue 15 Apr 2014

I've been trying to make sense of Actors in Fantom, to write them up for Hertz's book as part of learning the language.

Actor.locals is not my favourite thing, though I understand it can be used (and have used it).

But I think small changes to the language could make this area better, and clearer, without breaking anything.

Here is my problem with Actor.locals.

Ideally Actors should have no internal state, and execute concurrently - in fact different messages going to the same actor instance could be handled in different threads (and so overlapped) if actors really had no internal state.

But that is not typically what happens. Often one actor instance is used as a thread of computation (which however is scheduled as an ActorPool thinks is good on and off multiple CPUs.

An actor can only be used like this if there is Actor instance-local mutable state. (And the fact that this is ever allowed means that separate messages sent to one actor instance must be handled sequentially - that is not obviously true from the actor model).

Actor.locals is used to implement this, in a messy way. Also in an unsafe way because the same mechanism can be used to share mutable state between instances or actors.

What is really wanted is instance-local mutable state, which is a mutable private non-static field in the Actor subclass that moves with the actor instance from thread to thread as needed. Allowing mutable state of this type is exactly what is often needed in concurrent message-passing computation, so why restrict it?

Actors.local could remain as an escape-hatch for evil things and to keep backwards-compatibility.

So that would be to allow a field storage class qualifier actorlocal that could only be used in Actors and made fields that were mutable, non-static, private, actor-local. These fields could be initialised (from a constructor, or in the class definition).

Adding this to the core language is not too horrible since it has no compile-time correct use except when using actors. If you wanted to throw away concurrent later it would be harmless.

SlimerDude Tue 15 Apr 2014

This is my understanding of how Actors work (and I hope I'm correct!)...

Actor.locals ... unsafe ... used to share mutable state between instances

Not to my knowledge.

The map in Actor.locals() is bound to an Actor instance and that instance alone. The only way to give data to an Actor is through messages, or const fields.

Which would mean Fantom already does what you propose!

tomcl Tue 15 Apr 2014

I'm glad about that. I was not sure.

But it does what I propose in a messy way. Conceptually, it should be private actor-instance local fields added when extending Actor. That would make code much clearer, and be less obscure for somone learning the language.

You would not even need an extra keyword - there is no other sensible meaning for a private field defined on an actor - and such could be allowed.

SlimerDude Tue 15 Apr 2014

there is no other sensible meaning for a private field defined on an actor

Not sure I agree. Because then you're making a special case for fields defined in an Actor or a subclass. You'd be mixing non-core class implementations (concurrent::Actor) with core Fantom syntax (special meaning for fields).

And bear in mind that fields and instance methods on an Actor can be called normally, outside of the whole message passing construct.

It is conceptually difficult, because depending on whether the code is running from within receive() or not, depends on what is returned from Actor.locals(). It's a bit like having both client and server code on the same class - it's easy to loose track of where you are and try to access data that doesn't exist in the current thread context. But in the core API, I can't see a cleaner way to do it.

It's also why I spent some time coming up with the SynchronizedState class. It processes closures passed to withState() on the other side of the message, giving access to a mutable data object.

As you can only access the mutable data though the closures, it's type safe. That way, I find it easy to know in which thread context I'm in. I say more in this Fantom-Factory article.

SlimerDude Tue 15 Apr 2014

But in the core API, I can't see a cleaner way to do it.

Actually, if you couldn't subclass Actor but were forced to pass a receive func in, that might help!?

tomcl Tue 15 Apr 2014

I'd prefer a special storage class.

But a mutable field is not possible in a const class except in this special case (an actor) so I can't see that it is changing the core language.

I don't think it is unclear what is what. The receive method executes and can mutate private state on its actor instance, just as for any method of any class. The fact that it is an actor means that the method execution, and its local state, is not bound to any one thread of computation. The private local state cannot be accessed except through receive. (OK - that means that you cannot define other methods on the actor that access this state. Sort of like a static local in receive but can't be that because it is attached to the actor instance not the receive closure).

OTOH - if you are saying that you don't like actors having private mutable state and would rather encapsulate that in another class then I don't exactly disagree. Certainly type-safety is needed and Actor.locals, being non-type-safe, is not nice.

But I'm not sure I agree. There is no general way to deal with concurrency that fits all uses: message passing introduces dangerous mutable state by the back door as state of actors, if actors have no state you have data-flow computation which is fine for concurrency but limited.

If actors have state I don't see there is anything wrong with making that explicit in the normal way (mutable fields on actor instances). Why have to package operations on mutable state with closures?

The argument for doing this is that since conceptually operations on mutable state should be commutative, maybe if they are made explicit via closures it is clearer. I'd have to think about that.

brian Tue 15 Apr 2014

We have discussed a special storage class for fields in the past, but most of the time what I find works best is to just pull the mutable state out into a separate class. I think having a bunch of actor local fields would actually make things more confusing. So my thoughts have always been more along what we need is a little mini-framework above Actor and Actor.locals that makes actors easier to write. I've found over the years that there is definitely a series of boiler plate techniques I use for almost every Actor. I just have never arrived at a clean design that didn't involve language/compiler changes.

SlimerDude Tue 15 Apr 2014

You can always wrap Actor.locals() in a type-safe synthetic field:

const class Example : Actor {

  Str localStr {
    get { Actor.locals["localStr"] }
    set { Actor.locals["localStr"] = it }
  }
}

But it doesn't limit access to any particular thread.

: \

Login or Signup to reply.