August'24: Kamaelia is in maintenance mode and will recieve periodic updates, about twice a year, primarily targeted around Python 3 and ecosystem compatibility. PRs are always welcome. Latest Release: 1.14.32 (2024/3/24)

Axon Component Handles

The purpose behind Axon.Handle's is to allow the use of Kamaelia based components and systems in non-Kamaelia systems, in a relatively intuitive way.

Premise

The name Handle derives from the concept of a file-handle. We dropped the name "LikeFile" since whilst it derives from the concept of a file handle, it doesn't use the same API as file() for some good reasons we'll come back to.

A file handle is an opaque thing that you can .write() data to, and .read() data from. This is a very simple concept and belies a huge amount of parallel activity happening concurrently to your application. The file system is taking your data and typically buffering it into blocks. Those blocks then may need padding, and depending on the file system, may actually be written immediately to the end of a cyclone buffer in a journal with some write operations. Then periodically those buffers get flushed to the actual disk.

Based on the fact that file handles are a very natural thing for people to work with, based on their ubiquity, and the fact that it masks the fact you're accessing a concurrent system from a linear one, that's why we've taken this approach for integrating Kamaelia components (which are naturally parallel) with non-Kamaelia code, which is typically not parallel.

For simplicity of implementation, initially the implementation of Handle supports only the equivalent of non-blocking file handles. This has two implications:

Running the Axon Scheduler in the Background

Clearly for this to work, Kamaelia's core - Axon - needs a way of running components in the background. ie a way of putting the scheduler in the background. This is actually pretty trivial, and can be used in a very basic way (say, to run an Echo Protocol server in the background) like this:

from Axon.background import background

# Start the scheduler in a background thread
#
background().start()

# Add something to run in the background.
from Kamaelia.Protocol.EchoProtocol import EchoProtcol
from Kamaelia.Chassis.ConnectedServer import SimpleServer

# Due to background().start() above, the following starts running in
# the background immediately. 
SimpleServer(protocol=EchoProtcol, port=1500).activate()

# Do something else in the foreground.
import time
while 1:
     time.sleep(1)
     print "Ptang!"

As should be clear from this, the scheduler is started in the background, the server is activated and then system sits waiting for people to connect whilst periodically going "Ptang!"

Example Component

We'll use the following component in our examples below:

class Reverser(Axon.Component.component):
    Inboxes = {
         "inbox": "We receive data to reverse here",
         "control": "We shutdown when a message is sent here"
    }
    Outboxes = {
         "outbox": "We pass on the reversed message here",
         "signal": "We pass on any shutdown message that we receive here."
    }

    def main(self):
        while not self.dataReady("control):
            while self.dataReady('inbox'):
                item = self.recv('inbox')
                self.send(item[::-1], 'outbox') # reverse the string

            if not self.anyReady():
                self.pause()

            yield 1

        self.send( self.recv("control), "signal" ) # pass on the shutdown

Now, clearly this is a simple component, but it be anything. If could for example be a component that forwards each line to translate.google.com or babelfish.av.com, to translate the provided words into a different language. The usage would still be the same - messages coming in inbox "inbox", and transformed data coming out outbox "outbox".

Usage

Given the example component above, we decide to manually feed it the contents of stdin.

First of all we need our imports:

from Axon.Handle import Handle

from Axon.background import background

import Queue

Queue is needed because currently the exceptions thrown by Handle (when the Handle isn't ready) are from that module.

Start the scheduler in the background:

background().start()

Start our Reverser component in the background, inside a Handle:

reverser = Handle(Reverser()).activate()

The it's just a matter of using it:

while True:
    line = sys.stdin.readline()
    if line == "":
       break
    line = line.rstrip() # get rid of newline - looks odd otherwise :)

    reverser.put(line, "inbox")

    while 1:
        try:
            enil = reverser.get("outbox")
            break
        except Queue.Empty:
            time.sleep(0.1)

    print enil

What should be clear here is that we feed data into the reverser using the .put() method.

The reason why we don't use a file-like object[1] here also becomes clear : 1) unlike read()/write() we have multiple possible destinations for the data - "inbox", "control", and we also have multiple sources of data "outbox", "signal". 2) Also, rather than shipping over binary data, we're passing over arbitrary python objects. Whilst in this case this happens to be strings it could be anything from simple records in dictionaries through to video data.

[1] ie using the file API of write/read

Other Examples

Other examples are in the Axon distribution and also in the Kamaelia Distribution. These can be browsed online here:

Limitations

Limitations: