add Java FSM example and reST, see #1428
This commit is contained in:
parent
ee5ae1068b
commit
6afed30d43
6 changed files with 272 additions and 9 deletions
|
|
@ -32,13 +32,13 @@ State
|
||||||
|
|
||||||
Actor objects will typically contain some variables which reflect possible
|
Actor objects will typically contain some variables which reflect possible
|
||||||
states the actor may be in. This can be an explicit state machine (e.g. using
|
states the actor may be in. This can be an explicit state machine (e.g. using
|
||||||
the :ref:`fsm` module), or it could be a counter, set of listeners, pending
|
the :ref:`fsm-scala` module), or it could be a counter, set of listeners,
|
||||||
requests, etc. These data are what make an actor valuable, and they must be
|
pending requests, etc. These data are what make an actor valuable, and they
|
||||||
protected from corruption by other actors. The good news is that Akka actors
|
must be protected from corruption by other actors. The good news is that Akka
|
||||||
conceptually each have their own light-weight thread, which is completely
|
actors conceptually each have their own light-weight thread, which is
|
||||||
shielded from the rest of the system. This means that instead of having to
|
completely shielded from the rest of the system. This means that instead of
|
||||||
synchronize access using locks you can just write your actor code without
|
having to synchronize access using locks you can just write your actor code
|
||||||
worrying about concurrency at all.
|
without worrying about concurrency at all.
|
||||||
|
|
||||||
Behind the scenes Akka will run sets of actors on sets of real threads, where
|
Behind the scenes Akka will run sets of actors on sets of real threads, where
|
||||||
typically many actors share one thread, and subsequent invocations of one actor
|
typically many actors share one thread, and subsequent invocations of one actor
|
||||||
|
|
|
||||||
8
akka-docs/java/code/akka/docs/actor/FSMDocTest.scala
Normal file
8
akka-docs/java/code/akka/docs/actor/FSMDocTest.scala
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
package akka.docs.actor
|
||||||
|
|
||||||
|
import org.scalatest.junit.JUnitSuite
|
||||||
|
|
||||||
|
class FSMDocTest extends FSMDocTestBase with JUnitSuite
|
||||||
174
akka-docs/java/code/akka/docs/actor/FSMDocTestBase.java
Normal file
174
akka-docs/java/code/akka/docs/actor/FSMDocTestBase.java
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
package akka.docs.actor;
|
||||||
|
|
||||||
|
//#imports-data
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import akka.actor.ActorRef;
|
||||||
|
//#imports-data
|
||||||
|
|
||||||
|
//#imports-actor
|
||||||
|
import akka.event.LoggingAdapter;
|
||||||
|
import akka.event.Logging;
|
||||||
|
import akka.actor.UntypedActor;
|
||||||
|
//#imports-actor
|
||||||
|
|
||||||
|
import akka.actor.ActorSystem;
|
||||||
|
import akka.actor.Props;
|
||||||
|
import akka.testkit.TestProbe;
|
||||||
|
|
||||||
|
public class FSMDocTestBase {
|
||||||
|
|
||||||
|
//#data
|
||||||
|
public static final class SetTarget {
|
||||||
|
final ActorRef ref;
|
||||||
|
public SetTarget(ActorRef ref) {
|
||||||
|
this.ref = ref;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Queue {
|
||||||
|
final Object o;
|
||||||
|
public Queue(Object o) {
|
||||||
|
this.o = o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Object flush = new Object();
|
||||||
|
|
||||||
|
public static final class Batch {
|
||||||
|
final List<Object> objects;
|
||||||
|
public Batch(List<Object> objects) {
|
||||||
|
this.objects = objects;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#data
|
||||||
|
|
||||||
|
//#base
|
||||||
|
static abstract class MyFSMBase extends UntypedActor {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is the mutable state of this state machine.
|
||||||
|
*/
|
||||||
|
protected enum State { IDLE, ACTIVE; }
|
||||||
|
private State state = State.IDLE;
|
||||||
|
private ActorRef target;
|
||||||
|
private List<Object> queue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Then come all the mutator methods:
|
||||||
|
*/
|
||||||
|
protected void init(ActorRef target) {
|
||||||
|
this.target = target;
|
||||||
|
queue = new ArrayList<Object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setState(State s) {
|
||||||
|
if (state != s) {
|
||||||
|
transition(state, s);
|
||||||
|
state = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void enqueue(Object o) {
|
||||||
|
if (queue != null) queue.add(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<Object> drainQueue() {
|
||||||
|
final List<Object> q = queue;
|
||||||
|
if (q == null) throw new IllegalStateException("drainQueue(): not yet initialized");
|
||||||
|
queue = new ArrayList<Object>();
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Here are the interrogation methods:
|
||||||
|
*/
|
||||||
|
protected boolean isInitialized() {
|
||||||
|
return target != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected State getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ActorRef getTarget() {
|
||||||
|
if (target == null) throw new IllegalStateException("getTarget(): not yet initialized");
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* And finally the callbacks (only one in this example: react to state change)
|
||||||
|
*/
|
||||||
|
abstract protected void transition(State old, State next);
|
||||||
|
}
|
||||||
|
//#base
|
||||||
|
|
||||||
|
//#actor
|
||||||
|
static public class MyFSM extends MyFSMBase {
|
||||||
|
|
||||||
|
private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Object o) {
|
||||||
|
|
||||||
|
if (getState() == State.IDLE) {
|
||||||
|
|
||||||
|
if (o instanceof SetTarget)
|
||||||
|
init(((SetTarget) o).ref);
|
||||||
|
|
||||||
|
else whenUnhandled(o);
|
||||||
|
|
||||||
|
} else if (getState() == State.ACTIVE) {
|
||||||
|
|
||||||
|
if (o == flush)
|
||||||
|
setState(State.IDLE);
|
||||||
|
|
||||||
|
else whenUnhandled(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transition(State old, State next) {
|
||||||
|
if (old == State.ACTIVE) {
|
||||||
|
getTarget().tell(new Batch(drainQueue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void whenUnhandled(Object o) {
|
||||||
|
if (o instanceof Queue && isInitialized()) {
|
||||||
|
enqueue(((Queue) o).o);
|
||||||
|
setState(State.ACTIVE);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.warning("received unknown message {} in state {}", o, getState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#actor
|
||||||
|
|
||||||
|
ActorSystem system = ActorSystem.create();
|
||||||
|
|
||||||
|
@org.junit.Test
|
||||||
|
public void mustBunch() {
|
||||||
|
final ActorRef buncher = system.actorOf(new Props(MyFSM.class));
|
||||||
|
final TestProbe probe = new TestProbe(system);
|
||||||
|
buncher.tell(new SetTarget(probe.ref()));
|
||||||
|
buncher.tell(new Queue(1));
|
||||||
|
buncher.tell(new Queue(2));
|
||||||
|
buncher.tell(flush);
|
||||||
|
buncher.tell(new Queue(3));
|
||||||
|
final Batch b = probe.expectMsgClass(Batch.class);
|
||||||
|
assert b.objects.size() == 2;
|
||||||
|
assert b.objects.contains(1);
|
||||||
|
assert b.objects.contains(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@org.junit.After
|
||||||
|
public void cleanup() {
|
||||||
|
system.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
79
akka-docs/java/fsm.rst
Normal file
79
akka-docs/java/fsm.rst
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
.. _fsm-java:
|
||||||
|
|
||||||
|
###########################################
|
||||||
|
Building Finite State Machine Actors (Java)
|
||||||
|
###########################################
|
||||||
|
|
||||||
|
.. sidebar:: Contents
|
||||||
|
|
||||||
|
.. contents:: :local:
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
The FSM (Finite State Machine) pattern is best described in the `Erlang design
|
||||||
|
principles
|
||||||
|
<http://www.erlang.org/documentation/doc-4.8.2/doc/design_principles/fsm.html>`_.
|
||||||
|
In short, it can be seen as a set of relations of the form:
|
||||||
|
|
||||||
|
**State(S) x Event(E) -> Actions (A), State(S')**
|
||||||
|
|
||||||
|
These relations are interpreted as meaning:
|
||||||
|
|
||||||
|
*If we are in state S and the event E occurs, we should perform the actions A
|
||||||
|
and make a transition to the state S'.*
|
||||||
|
|
||||||
|
While the Scala programming language enables the formulation of a nice internal
|
||||||
|
DSL (domain specific language) for formulating finite state machines (see
|
||||||
|
:ref:`fsm-scala`), Java’s verbosity does not lend itself well to the same
|
||||||
|
approach. This chapter describes ways to effectively achieve the same
|
||||||
|
separation of concerns through self-discipline.
|
||||||
|
|
||||||
|
How State should be Handled
|
||||||
|
===========================
|
||||||
|
|
||||||
|
All mutable fields (or transitively mutable data structures) referenced by the
|
||||||
|
FSM actor’s implementation should be collected in one place and only mutated
|
||||||
|
using a small well-defined set of methods. One way to achieve this is to
|
||||||
|
assemble all mutable state in a superclass which keeps it private and offers
|
||||||
|
protected methods for mutating it.
|
||||||
|
|
||||||
|
.. includecode:: code/akka/docs/actor/FSMDocTestBase.java#imports-data
|
||||||
|
|
||||||
|
.. includecode:: code/akka/docs/actor/FSMDocTestBase.java#base
|
||||||
|
|
||||||
|
The benefit of this approach is that state changes can be acted upon in one
|
||||||
|
central place, which makes it impossible to forget inserting code for reacting
|
||||||
|
to state transitions when adding to the FSM’s machinery.
|
||||||
|
|
||||||
|
Message Buncher Example
|
||||||
|
=======================
|
||||||
|
|
||||||
|
The base class shown above is designed to support a similar example as for the
|
||||||
|
Scala FSM documentation: an actor which receives and queues messages, to be
|
||||||
|
delivered in batches to a configurable target actor. The messages involved are:
|
||||||
|
|
||||||
|
.. includecode:: code/akka/docs/actor/FSMDocTestBase.java#data
|
||||||
|
|
||||||
|
This actor has only the two states ``IDLE`` and ``ACTIVE``, making their
|
||||||
|
handling quite straight-forward in the concrete actor derived from the base
|
||||||
|
class:
|
||||||
|
|
||||||
|
.. includecode:: code/akka/docs/actor/FSMDocTestBase.java#imports-actor
|
||||||
|
|
||||||
|
.. includecode:: code/akka/docs/actor/FSMDocTestBase.java#actor
|
||||||
|
|
||||||
|
The trick here is to factor out common functionality like :meth:`whenUnhandled`
|
||||||
|
and :meth:`transition` in order to obtain a few well-defined points for
|
||||||
|
reacting to change or insert logging.
|
||||||
|
|
||||||
|
State-Centric vs. Event-Centric
|
||||||
|
===============================
|
||||||
|
|
||||||
|
In the example above, the subjective complexity of state and events was roughly
|
||||||
|
equal, making it a matter of taste whether to choose primary dispatch on
|
||||||
|
either; in the example a state-based dispatch was chosen. Depending on how
|
||||||
|
evenly the matrix of possible states and events is populated, it may be more
|
||||||
|
practical to handle different events first and distinguish the states in the
|
||||||
|
second tier. An example would be a state machine which has a multitude of
|
||||||
|
internal states but handles only very few distinct events.
|
||||||
|
|
@ -21,4 +21,5 @@ Java API
|
||||||
stm
|
stm
|
||||||
agents
|
agents
|
||||||
transactors
|
transactors
|
||||||
|
fsm
|
||||||
extending-akka
|
extending-akka
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
.. _fsm:
|
.. _fsm-scala:
|
||||||
|
|
||||||
###
|
###
|
||||||
FSM
|
FSM
|
||||||
|
|
@ -21,7 +21,8 @@ A FSM can be described as a set of relations of the form:
|
||||||
|
|
||||||
These relations are interpreted as meaning:
|
These relations are interpreted as meaning:
|
||||||
|
|
||||||
*If we are in state S and the event E occurs, we should perform the actions A and make a transition to the state S'.*
|
*If we are in state S and the event E occurs, we should perform the actions A
|
||||||
|
and make a transition to the state S'.*
|
||||||
|
|
||||||
A Simple Example
|
A Simple Example
|
||||||
================
|
================
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue