#710 Actor programming example?

yachris Fri 7 Aug 2009

Hello,

So first of all, I have to say that Fan is... fantastic (maybe that should be the new name :-) I'm just having tons of fun, particularly for doing GUI stuff. Wow, it's so wonderful to not have to deal with so much of the Java cruft.

However, I've got that "You're doing it wrong" :-) feeling. I'm trying to put together a server which listens to a port, builds a socket, retrieves data, and does stuff with it.

I've done this in Java, and the usual model is to do the accept in a thread which, when it gets a client connecting to it, spawns off another thread on the new socket to deal with the communication, so the main thread can go back to waiting for other clients.

I'd REALLY appreciate it if the cookbook section could have some Actor examples... it's just way not clear how to do it, and my example code, which works, is ugly.

Thanks!

brian Sat 8 Aug 2009

Promoted to ticket #710 and assigned to brian

probably a good idea since that is a new topic for many Java programmers

in the meantime, you might want to take a look at the source for code for wisp which implements an HTTP server using actors

yachris Tue 11 Aug 2009

Thanks for the useful pointer.

I got a something like the Wisp server example going. I have one class which is a basic server, which creates one Actor subclass to listen for new sockets, which then get handed off to another Actor subclass to handle them. Separately, I have a client class which opens a socket to the server, sends something across, gets something back and shows it (all Strings for the moment).

However, I'm stuck again...

What I want is a GUI which receives and displays messages by listening to a socket. I can't send an Actor a reference to my GUI class (it's not const). I can't seem to make the GUI class a Singleton so I can get the one true instance in the Actor code (NOTE: a lack of Singletons may be a good thing...)

Any clues here?

brian Tue 11 Aug 2009

this is a good question

what we have now is fwt::Desktop.callAsync which an actor could use to invoke a function in the UI thread (which could poke the UI widgets to update)

but I haven't actually built out this test case myself, so I am not quite sure if we have everything in place

we do have a bit of existing UI code that works with actors you can poke around in:

  • flux::Console (uses Actor.locals to stash top level frames and callAsync)
  • flux::FileIndex (runs background actor to perform file indexing)

Console is probably a good example for a "push model", and FileIndex a "pull model".

if you want to try it out and provide some feedback then we can evaluate what enhancements are needed to make this easier

KevinKelley Tue 11 Aug 2009

I've done a couple variations of this lately. The key, and the only way I've found, is to register the UI widget under a const handle of some form, a Str as in the flux::Console example is simplest. Then Desktop.callAsync a function that looks up the widget from its handle and does an update on it.

In UI setup code:

Actor.locals[id] = widget  // id can be Str or any const object

In the Actor receive method:

fwt::Desktop.callAsync(&doUpdate)

In the same actor class:

Void doUpdate()
{
  widget := Actor.locals[id]
  widget->repaint 
}

Next thing is to pass around some actual data; simplest thing is to declare a MessageHandler mixin, and instead of registering the widget, implement a handleMessage(Message msg) in some ui class; then the actor looks up a messageHandler and passes a message (any const data you need) to it.

The key issue is, only the UI thread can know about the actual widget object. But anybody can pass around handles to one. So somewhere that you have access to the widget, register it into thread-local storage (you're on the UI thread or you wouldn't have a widget), keep the handle for your actors, and whenever you need to update the ui use Desktop.callAsync and pass the handle so the UI thread can recover the widget.

The recent talk about making closures respond to toImmutable should make possible some interesting patterns for this, but the above is the basic scheme that works now.

helium Tue 11 Aug 2009

OK, I haven't realy looked into Fans "actors", but to me it seems like Fan's "actors" aren't actors at all. An actor is its own process/thread. It can do whatever it wants to and can actively call receive to wait for a message.

Fan's "actors" are something different. You don't ask for messages, you aren't even "alive" but some thread pool calls your receive function whenever it has time and a message for you.

It's an inversion of control.


BTW, a total different topic: Fan claims not to have shared state but aren't actors somehow all about sharing state and originally in Erlangs case in a distributed setting over a network between multiple machines?

Here a simple example that encapsulates an actor to make it very obvious that you can share state in Fan:

