Using Futures with a FSM

696 views
Skip to first unread message

Steffen Fritzsche

unread,
Jun 7, 2012, 11:35:44 AM6/7/12
to akka...@googlegroups.com
Dear hakkers,

I have a FSM to coordinate a long running analysis process. During that process I have to load data from my postgres db at various stages. Since the database access is synchronous by nature my access layer is wrapping those calls in futures.

Now, I'm not sure how I should await the results of those database operations within my FSM. I would like to do something like this:

when(foo) {
case Event(LoadingEvent, _) =>
// start loading -> returns a Future[LoadingResult]
goto(LoadingState)
}

when(LoadingState) {
case Event(LoadingFinished, LoadingResult) =>
// process result and go to next state
}

but I've got no idea how to accomplish that the akka way :(

One idea was to implement "foo" as

when(foo) {
case Event(LoadingEvent, -) =>
dbActor ? LoadFooData mapTo manifest[LoadingResult] onComplete {
case Right(result) => self ! Event(LoadingFinished, result)
...
}
goto(LoadingState)
}

Would this be a good solution?

Thanks for any suggestions!
Steffen

Roland Kuhn

unread,
Jun 7, 2012, 2:57:17 PM6/7/12
to akka...@googlegroups.com
Hi Steffen,
This depends on what you have left out: will the actor do anything else in the LoadingState? Is the dbActor shared between more client actors, i.e. not just for this purpose? As I guess that the answer to the second question is probably “yes”, then that actor will do the blocking (otherwise you could just do the blocking call from the FSM). Using Futures should not be necessary unless there could be confusion wrt. the results which you receive back.

One thing: your usage of Event() seems a bit confused, because you send it around. That case class is only manufactured by the FSM trait in order to pass the (msg, data) pair into your when-blocks, meaning that you probably want to send to yourself something like LoadingResult(result). Depending on what you want to do in the Left(...) case, you could also use pipeTo instead (import akka.pattern.pipe).

Regards,


Roland Kuhn
Typesafe – The software stack for applications that scale.
twitter: @rolandkuhn


andy

unread,
Jun 7, 2012, 3:01:13 PM6/7/12
to akka...@googlegroups.com
Greetings,

The way I accomplish this is similar to what you have except you will need to extract and pass along the sender with your messages.

when(READY) {
  case Event(LoadEvent,_) =>
    val respondTo = context.sender // extract the sender 
    ask( dbActor, LoadData ).mapTo[LoadingResult].map { result => ( result, respondTo ) } pipeTo self
    goto( AWAIT_RESULT )
}

when( AWAIT_RESULT ) {
  case Event( (result :LoadingResult, respondTo :ActorRef), _ ) =>
    // do something with result
    respondTo ! result 
}


This works, however it has some weaknesses. First it will only handle a single loading request at a time and two you will need to decide if you want to buffer or drop load requests you get while in the await result state. 

Here are some things to remember. 

If you are using a synchronous / blocking api the there will be blocking somewhere thats just the way it is. Always trying to push off the blocking can lead to confusing code. Sometimes it is better just to do the synchronous call in your actor if it leads to more understandable code. You just need to remember this unbreakable rule. NEVER ( EVER, EVER, EVER ) have an actor block waiting for a response from something that shares the same dispatcher. If you do this you will ruin christmas by creating thread starvation deadlocks which are a nightmare to diagnose. 

Don't close over mutable data in your future mappings in the actor. What I mean is if in the future mapping you used " map.{ result => ( result, context.sender ) } " there is no telling where your response will be forwarded to because "context" is mutable and changes with every new message processed. By the time the future is mapped the context.sender could be a different sender than the one you intend. This is why you must extract any mutable data into a val on the handling blocks local stack.

If you are mixing domain state with process state in a single FSM your code will become hopelessly complex because of all the state combinations. Separate your state machines into a domain state machine passed around in StateData and use the FSM for your process state machine.

Hope this helps.

All the best,
Andy

andy

unread,
Jun 7, 2012, 3:03:08 PM6/7/12
to akka...@googlegroups.com
sorry forgot the goto ready...

