ANN: Axon 1.0

Michael Sparks zathras at thwackety.com
Sat Dec 25 02:32:24 CET 2004


Hi,


Axon is the core set of modules in Kamaelia[1], and is essentially a set
of tools for managing concurrency inside a single thread.  Whilst it is a
pre-requisite for Kamaelia, it can be used independently as well.

Rather than the usual statemachine (or state machine derived) approach it
uses communicating generators. The design of the system is inspired very
heavily by asynchronous hardware verification systems. (Which leads to
similarities with CSP & unix pipelines)

[1] Kamaelia is BBC R&D testbed for developing media network protocols.
    (It takes it's name from an internal BBC R&D project aimed at looking
    how we scale internet delivery of the BBC's TV & Radio output.)

Sourceforge page with Axon release:
    http://sourceforge.net/projects/kamaelia/

Installation is the usual "python setup.py install" dance, and the API
should be fairly stable, and has evolved over a period of time, hence the
1.0 release version. The project became test infected rather late in the
day, so some of test suite has been retrofitted if it looks a bit odd!

Kamaelia's website:
    http://kamaelia.sourceforge.net/

(Explains context and there's a presentation that contains a simplified
implementation using decorators, which might help show use cases)

I've copied much of the README at the end of this mail for the curious.
(License is the Mozilla tri-license : MPL1.1, GPL2.0, LGPL2.1)

Merry Christmas! (Hopefully someone will find this a nice christmas toy :)


Michael.
--------
Axon is the core of Kamaelia. The contents of this directory must be
installed before the rest of Kamaelia can be used. It can also be used
independently of Kamaelia.

The install procedure is python's usual dance:

   * python setup.py install

Documentation is held in two places:
   * The usual 'pydoc name' - probably worth starting with:
     pydoc Axon.Component

   * The test suite is designed to allow you to get low level API behaviour
     information - "should X,Y, Z work? If so, what happens?". It's a partly
     retrofitted test suite, but some is TDD. (TDD took over late in the
     project) As a result, for example, passing a -v flag result in the
     docstring for each test to be dumped in a form that allows collation,
     and summarisation. (For an example of what we expect to automate from
     the test suite, see the end of this README file)

Sample producer/consumber & wrapper component system:

    /-- testComponent -----------------------------------------------\
    |                                                                |
    |  +-- Producer ----+          +-- Consumer ----+                |
    |  |            |result|--->|source|         |result|--->|_input||
    |  +----------------+          +----------------+                |
    |                                                                |
    \----------------------------------------------------------------/

The testComponent creates 2 subcomponents, creates the links in place, and
takes the output from the consumer and links it to its own private/internal
_input inbox. When it recieves a value from the consumer, it reports this
fact and ceases operation.

   Producer sends values to its result outbox
   Consumer takes values from its source, does some work and sends results to
      its outbox

(It's probably worth noting that an introspection system would be possible
to write/nice to see that would be able to derive the above diagram from
the running system)

Example code:

class Producer(component):
   Inboxes=[]
   Outboxes=["result"]
   def __init__(self):
      self.__super.__init__()
   def main(self):
      i = 100
      while(i):
         i = i -1
         self.send("hello", "result")
         yield  1

class Consumer(component):
   Inboxes=["source"]
   Outboxes=["result"]
   def __init__(self):
      self.__super.__init__()
      self.count = 0
      self.i = 30
   def doSomething(self):
      print self.name, "Woo",self.i
      if self.dataReady("source"):
         self.recv("source")
         self.count = self.count +1
         self.send(self.count, "result")

   def main(self):
      yield 1
      while(self.i):
         self.i = self.i -1
         self.doSomething()
         yield 1

class testComponent(component):
   Inboxes=["_input"]
   Outboxes=[]
   def __init__(self):
      self.__super.__init__()
      self.producer = Producer()
      self.consumer = Consumer()
      self.addChildren(self.producer, self.consumer)
      self.link((self.producer, "result"), (self.consumer, "source"))
      linkage(self.consumer,self,"result","_input", self.postoffice)

   def childComponents(self):
      return [self.producer, self.consumer]

   def mainBody(self):
      while len(self.inboxes["_input"]) < 1:
         yield 1
      result = self.recv("_input")
      print "Consumer finished with result:", result, "!"

r = scheduler()
p = testComponent()
children = p.childComponents()
p.activate()
for p in children:
   p.activate()
scheduler.run.runThreads(slowmo=0)

(It would probably be nice to have better syntactic sugar here by using
dictionaries, operators (eg '|' ) and decorators. The presentation on the
website on Kamaelia shows a partial semi-reimplementation of ideas using
decorators to eliminate the classes above)

For various reasons it makes sense to run all Axon code using the -OO flags
- this is due to the currently highly inefficient debug framework. One
downside of async systems is that debuggers tend to have a hard time - but
this has been thought of upfront :), the downside is that if you run with
debugging enabled/possible, then the system runs like a pig. (Due to some
rather heavy duty fumbling around in the garbage collector)


