score.serve¶
This module is responsible for starting long-running processes like a web-server, celery-workers or other services.
Quickstart¶
Let’s write a small service that sends spam to stdout periodically. We will
first need a subclass of Worker
that will perform the sending. Let’s
use the very simple SimpleWorker
subclass:
import time
from score.serve import SimpleWorker
class Spammer(SimpleWorker):
def loop(self):
while self.running:
print('spam!')
time.sleep(1)
We now need our module to expose a function called score_serve_workers that returns an instance of this class:
from score.init import ConfiguredModule
class ConfiguredSpamModule(ConfiguredModule):
def __init__(self):
import demo.spam
super().__init__(demo.spam)
def score_serve_workers(self):
return [Spammer()]
The only thing left to do is to configure the serve module to start this worker. Add the following to your configuration:
[score.serve]
modules = demo.spam
You can now start your glorious spam server with score serve.
Configuration¶
-
score.serve.
init
(confdict)[source]¶ Initializes this module acoording to our module initialization guidelines with the following configuration keys:
- autoreload False
- When set to
true
, the server will automatically reload whenever it detects a change in one of the python files, that are in use. - modules
- The
list
of modules to serve. This need to be a list of module aliases, i.e. the same name, with which you configured the module with (“score.http” becomes “http” if not specified otherwise.)
Details¶
Although we have tried to render the usage of this module as simple as possible, it has very components inside. The reason for this complexity is the many constraints, that were imposed upon the architecture:
- It must be possible to pause a worker. The worker is expected to cease handling new requests, but should also be able to continue operations immediately when instructed to do so.
- It should be possible to track the state of every worker at all times, even when a worker is transitioning from one state to another.
- Reloading the application when a python file changes should be as fast as possible, since this will be done very often during application development.
- The main loop should not rely on the existence of a console. Future versions of this module will provide a maintenance port, where workers can be probed and controlled by externals applications.
Worker States¶
The above constraints have lead us to the following states for workers:
- STOPPED: This is the initial as well as the final state of workers. Workers are not expected to consume any significant resources in this state.
- PAUSED: The worker has all required resources to start perform its duty at any point. An HTTP server will have an open socket at port 80, for example, but will not accept any connections.
- RUNNING: This is the state where the worker actually does whatever it is meant to do.
If the worker states are not tweaked manually (via @transitions
annotations), the state machine looks like the
following:
. STOPPED
/ ^
PAUSING |
| STOPPING
V /
PAUSED
/ ^
STARTING |
| PAUSING
V /
RUNNING
So every transition passes through an intermediate state which represents the
state the worker ist transitioning to. This implies that the PAUSING
state
occurs twice in the diagram, as you might have noticed.
It is possible to add furhter transition capabilities to your worker using
@transitions
. Have a look at the function
documentation for details.
Worker API¶
To make these state transitions as transparent as possible, the Worker API
follows a simple design principle: Every state transition is represented by a
function. The transition STOPPED -> PAUSED
is handled by the function
prepare
, for example. As soon as the
method is entered, the worker is assumed to be in the PAUSING
state. When
the function terminates, the worker is assumed to be in the PAUSED
state.
The layers above the Worker
will make sure, that these functions are
always called in the correct order. Workers have the guarantee, that they are
in the STOPPED
state, when their prepare
method is called, for example.
Due to the working of Service API (the next higher layer), every transition
function is called inside a different thread. This is the only inconvenience
imposed upon this layer. The provided SimpleWorker
class implements an abstraction around this
limitation, so if you don’t want to dirty your code with threading, you can
just go ahead and use that class instead of the more powerful and more complex
Worker
base class.
Service API¶
Every worker is wrapped in a Service
object that
handles the state transitions of a worker. Service objects expose the same API
as workers, but they have a slightly different meaning: When you call
start
on a service object, it will make
sure that the worker transitions to the RUNNING
state eventually, no matter
what state it currently is in.
API¶
Configuration¶
-
score.serve.
init
(confdict)[source] Initializes this module acoording to our module initialization guidelines with the following configuration keys:
- autoreload False
- When set to
true
, the server will automatically reload whenever it detects a change in one of the python files, that are in use. - modules
- The
list
of modules to serve. This need to be a list of module aliases, i.e. the same name, with which you configured the module with (“score.http” becomes “http” if not specified otherwise.)
Workers¶
-
class
score.serve.
Worker
[source]¶ The implementation of a single service.
The worker will be wrapped in
Service
objects before being started.-
cleanup
(exception)[source]¶ Called when an exception occured. Due to the nature of threading, it is not entirely clear, in which state the worker was, when this specific exception occurred.
-
register_state_change_listener
(callback)[source]¶ Registers a callable that will be invoked whenever the state of this worker changes. The callback will receive three arguments:
- a
Service
wrapping this worker, - the old
state
and - the new (current) state.
Note, that due to the nature of threading, it is possible that the Service is already in another state than the one provided as the third argument.
- a
-
-
score.serve.
transitions
(state1, state2=None)[source]¶ This annotation can add additional transitions to a class.
If your worker is capable of going from RUNNING to STOPPED, for example, you can add the additional transition to a new class method:
class MyWorker(Worker): @transitions(Service.State.RUNNING, Service.State.STOPPED) def kill(): # ...
-
class
score.serve.
SimpleWorker
[source]¶ A simplified worker base class, that hides all the ugly threading logic.
You can subclass this and implement a
loop
function that periodically checks this.running:class Spammer(SimpleWorker): def loop(): while self.running: print('spam!') time.sleep(1)
-
class
score.serve.
SocketServerWorker
[source]¶ A specialized worker for handling
socketserver
objects.You only need to implement the function
_mkserver
in subclasses. That function must return asocketserver.BaseServer
instance. The Worker will then perform the equivalent of calling itsserve_forever
method.
-
class
score.serve.
AsyncioWorker
[source]¶ A specialized worker for
asyncio
servers.This base class will add a layer of abstraction to eliminate threading. Subclasses can override the functions
_prepare()
,_start()
,_pause()
,_stop()
and_cleanup()
. These functions will be called inside a running event loop (which can be accessed asself.loop
) and can be regular functions or coroutines.Example implementation:
class EchoServer(AsyncioWorker): async def _start(self): self.server = yield from self.loop.create_server(myserver) def _pause(self): self.server.close()
Service¶
-
class
score.serve.
Service
(name, worker)[source]¶ A wrapper around workers, that you can use to control your workers without worrying about threading.
-
State
¶ alias of
ServiceState
-
register_state_change_listener
(callback)[source]¶ Registers a callable that will be invoked whenever the state of the worker changes. The callback will receive three arguments:
- this service,
- the old state and
- the new (current) state.
Note, that due to the nature of threading, it is possible that the Service is already in another state than the one provided as the third argument.
-