when( AWAIT_RESULT ) {
  case Event( (result :LoadingResult, respondTo :ActorRef), _ ) =>
    // do something with result
    respondTo ! result 
    goto( READY )
}

-A

Roland Kuhn

unread,
Jun 7, 2012, 6:23:42 PM6/7/12
to akka...@googlegroups.com, akka...@googlegroups.com
Hi Andy,

thanks for this reply, it contains many gold nuggets!



Regards,

Roland Kuhn
Typesafe — The software stack for applications that scale
twitter: @rolandkuhn
--
You received this message because you are subscribed to the Google Groups "Akka User List" group.
To view this discussion on the web visit https://groups.google.com/d/msg/akka-user/-/Q7_yFbb-HFUJ.
To post to this group, send email to akka...@googlegroups.com.
To unsubscribe from this group, send email to akka-user+...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/akka-user?hl=en.

√iktor Ҡlang

unread,
Jun 7, 2012, 6:38:13 PM6/7/12
to akka...@googlegroups.com
Spot on Andy!

--
You received this message because you are subscribed to the Google Groups "Akka User List" group.
To view this discussion on the web visit https://groups.google.com/d/msg/akka-user/-/65I7hLXLKHcJ.

To post to this group, send email to akka...@googlegroups.com.
To unsubscribe from this group, send email to akka-user+...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/akka-user?hl=en.



--
Viktor Klang

Akka Tech Lead
Typesafe - The software stack for applications that scale

Twitter: @viktorklang

Steffen Fritzsche

unread,
Jun 8, 2012, 4:20:07 AM6/8/12
to akka...@googlegroups.com
Thanks for your enlightening answers!

> This depends on what you have left out: will the actor do anything else in the LoadingState? Is the dbActor shared between
> more client actors, i.e. not just for this purpose? As I guess that the answer to the second question is probably “yes”, then that actor will do the blocking (otherwise you could just do the blocking call from the FSM). Using Futures should not be necessary unless there could be confusion wrt. the results which you receive back.

Roland, you are right, there are situations where the actor does some more calculations when in LoadingState. However, then this isn't a LoadingState anymore but the problem remains the same. The dbActor is also used by other actors. Moreover, some db calls are writes with potentially lots of data, this might take some minutes, that's why I decided that it's best to always return a future to make this obvious in the api. That way I'm able to control the future timeouts right in my db api where it's obvious how long those things might run in the worst case.

> One thing: your usage of Event() seems a bit confused, because you send it around. That case class is only manufactured by the FSM trait in order to pass the (msg, data) pair into your when-blocks, meaning that you probably want to send to yourself something like LoadingResult(result). Depending on what you want to do in the Left(...) case, you could also use pipeTo instead (import akka.pattern.pipe).

Oh, yeah, you are totally right with the Event() message, this should be my own message type. Regarding the Left() case I might send an alternative event to go into a failure state. But you are probably right with this one too, it might be best to just forward the result and do the logic in the LoadingState. In fact that's the place where the result interpretation should be done, otherwise this is very confusing.

> The way I accomplish this is similar to what you have except you will need to extract and pass along the sender with your messages.


Andy, your pointer to extract the sender makes total sense. However, in my case the fsm only sends messages to itself and the self reference should be stable. It's not necessary to return anything to the original sender once the start of the process is confirmed. The startup process includes the creation of a separate process actor which is feed by the fsm and it's spawn analysis actors and can be queried by any other actor including the original sender later on. After all the whole analysis process might take anything between couple of minutes up to two or three days and is triggered using a spray-based web-interface. You see, the original sender might be gone anyways ;)

> If you are mixing domain state with process state in a single FSM your code will become hopelessly complex because of all the state combinations. Separate your state machines into a domain state machine passed around in StateData and use the FSM for your process state machine.


I have to think about this. Maybe this would ease things a bit. Point is, the loading (and storing) is part of the analysis and hence not always easily to decouple. One solution I'm thinking of right now is to split the analysis fsm itself into smaller sub fsm to handle the intermediate analysis state.

It's time to do some hakking! Lot's of inspiration! Thank you guys!

Steffen


Reply all
Reply to author
Forward
0 new messages