Michael, December 2004

-----------------------------------------------------------------------------

Example of expected autodocs from test suite:
(Ideally these would be merged with (or replace!)the doc strings/output
from pydoc.)

./test_Component.py -v 2>&1 | ~/bin/parsetestResults.pl

Standard:
   __init__
       Class constructor is expected to be called without arguments.

   __str__
       Returns a string representation of the component - consisting of
       Component, representation of inboxes, representation of outboxes.
       Returns a string that contains the fact that it is a component object
       and the name of it.

Public:
   addChildren
       All arguments are added as child components of the component.

   childComponents
       Returns the list of children components of this component.

   closeDownComponent
       stub method, returns 1, expected to be overridden by clients.

   dataReady
       Returns true if the supplied inbox has data ready for processing.

   initialiseComponent
       Stub method, returns 1, expected to be overridden by clients.

   link
       Creates a link, handled by the component's postman, that links a
       source component to it's sink, honouring passthrough, pipewidth and
       synchronous attributes.

   main
       Returns a generator that implements the documented behaviour of a
       highly simplistic approach component statemachine.
       This ensures that the closeDownComponent method is called at the end
       of the loop.  It also repeats the above test.

   mainBody
       stub method, returns None, expected to be overridden by clients as the
       main loop.

   recv
       Takes the first item available off the specified inbox, and returns
       it.

   removeChild
       Removes the specified component from the set of child components and
       deregisters it from the postoffice.

   send
       Takes the message and places it into the specified outbox, throws
       an exception if there is no space in a synchronous outbox.

   synchronisedBox
       Called with no arguments sets the outbox 'outbox' to being a
       synchronised box, with a maximum depth of 1.

   synchronisedSend
       Takes a list of things to send, and returns a generator that when
       repeatedly called tries to send data over a synchronised outbox.

private:
   __addChild
       Registers the component as a child of the component. Internal function.

   _activityCreator
       Always returns true.  Components are microprocesses instantiated by
       users typically - thus they are creators of activity, not slaves to
       it. Internal function.

   _closeDownMicroprocess
       Checks the shutdownMicroprocess message for the scheduler contains a
       reference to the postoffice associated with the component.
       Returns a shutdownMicroprocess. Internal Function.

   _collect
       Takes the first piece of data in an outbox and returns it. Raises
       IndexError if empty. Internal function.

   _collectInbox
       Tests with default args. All these deliveries should suceed. Internal
       Function.
       Tests with default args. Should raise IndexError as the box should be
       empty in this test. Internal Function.
       Tests with inbox arg. Should raise IndexError as the box should be
       empty in this test. Internal Function.
       Tests with inbox arg. Tests collection. Internal Function.

   _deliver
       Appends the given message to the given inbox. Internal Function.
       Checks delivery to a synchronised inbox fails when it is full using the
       force method.
       Checks delivery to a synchronised inbox fails when it is full.

   _passThroughDeliverIn
       Appends the given message to the given inbox. Internal Function.
       Should throw noSpaceInBox if a synchronised box is full.
       When force is passed as true the box can be overfilled.

   _passThroughDeliverOut
       Appends the given message to the given outbox. Internal Function.
       Checks delivery is limited to the pipewidth.
       Checks delivery is limited to the pipewidth.

   _passThroughDeliverOut_Sync
       Appends messages to given outbox. Should throw noSpaceInBox when full.

   _safeCollect
       Wrapper around _collect - returns None where an IndexError would
       normally be thrown. Internall Function.





More information about the Python-list mailing list