const class SharedState 
{   
   private static const ActorPool pool := ActorPool()
   private const Actor state := Actor(pool, &receive)

   new make(Obj? val) { set(val) }

   private Obj? receive (Obj? message, Context context)
   {
      if (message != null)
         context["value"] = message         
      return context["value"]
   }

   Obj get() { state.send(null).get }

   Void set(Obj? newVal) { state.send(newVal) }
}

class Test
{
   Void main()
   {  
      // look, a mutable const class:
      testState := SharedState(42)

      echo(testState.get())   // 42

      testState.set((Int)testState.get() + 1)

      echo(testState.get())   // 43
   }
}

You can pass a SharedState around between actors as it's a const class yet it's mutable.

Actors are inherently shared state and so you have all the race condition problems, etc. That only as a general information regarding actors because some people seem not to be aware of it.

brian Tue 11 Aug 2009

OK, I haven't realy looked into Fans "actors", but to me it seems like Fan's "actors" aren't actors at all. An actor is its own process/thread.

Virtually every actor model I know about does not map actors directly to OS level threads. Otherwise you would max out at a couple thousand actors. For example Erlang implements its own "green threads", although it calls them processes. Scala has two different modes, the preferred being similar to Fan's model. So to acheive 100,000s of actors in a given system, the design challenge is to figure how to multiplex actors onto threads. I think the current Fan design works quite well for that because it lets you group things into backing thread pools.

Fan's "actors" are something different. You don't ask for messages, you aren't even "alive" but some thread pool calls your receive function whenever it has time and a message for you.

There is nothing that requires this. In fact you can use an Actor just like a thread and spawn it off and never wait for messages. I do this all the time. However you must be aware that you are pinning one of the threads in that actor's pool. For example to use an actor like a thread:

actor := Actor(ActorPool()) |msg|
{
  while (true)
  {
    Actor.sleep(1sec)
    echo("looping...")
  }
  return null
}
actor.send("start")
actor.pool.join // blocks forever    

BTW, a total different topic: Fan claims not to have shared state but aren't actors somehow all about sharing state and originally in Erlangs case in a distributed setting over a network between multiple machines?

The goal of actors is to share state - if we couldn't pass data between actors then they wouldn't be much use. The goal is avoid sharing mutable state. Whenever you are working with a given object, Fan guarantees that it can never mutate underneath you, because the current thread is the only one who can access it. This doesn't prevent deadlocks, but it moves concurrency to a higher level from low level locks and makes it vastly easier to reason about.

To take your example, what Fan guarantees is that the object passed from get/set to the actor is either immutable or serialized b/w threads. So under no circumstances could I call get and then have that object mutate underneath me. Although like any function, calling get multiple times might return different objects. And because you are using a blocking send, there is still a chance for deadlock - but the easy rule to follow is never perform a blocking send if you receive messages yourself.

helium Tue 11 Aug 2009

Virtually every actor model I know about does not map actors directly to OS level threads.

That's not what i meant.

