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
|
||||
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
|
||||
requests, etc. These data are what make an actor valuable, and they must be
|
||||
protected from corruption by other actors. The good news is that Akka actors
|
||||
conceptually each have their own light-weight thread, which is completely
|
||||
shielded from the rest of the system. This means that instead of having to
|
||||
synchronize access using locks you can just write your actor code without
|
||||
worrying about concurrency at all.
|
||||
the :ref:`fsm-scala` module), or it could be a counter, set of listeners,
|
||||
pending requests, etc. These data are what make an actor valuable, and they
|
||||
must be protected from corruption by other actors. The good news is that Akka
|
||||
actors conceptually each have their own light-weight thread, which is
|
||||
completely shielded from the rest of the system. This means that instead of
|
||||
having to synchronize access using locks you can just write your actor code
|
||||
without worrying about concurrency at all.
|
||||
|
||||
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
|
||||
|
|
|
|||
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
|
||||
agents
|
||||
transactors
|
||||
fsm
|
||||
extending-akka
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
.. _fsm:
|
||||
.. _fsm-scala:
|
||||
|
||||
###
|
||||
FSM
|
||||
|
|
@ -21,7 +21,8 @@ A FSM can be described as a set of relations of the form:
|
|||
|
||||
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
|
||||
================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue