Documentation for pulsar 0.5.1. For development docs, go here.

Design

Pulsar implements a double layer of components for building a vast array of parallel and asynchronous applications.

  • The first layer is based on the building blocks of pulsar library: the Actor class for parallel execution and the the Deferred class for handling asynchronous events via an actor’s EventLoop.
  • The second layer, built on top of the first one, is based on the apps.Application class.

Server State

Pulsar can be used as a stand-alone asynchronous library, without using actors to provide parallel execution. However, when using pulsar application framework, you need to use pulsar in server state, that is to say, there will be a centralised Arbiter controlling the main EventLoop in the main thread of the master process. The arbiter is a specialised Actor which control the life all Actor and Monitor.

To access the Arbiter, from the main process, one can use the arbiter() high level function:

>>> arbiter = pulsar.arbiter()
>>> arbiter.running()
False

Actors

Actors are the atoms of pulsar’s concurrent computation, they do not share state between them, communication is achieved via asynchronous inter-process message passing, implemented using the standard python socket library. An Actor can be thread-based or process-based (default) and control at least one running EventLoop. To obtain the actor in the current thread:

actor = pulsar.get_actor()

Spawning a new actor can be achieved by the spawn() function, for example the following will spawn a thread-based actor:

ap = spawn(concurrency='thread')

Event loop

Each actor has its own Actor.event_loop, an instance of EventLoop, which can be used to register handlers on file descriptors. The Actor.event_loop is initiated just after forking (or after the actor’s thread starts for thread-based actors). Pulsar EventLoop will be following pep-3156 guidelines.

IO-bound

The most common usage for an Actor is to handle Input/Output events on file descriptors. An Actor.event_loop tells the operating system (through epoll or select) that it should be notified when a new connection is made, and then it goes to sleep. Serving the new request should occur as fast as possible so that other connections can be served simultaneously.

CPU-bound

Another way for an actor to function is to use its of Actor.thread_pool to perform CPU intensive operations, such as calculations, data manipulation or whatever you need them to do. CPU-bound Actors have the following properties:

Mailbox

Each Actor, with the only exception of Monitor, have its own Actor.mailbox, an asynchronous client of the Arbiter mailbox server. Check the message passing documentation for more information.

Spawning

Spawning a new actor is achieved via the spawn() function:

from pulsar import spawn

def periodic_task():
    # do something useful here
    ...

ap = spawn(on_start=lambda: get_event_loop().call_repeatedly(2, periodic_task))

The valued returned by spawn() is an ActorProxyDeferred instance, a specialised Deferred, which has the spawned actor id aid and it is called back once the remote actor has started. The callback will be an ActorProxy, a lightweight proxy for the remote actor.

When spawning from an actor other than the arbiter, the workflow of the spawn() function is as follow:

  • send() a message to the arbiter to spawn a new actor.
  • The arbiter spawn the actor and wait for the actor’s hand shake. Once the hand shake is done, it sends the response (the ActorProxy of the spawned actor) to the original actor.

The actor hand shake is the mechanism with which a Actor register its mailbox address with the Arbiter so that the arbiter can monitor its behavior. If the hand-shake fails, the spawned actor will eventually stop.

Hooks

An Actor exposes three one time events which can be used to customise its behaviour. These functions do nothing in the standard Actor implementation.

start

Fired just before the actor starts its event loop. This function can be used to setup the application and register event handlers. For example, the socket server application creates the server and register its file descriptor with the Actor.event_loop.

stopping

Fired when the Actor starts stopping.

stop

Fired just before the Actor is garbage collected

Commands

An Actor communicate with a remote Actor by sending an action to perform. This action takes the form of a command name and optional positional and key-valued parameters. It is possible to add new commands via the pulsar.command decorator as explained in the api documentation.

ping

Ping the remote actor abcd and receive an asynchronous pong:

send('abcd', 'ping')

echo

received an asynchronous echo from a remote actor abcd:

send('abcd', 'echo', 'Hello!')

run

Run a function on a remote actor. The function must accept actor as its initial parameter:

def dosomething(actor, *args, **kwargs):
    ...

send('monitor', 'run', dosomething, *args, **kwargs)

stop

Tell the remote actor abc to gracefully shutdown:

send('abc', 'stop')

Asynchronous Components

Exceptions

There are two categories of exceptions in Python: those that derive from the Exception class and those that derive from BaseException. Exceptions deriving from Exception will generally be caught and handled appropriately; for example, they will be passed through by Deferred, and they will be logged and ignored when they occur in a callback.

However, exceptions deriving only from BaseException are never caught, and will usually cause the program to terminate with a traceback. (Examples of this category include KeyboardInterrupt and SystemExit; it is usually unwise to treat these the same as most other exceptions.)

Application Framework

To aid the development of applications running on top of pulsar concurrent framework, the library ships with the Application class.

Table Of Contents

Previous topic

Overview

Next topic

FAQ

This Page