I wanted to destinguish between an active thing (no matter how it's actually implemented) that can call/use a recive construct when it want's to receive a message versus a passive thing that gets called whenever there is a message.

The goal is avoid sharing mutable state.

Can you call a state that cannot change/mutate a "state"? And I just showed how to create mutable shared state, didn't I.

Perhaps I have to make it even more obvious. Let's write a small C# program:

class Program
{
    static int sharedMutableState = 0;

    static void Main(string[] args)
    {
        var thread = new Thread(new ThreadStart(show));
        thread.Start();

        while (true)
        {
            sharedMutableState = sharedMutableState + 1;
            sharedMutableState = sharedMutableState + 1;
        }
    }

    static void show()
    {
        while (true)
            Console.WriteLine(sharedMutableState);
    }
}

There is the main thread and another thread that I created. Both access a static variable that I called sharedMutableState and one of them in this example mutates it, but theoretically both could. Two threads access the state so it's shared and both could mutate it so it's mutable, so I assume we can call this shared mutable state. Any objections so far?

Let's write a small Fan program using the class I wrote earlier:

const class Program
{
   const SharedState sharedMutableState := SharedState(0)

   Void main()
   {      
      thread := Actor(ActorPool(), &show)
      thread.send(null)

      while (true) {
         sharedMutableState.set((Int)sharedMutableState.get() + 1)
         sharedMutableState.set((Int)sharedMutableState.get() + 1)
      }
   }

   Void show()
   {
      while (true)
         echo(sharedMutableState.get())
   }
}

Astonishingly similar to the C# code, isn't it? Two threads can access the state that is stored in sharedMutableState and both can mutate it, at least that's how I see it and that's why I'd call this shared mutable state.


I don't want to say that anything about this is bad. I just wanted to point this out. When you write Erlang code you eventually will have to debug race conditions despite Erlang being very functional and and not having any obvious mutable variables, but state is state and shared is shared no matter whether you put it inside some higher level construct. And race conditions are just as hard to debug in Erlang because as everybody knows these things don't happen deterministicly. There was recently a persentation on infoq.com about testing and debugging Erlang that perhaps shows you some of the problems when programming with actors.

JohnDG Tue 11 Aug 2009

perhaps shows you some of the problems when programming with actors.

Actors are fundamentally unsound, and can suffer from all the same issues that manual locking can. That said, many people find it easier to write working multithreaded code with actors than with standard synchronization primitives.

STM is one of the few safe, composable means of multithreading. Eventually Fan can get that via a compiler plug-in.

tcolar Tue 11 Aug 2009

Not sure that's really an issue with Actors. It's more a side effect of being able to re-assing a const multiple times in a constructor(which I wonder the why) Maybe it should only allow a "final" const

I think I see Hellium's point, though you really have to write this the "right" way to make it an issue though.

andy Tue 11 Aug 2009

I see where you're coming from helium. We can't ever make Fan fully safe, because you still have FFI and natives at the end of the day. All we can do is make it as easy as possible to make concurrent programming easier. And I think the current Actor model makes the right trade-offs in easy-of-use and shoot-me-in-the-foot. That being said, I think there is still much room for improvement in the currency APIs, which will mature as we gain experience moving forward.

brian Tue 11 Aug 2009

I wanted to destinguish between an active thing (no matter how it's actually implemented) that can call/use a recive construct when it want's to receive a message versus a passive thing that gets called whenever there is a message.

OK, I see what you mean, something like:

Void loop()
{
  init
  while (isAlive) process(receive)
  cleanup
}

That was the old model we had, but it suffered some serious problems regarding the ability to ensure orderly cleanup and management of state between threads. So the new way isn't perfect, but I think is a pretty good compromise - you just need to do your init/cleanup in a message and store to the context.

Two threads can access the state that is stored in sharedMutableState and both can mutate it, at least that's how I see it and that's why I'd call this shared mutable state.

Well overall application state that doesn't mutate over time isn't very useful, but I see what you mean. What Fan guarantees is that the messages passed b/w actors are guaranteed to never mutate - once I send/receive a given message that object itself will never mutate unless my actor does so. But no guarantee is made that subsequent messages won't be different. Conceptually in your example exactly one actor/thread is the master of the shared state, but in practice that doesn't stop you from creating a race condition in how that state is used.

So if your point is that terminology is incorrect, I am open to suggestions. Fan requires state to be passed between actors using messages and those messages are guaranteed to not mutate. But overall application state is of course mutable.

If your point is that actors don't prevent race conditions or deadlocks, then that is spot on. But actors do raise the abstraction level and make these problems much easier to reason about. There is definitely a huge difference in how you program the locking model versus the actor model.

yachris Wed 12 Aug 2009

Have you guys looked at Hoare's Communicating Sequential Processes (CSP) paradigm? It's a specific set of constructs that are useful for both general purpose parallel programming, both one-machine and distributed.

The interesting claim is that you can programmatically verify that there is no deadlock, livelock, or other contention problems in a given program. I think this would probably be possible for Fan, given its tractable syntax (and/or the analysis could possibly be done on the bytecode).

This three-part article gives a good overview of the concepts, with a view towards pointing you towards the JCSP library (which I haven't used). JCSP is at least partly commercial.

brian Wed 12 Aug 2009

Have you guys looked at Hoare's Communicating Sequential Processes (CSP) paradigm? It's a specific set of constructs that are useful for both general purpose parallel programming, both one-machine and distributed.

Definitely an interesting read, thanks for passing it along. But fundamentally that appears to be an actor model with message passing (just they call them channels). So it wasn't clear to me how it truly prevents race conditions. Although the real problem tends to be blocking on a future - but that is so damn useful that I can't image trying to work around that limitation.

KevinKelley Wed 12 Aug 2009

The building block in CSP is a process that can only access its own local data and methods; this removes a lot of the confusion of Java-style threads where any method could be run by any thread.

Then these CSProcesses operate on inputs and produce outputs; this gives a mechanism for composing a network of CSPs to do a job; which can be further composed...

I haven't used CSP since a college project in OCCAM on transputers, years ago. Seems like good stuff though, and the JCSP lib looks real interesting.

I notice there's mention of interfacing to AWT/Swing by treating the Listener-style interfaces as CSP inputs/outputs, which sounds like it could be a powerful way to do UI multithreading.

It certainly seems more promising than the Java UI-threading model, which is: DON'T. :-)

I want to experiment with this; with FFI it should be just a matter of plug it in and try it out.

My take on what I know so far is, CSP doesn't prevent race conditions; it provides a formalism where it's possible to prove whether or not you have them. How that proof process works out in real designs, I don't know. It certainly does sound like a good way to manage the complexity, though.

My other take, which I can't at all prove, is that any language that is Turing-complete, can be used to program a race condition. I think to make a language completely unfarkable you'd have to remove its ability to execute code. It's just that we need a better model for managing the power than just Java's monitors. CSP seems to have mechanisms that might fit well with Fan.

yachris Thu 13 Aug 2009

So it wasn't clear to me how it truly prevents race conditions.

I'm not an expert in CSP, but I think the idea is that it doesn't prevent all the horrors of MIMD programming, but it DOES give you the ability to write a program which will analyze CSP-based programs, and tell you if those problems can occur in the studied program. The analysis program would just have to be written once. It would be a major thing to be able to offer that as part of the Fan standard. It's kind of like proving programs correct, without the writing a proof part, the software does it for you :-)

It certainly seems more promising than the Java UI-threading model, which is: DON'T. :-)

Heh. However, it would be REALLY nice to have some of the new(ish) things like LinkedBlockingQueue be part of Fan... it's so nice to have both the put/take (blocking) and offer/poll (non-blocking) type queue behavior, with a limited size. Maybe with Fan it would be limited to immutable/serializable objects? Sorry, I have no idea whatsoever about how this would be realized in the .NET libraries (let alone javascript!).

brian Thu 13 Aug 2009

Heh. However, it would be REALLY nice to have some of the new(ish) things like LinkedBlockingQueue be part of Fan

This is effectively what actors are - a simple, but powerful message queue. When you send a message it gets checked for immutability or serialized and stuck on a queue. Then the receiver dequeues these messages in order. All this is cleanly mapped onto a thread pool, futures, and scheduling infrastructure. Plus one of the really important thing these queues can do is coalesce messages which I've found to be an indispensable feature.

KevinKelley Thu 13 Aug 2009

A couple comments on the Actor docs, while we're on this subject. Kind of nitpicky, but it's a tricky area.

One, there's several places where "thread" is mentioned, and that doesn't quite jibe: in fact an actor may be running on any one of the threads in its ActorGroup pool. So when docLang::Actors#locals talks about Actor Locals as being local to the "thread", or same thing in sys::Actor#locals doc, it doesn't sound quite right. Instead of "visible only on the current thread", should it read "to this Actor", or maybe to any actors that share the same pool?

The other thing is the example in the Coalescing Messages section: that's kind of a newbie trap, putting in an example code that can't compile. Would suggest either a note to that effect, or better, show how that repaint example could actually work.

brian Thu 13 Aug 2009

I fixed the Actor.locals fandoc

the coalescing messages is probably best pulled out into cookbook as a full example

BTW, if anyone wants to submit examples for the cookbook, that is a great way to help out

brian Thu 13 Aug 2009

KevinKelley donated an example which shows a FWT clock which updates with a background actor, good illustration of using actors with FWT. I pushed to hg changeset 976169f7edc3

thanks Kevin!

brian Tue 8 Sep 2009

Ticket resolved in 1.0.46

added new examples\sys\actor.fan script

Login or Signup to reply.