Skip to content

jeckhart/python-actors

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 

Repository files navigation

python-actors

This is some work on Donovan Preston's python-actors [1] so that it works with gevent.

Since the dawn of concurrency research, there have been two camps: shared everything, and shared nothing. Most modern applications use threads for concurrency, a shared everything architecture.

Actors, however, use a shared nothing architecture where lightweight processes communicate with each other using message passing. Actors can change their state, create a new Actor, send a message to any Actor it has the Address of, and wait for a specific kind of message to arrive in it's mailbox.

[1] https://bitbucket.org/fzzzy/python-actors

Requirements:

  • gevent 0.13
  • simplejson
  • a large dose of patience

What Is an Actor?

  • An actor is a process
  • An actor can change it's own state
  • An actor can create another actor and get it's address
  • An actor can send a message to any addresses it knows
  • An actor can wait for a specific message to arrive in it's mailbox

Why Use Actors?

  • Only an actor can change it's own state
  • Each actor is a process, simplifying control flow
  • Message passing is easy to distribute
  • Most exceptional conditions occur when waiting for a message
  • Isolates error handling code
  • Makes it easier to build fault tolerant distributed systems

How Are Actors Implemented in python-actors?

gevent and greenlet threads are used to implement the actor processes. This doesn't provide real isolation: but python doesn't provide private either.

When messages are sent between actors they are serialized to json and copied. This provides isolation and makes the messages network safe.

Problem: Imported modules leak state between actors

  • Possibility: Keep a unique copy of sys.modules for every actor
  • Possibility: Seal modules in wrapper object preventing modification
  • Reality: Just write code that doesn't abuse global module state

How To Use python-actors

Most stuff lives in the pyact package and in the actor module:

  • pyact.actor.Mesh represents the mesh of connected nodes.
  • pyact.actor.Node is a local node.
  • pyact.actor.Actor is an actor.

To get going you need to create a mesh and a local node:

mesh = actor.Mesh()
node = actor.Node(mesh, 'cookie@localhost.local:3433')

The second argument to Node is the name of the node. The format is COOKIE@HOST:PORT. The cookie is used to create some kind of security: two nodes must have the same cookie to be able to communicate. The HOST and PORT arguments give the network location of the node.

When the node is set up the first actor can be created:

address = node.spawn(fn)

fn is a function that receives a receive funcion as the first argument.

There are two ways to create an actor: either by subclassing Actor or by just passing a function to spawn. Arguments passed to spawn are forwarded to the actor:

def forward(receive, address):
    pat, data = receive()
    address | data

def build(receive, n):
    ring = []
    for i in range(n):
        if not ring:
            node = actor.spawn(forward, actor.curaddr())
        else:
            node = actor.spawn(forward, ring[-1])
        ring.append(node)
        gevent.sleep()

    ring[-1] | {'text': 'hello around the ring'}
    pat, data = receive()
    return data

mesh = actor.Mesh()
node = actor.Node(mesh, 'cookie@localhost.local:3433')
addr = node.spawn(build, 10000)
print node.wait(addr)

This passes 10000 as n to the actor function build. This creates 10,000 sub-actors, where actor N will forward any received message to actor N+1 and then die. When all actors has been created a message is sent through the ring.

Worth noting is the curaddr() function that returns the address of the current actor. Another neat function is the node.wait function that waits for a local actor to finish and returns the result.

Receiving Messages

python-actors has just like Erlang selective receive. This means that if messages in the mailbox will be left there if the call to receive do not provide a matching pattern.

Patterns are python objects that can contain "wildcard types". A simple example is the following dictionary pattern: {"name": int}. This will match {"name": 1} but not {"name": "data"}. The type object will match anything.

DATA = ('data', str)
EVENT = {'event': str, 'data': object}

pat, msg = receive(DATA, EVENT)
if pat is DATA:
   print "we got some data", msg[1]
if pat is EVENT:
   print "wow, an event", msg['event'], msg['data']

Note that tuples must match is length. This is not true for lists, which is used to match arrays. The first element in an array match is a type: [str] will match ['a', 'b'] but not [1, 'b'].

Roadmap

  • Get remote nodes working so we can build actual networks
  • Proper linking and monitoring
  • Create basic constructs such as supervisors and routers

About

python-actors

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published