Categories
Blather Programming Python Software

Twisted Daemonologie

Part 16: Twisted Daemonologie

This continues the introduction started here. You can find an index to the entire series here.

Introduction

The servers we have written so far have just run in a terminal window, with output going to the screen via print statements. This works alright for development, but it’s hardly a way to deploy services in production. A well-behaved production server ought to:

  1. Run as a daemon process, unconnected with any terminal or user session. You don’t want a service to shut down just because the administrator logs out.
  2. Send debugging and error output to a set of rotated log files, or to the syslog service.
  3. Drop excessive privileges, e.g., switching to a lower-privileged user before running.
  4. Record its pid in a file so that the administrator can easily send signals to the daemon.

We can get all of those features using the twistd script provided by Twisted. But first we’ll have to change our code a bit.

The Concepts

Understanding twistd will require learning a few new concepts in Twisted, the most important being a Service. As usual, several of the new concepts are accompanied by new Interfaces.

IService

The IService interface defines a named service that can be started and stopped. What does the service do? Whatever you like — rather than define the specific function of the service, the interface requires only that it provide a small set of generic attributes and methods.

There are two required attributes: name and running. The name attribute is just a string, like 'fastpoetry', or None if you don’t want to give your service a name. The running attribute is a Boolean value and is true if the service has been successfully started.

We’re only going to touch on some of the methods of IService. We’ll skip some that are obvious, and others that are more advanced and often go unused in simpler Twisted programs. The two principle methods of IService are startService and stopService:

    def startService():
        """
        Start the service.
        """

    def stopService():
        """
        Stop the service.

        @rtype: L{Deferred}
        @return: a L{Deferred} which is triggered when the service has
            finished shutting down. If shutting down is immediate, a
            value can be returned (usually, C{None}).
        """

Again, what these methods actually do will depend on the service in question. For example, the startService method might:

  • Load some configuration data, or
  • Initialize a database, or
  • Start listening on a port, or
  • Do nothing at all.

And the stopService method might:

  • Persist some state, or
  • Close open database connections, or
  • Stop listening on a port, or
  • Do nothing at all.

When we write our own custom services we’ll need to implement these methods appropriately. For some common behaviors, like listening on a port, Twisted provides ready-made services we can use instead.

Notice that stopService may optionally return a deferred, which is required to fire when the service has completely shut down. This allows our services to finish cleaning up after themselves before the entire application terminates. If your service shuts down immediately you can just return None instead of a deferred.

Services can be organized into collections that get started and stopped together. The last IService method we’re going to look at, setServiceParent, adds a Service to a collection:

    def setServiceParent(parent):
        """
        Set the parent of the service.

        @type parent: L{IServiceCollection}
        @raise RuntimeError: Raised if the service already has a parent
            or if the service has a name and the parent already has a child
            by that name.
        """

Any service can have a parent, which means services can be organized in a hierarchy. And that brings us to the next Interface we’re going to look at today.

IServiceCollection

The IServiceCollection interface defines an object which can contain IService objects. A service collection is a just plain container class with methods to:

Note that an implementation of IServiceCollection isn’t automatically an implementation of IService, but there’s no reason why one class can’t implement both interfaces (and we’ll see an example of that shortly).

Application

A Twisted Application is not defined by a separate interface. Rather, an Application object is required to implement both IService and IServiceCollection, as well as a few other interfaces we aren’t going to cover.

An Application is the top-level service that represents your entire Twisted application. All the other services in your daemon will be children (or grandchildren, etc.) of the Application object.

It is rare to actually implement your own Application. Twisted provides an implementation that we’ll use today.

Twisted Logging

Twisted includes its own logging infrastructure in the module twisted.python.log. The basic API for writing to the log is simple, so we’ll just include a short example located in basic-twisted/log.py, and you can skim the Twisted module for details if you are interested.

We won’t bother showing the API for installing logging handlers, since twistd will do that for us.

FastPoetry 2.0

Alright, let’s look at some code. We’ve updated the fast poetry server to run with twistd. The source is located in twisted-server-3/fastpoetry.py. First we have the poetry protocol:

class PoetryProtocol(Protocol):

    def connectionMade(self):
        poem = self.factory.service.poem
        log.msg('sending %d bytes of poetry to %s'
                % (len(poem), self.transport.getPeer()))
        self.transport.write(poem)
        self.transport.loseConnection()

Notice instead of using a print statement, we’re using the twisted.python.log.msg function to record each new connection.
Here’s the factory class:

class PoetryFactory(ServerFactory):

    protocol = PoetryProtocol

    def __init__(self, service):
        self.service = service

As you can see, the poem is no longer stored on the factory, but on a service object referenced by the factory. Notice how the protocol gets the poem from the service via the factory. Finally, here’s the service class itself:

class PoetryService(service.Service):

    def __init__(self, poetry_file):
        self.poetry_file = poetry_file

    def startService(self):
        service.Service.startService(self)
        self.poem = open(self.poetry_file).read()
        log.msg('loaded a poem from: %s' % (self.poetry_file,))

As with many other Interface classes, Twisted provides a base class we can use to make our own implementations, with helpful default behaviors. Here we use the twisted.application.service.Service class to implement our PoetryService.

The base class provides default implementations of all required methods, so we only need to implement the ones with custom behavior. In this case, we just override startService to load the poetry file. Note we still call the base class method (which sets the running attribute for us).

Another point is worth mentioning. The PoetryService object doesn’t know anything about the details of the PoetryProtocol. The service’s only job is to load the poem and provide access to it for any object that might need it. In other words, the PoetryService is entirely concerned with the higher-level details of providing poetry, rather than the lower-level details of sending a poem down a TCP connection. So this same service could be used by another protocol, say UDP or XML-RPC. While the benefit is rather small for our simple service, you can imagine the advantage for a more realistic service implementation.

If this were a typical Twisted program, all the code we’ve looked at so far wouldn’t actually be in this file. Rather, it would be in some other module(s) (perhaps fastpoetry.protocol and fastpoetry.service). But following our usual practice of making these examples self-contained, we’ve including everything we need in a single script.

Twisted tac files

The rest of the script contains what would normally be the entire content — a Twisted tac file. A tac file is a Twisted Application Configuration file that tells twistd how to construct an application. As a configuration file it is responsible for choosing settings (like port numbers, poetry file locations, etc.) to run the application in some particular way. In other words, a tac file represents a specific deployment of our service (serve that poem on this port) rather than a general script for starting any poetry server.

If we were running multiple poetry servers on the same host, we would have a tac file for each one (so you can see why tac files normally don’t contain any general-purpose code). In our example, the tac file is configured to serve poetry/ecstasy.txt run on port 10000 of the loopback interface:

# configuration parameters
port = 10000
iface = 'localhost'
poetry_file = 'poetry/ecstasy.txt'

Note that twistd doesn’t know anything about these particular variables, we just define them here to keep all our configuration values in one place. In fact, twistd only really cares about one variable in the entire file, as we’ll see shortly. Next we begin building up our application:

# this will hold the services that combine to form the poetry server
top_service = service.MultiService()

Our poetry server is going to consist of two services, the PoetryService we defined above, and a Twisted built-in service that creates the listening socket our poem will be served from. Since these two services are clearly related to each other, we’ll group them together using a MultiService, a Twisted class which implements both IService and IServiceCollection.

As a service collection, the MultiService will group our two poetry services together. And as a service, the MultiService will start both child services when the MultiService itself is started, and stop both child services when it is stopped. Let’s add the first poetry service to the collection:

# the poetry service holds the poem. it will load the poem when it is
# started
poetry_service = PoetryService(poetry_file)
poetry_service.setServiceParent(top_service)

This is pretty simple stuff. We just create the PoetryService and then add it to the collection with setServiceParent, a method we inherited from the Twisted base class. Next we add the TCP listener:

# the tcp service connects the factory to a listening socket. it will
# create the listening socket when it is started
factory = PoetryFactory(poetry_service)
tcp_service = internet.TCPServer(port, factory, interface=iface)
tcp_service.setServiceParent(top_service)

Twisted provides the TCPServer service for creating a TCP listening socket connected to an arbitrary factory (in this case our PoetryFactory). We don’t call reactor.listenTCP directly because the job of a tac file is to get our application ready to start, without actually starting it. The TCPServer will create the socket after it is started by twistd.

You might have noticed we didn’t bother to give any of our services names. Naming services is not required, but only an optional feature you can use if you want to ‘look up’ services at runtime. Since we don’t need to do that in our little application, we don’t bother with it here.

Ok, now we’ve got both our services combined into a collection. Now we just make our Application and add our collection to it:

# this variable has to be named 'application'
application = service.Application("fastpoetry")

# this hooks the collection we made to the application
top_service.setServiceParent(application)

The only variable in this script that twistd really cares about is the application variable. That is how twistd will find the application it’s supposed to start (and so the variable has to be named ‘application’). And when the application is started, all the services we added to it will be started as well.

Figure 34 shows the structure of the application we just built:

Figure 34: the structure of our fastpoetry application
Figure 34: the structure of our fastpoetry application

Running the Server

Let’s take our new server for a spin. As a tac file, we need to start it with twistd. Of course, it’s also just a regular Python file, too. So let’s run it with Python first and see what happens:

python twisted-server-3/fastpoetry.py

If you do this, you’ll find that what happens is nothing! As we said before, the job of a tac file is to get an application ready to run, without actually running it. As a reminder of this special purpose of tac files, some people name them with a .tac extension instead of .py. But the twistd script doesn’t actually care about the extension.

Let’s run our server for real, using twistd:

twistd --nodaemon --python twisted-server-3/fastpoetry.py

After running that command, you should see some output like this:

2010-06-23 20:57:14-0700 [-] Log opened.
2010-06-23 20:57:14-0700 [-] twistd 10.0.0 (/usr/bin/python 2.6.5) starting up.
2010-06-23 20:57:14-0700 [-] reactor class: twisted.internet.selectreactor.SelectReactor.
2010-06-23 20:57:14-0700 [-] __builtin__.PoetryFactory starting on 10000
2010-06-23 20:57:14-0700 [-] Starting factory <__builtin__.PoetryFactory instance at 0x14ae8c0>
2010-06-23 20:57:14-0700 [-] loaded a poem from: poetry/ecstasy.txt

Here’s a few things to notice:

  1. You can see the output of the Twisted logging system, including the PoetryFactory‘s call to log.msg. But we didn’t install a logger in our tac file, so twistd must have installed one for us.
  2. You can also see our two main services, the PoetryService and the TCPServer starting up.
  3. The shell prompt never came back. That means our server isn’t running as a daemon. By default, twistd does run a server as a daemon process (that’s the main reason twistd exists), but if you include the --nodaemon option then twistd will run your server as a regular shell process instead, and will direct the log output to standard output as well. This is useful for debugging your tac files.

Now test out the server by fetching a poem, either with one of our poetry clients or just netcat:

netcat localhost 10000

That should fetch the poem from the server and you should see a new log line like this:

2010-06-27 22:17:39-0700 [__builtin__.PoetryFactory] sending 3003 bytes of poetry to IPv4Address(TCP, '127.0.0.1', 58208)

That’s from the call to log.msg in PoetryProtocol.connectionMade. As you make more requests to the server, you will see additional log entries for each request.

Now stop the server by pressing Ctrl-C. You should see some output like this:

^C2010-06-29 21:32:59-0700 [-] Received SIGINT, shutting down.
2010-06-29 21:32:59-0700 [-] (Port 10000 Closed)
2010-06-29 21:32:59-0700 [-] Stopping factory <__builtin__.PoetryFactory instance at 0x28d38c0>
2010-06-29 21:32:59-0700 [-] Main loop terminated.
2010-06-29 21:32:59-0700 [-] Server Shut Down.

As you can see, Twisted does not simply crash, but shuts itself down cleanly and tells you about it with log messages. Notice our two main services shutting themselves down as well.

Ok, now start the server up once more:

twistd --nodaemon --python twisted-server-3/fastpoetry.py

Then open another shell and change to the twisted-intro directory. A directory listing should show a file called twistd.pid. This file is created by twistd and contains the process ID of our running server. Try executing this alternative command to shut down the server:

kill `cat twistd.pid`

Notice that twistd cleans up the process ID file when our server shuts down.

A Real Daemon

Now let’s start our server as an actual daemon process, which is even simpler to do as it’s twistd‘s default behavior:

twistd --python twisted-server-3/fastpoetry.py

This time we get our shell prompt back almost immediately. And if you list the contents of your directory you will see, in addition to the twistd.pid file for the server we just ran, a twistd.log file with the log entries that were formerly displayed at the shell prompt.

When starting a daemon process, twistd installs a log handler that writes entries to a file instead of standard output. The default log file is twistd.log, located in the same directory where you ran twistd, but you can change that with the --logfile option if you wish. The handler that twistd installs also rotates the log whenever the size exceeds one megabyte.

You should be able to see the server running by listing all the processes on your system. Go ahead and test out the server by fetching another poem. You should see new entries appear in the log file for each poem you request.

Since the server is no longer connected to the shell (or any other process except init), you cannot shut it down with Ctrl-C. As a true daemon process, it will continue to run even if you log out. But we can still use the twistd.pid file to stop the process:

kill `cat twistd.pid`

And when that happens the shutdown messages appear in the log, the twistd.pid file is removed, and our server stops running. Neato.

It’s a good idea to check out some of the other twistd startup options. For example, you can tell twistd to switch to a different user or group account before starting the daemon (typically a way to drop privileges your server doesn’t need as a security precaution). We won’t bother going into those extra options, you can find them using the --help switch to twistd.

The Twisted Plugin System

Ok, now we can use twistd to start up our servers as genuine daemon processes. This is all very nice, and the fact that our “configuration” files are really just Python source files gives us a great deal of flexibility in how we set things up. But we don’t always need that much flexibility. For our poetry servers, we typically only have a few options we might care about:

  1. The poem to serve.
  2. The port to serve it from.
  3. The interface to listen on.

Making new tac files for simple variations on those values seems rather excessive. It would be nice if we could just specify those values as options on the twistd command line. The Twisted plugin system allows us to do just that.

Twisted plugins provide a way of defining named Applications, with a custom set of command-line options, that twistd can dynamically discover and run. Twisted itself comes with a set of built-in plugins. You can see them all by running twistd without any arguments. Try running it now, but outside of the twisted-intro directory. After the help section, you should see some output like this:

    ...
    ftp                An FTP server.
    telnet             A simple, telnet-based remote debugging service.
    socks              A SOCKSv4 proxy service.
    ...

Each line shows one of the built-in plugins that come with Twisted. And you can run any of them using twistd.
Each plugin also comes with its own set of options, which you can discover using --help. Let’s see what the options for the ftp plugin are:

twistd ftp --help

Note that you need to put the --help switch after the ftp command, since you want the options for the ftp plugin rather than for twistd itself.
We can run the ftp server with twistd just like we ran our poetry server. But since it’s a plugin, we just run it by name:

twistd --nodaemon ftp --port 10001

That command runs the ftp plugin in non-daemon mode on port 10001. Note the twistd option nodaemon comes before the plugin name, while the plugin-specific option port comes after the plugin name. As with our poetry server, you can stop that plugin with Ctrl-C.

Ok, let’s turn our poetry server into a Twisted plugin. First we need to introduce a couple of new concepts.

IPlugin

Any Twisted plugin must implement the twisted.plugin.IPlugin interface. If you look at the declaration of that Interface, you’ll find it doesn’t actually specify any methods. Implementing IPlugin is simply a way for a plugin to say “Hello, I’m a plugin!” so twistd can find it. Of course, to be of any use, it will have to implement some other interface and we’ll get to that shortly.

But how do you know if an object actually implements an empty interface? The zope.interface package includes a function called implements that you can use to declare that a particular class implements a particular interface. We’ll see an example of that in the plugin version of our poetry server.

IServiceMaker

In addition to IPlugin, our plugin will implement the IServiceMaker interface. An object which implements IServiceMaker knows how to create an IService that will form the heart of a running application. IServiceMaker specifies three attributes and a method:

  1. tapname: a string name for our plugin. The “tap” stands for Twisted Application Plugin. Note: an older version of Twisted also made use of pickled application files called “tapfiles”, but that functionality is deprecated.
  2. description: a description of the plugin, which twistd will display as part of its help text.
  3. options: an object which describes the command-line options this plugin accepts.
  4. makeService: a method which creates a new IService object, given a specific set of command-line options

We’ll see how all this gets put together in the next version of our poetry server.

Fast Poetry 3.0

Now we’re ready to take a look at the plugin version of Fast Poetry, located in twisted/plugins/fastpoetry_plugin.py.

You might notice we’ve named these directories differently than any of the other examples. That’s because twistd requires plugin files to be located in a twisted/plugins directory, located in your Python module search path. The directory doesn’t have to be a package (i.e., you don’t need any __init__.py files) and you can have multiple twisted/plugins directories on your path and twistd will find them all. The actual filename you use for the plugin doesn’t matter either, but it’s still a good idea to name it according to the application it represents, like we have done here.

The first part of our plugin contains the same poetry protocol, factory, and service implementations as our tac file. And as before, this code would normally be in a separate module but we’ve placed it in the plugin to make the example self-contained.

Next comes the declaration of the plugin’s command-line options:

class Options(usage.Options):

    optParameters = [
        ['port', 'p', 10000, 'The port number to listen on.'],
        ['poem', None, None, 'The file containing the poem.'],
        ['iface', None, 'localhost', 'The interface to listen on.'],
        ]

This code specifies the plugin-specific options that a user can place after the plugin name on the twistd command line. We won’t go into details here as it should be fairly clear what is going on. Now we get to the main part of our plugin, the service maker class:

class PoetryServiceMaker(object):

    implements(service.IServiceMaker, IPlugin)

    tapname = "fastpoetry"
    description = "A fast poetry service."
    options = Options

    def makeService(self, options):
        top_service = service.MultiService()

        poetry_service = PoetryService(options['poem'])
        poetry_service.setServiceParent(top_service)

        factory = PoetryFactory(poetry_service)
        tcp_service = internet.TCPServer(int(options['port']), factory,
                                         interface=options['iface'])
        tcp_service.setServiceParent(top_service)

        return top_service

Here you can see how the zope.interface.implements function is used to declare that our class implements both IServiceMaker and IPlugin.

You should recognize the code in makeService from our earlier tac file implementation. But this time we don’t need to make an Application object ourselves, we just create and return the top level service that our application will run and twistd will take care of the rest. Notice how we use the options argument to retrieve the plugin-specific command-line options given to twistd.

After declaring that class, there’s only on thing left to do:

service_maker = PoetryServiceMaker()

The twistd script will discover that instance of our plugin and use it to construct the top level service. Unlike the tac file, the variable name we choose is irrelevant. What matters is that our object implements both IPlugin and IServiceMaker.

Now that we’ve created our plugin, let’s run it. Make sure that you are in the twisted-intro directory, or that the twisted-intro directory is in your python module search path. Then try running twistd by itself. You should now see that “fastpoetry” is one of the plugins listed, along with the description text from our plugin file.

You will also notice that a new file called dropin.cache has appeared in the twisted/plugins directory. This file is created by twistd to speed up subsequent scans for plugins.

Now let’s get some help on using our plugin:

twistd fastpoetry --help

You should see the options that are specific to the fastpoetry plugin in the help text. Finally, let’s run our plugin:

twistd fastpoetry --port 10000 --poem poetry/ecstasy.txt

That will start a fastpoetry server running as a daemon. As before, you should see both twistd.pid and twistd.log files in the current directory. After testing out the server, you can shut it down:

kill `cat twistd.pid`

And that’s how you make a Twisted plugin.

Summary

In this Part we learned about turning our Twisted servers into long-running daemons. We touched on the Twisted logging system and on how to use twistd to start a Twisted application as a daemon process, either from a tac configuration file or a Twisted plugin. In Part 17 we’ll return to the more fundamental topic of asynchronous programming and look at another way of structuring our callbacks in Twisted.

Suggested Exercises

  1. Modify the tac file to serve a second poem on another port. Keep the services for each poem separate by using another MultiService object.
  2. Create a new tac file that starts a poetry proxy server.
  3. Modify the plugin file to accept an optional second poetry file and second port to serve it on.
  4. Create a new plugin for the poetry proxy server.

24 replies on “Twisted Daemonologie”

Hello!

It looks like MultiService is optional, one could set for each service

service1.setServiceParent(application)
service2.setServiceParent(application)

or is there any differences?

P.S. Great part! special thanks for plugins 🙂

Thank you for writing this tutorial series.
This helped me a lot to understand asynchronous programming model and of course Twisted.
Will wait for next parts.

hello Dave!

I’ve tried your Plugin example, and twistd command doesn’t show fastpoetry in the list of available commands, if I’m root. Is there any workaround to get this work for root too?

thx

p.s. Custom logging doesn’t work either with plugins as discussed here http://markmail.org/message/sy5hq56kn3i3frtr

because plugin has no access to application object. I hope it will be added in feature

if I print sys.path in python interpreter, they both are identical for root and normal user and corrent path ” is first item in list

Many thanks for finding time to share this info. It really helped me while struggling with making multiple services running behind with same twistd command since the official Twisted plugins documentation is somehow sending towards using only one service.

Cheers,
DanB

Hey, the tutorials are really useful.
I don’t really understand though, if I have 2 services, and one service needs to call another, how would I do that?

I should specifiy that they are both plugins. if they are run seperatlely is it possible for them to communicate? or do they have to be contained under one plugin

Do you mean you run the both with twistd as separate processes?
If so, one could call the other through whatever network interface
you are exposing the service through. But if you wanted to avoid
the network call, then they will need to run in the same plugin.

thats what I currently do, is do an http request, but I don’t really like that.

How would I do it with them both in the same plugin?

Currently I just do:
service = twisted.application.service.ServiceMaker()
in my plugin file in dir/twisted/plugins

I think you would use a MultiService. Make that your top-level service
and add the other services to it. The plugin file can give a reference
from one service to the other.

Hey, Thanks for all nice tutorials, it helped me a lot to understand asynchronous programming and twisted as well.

Currently I am working on a UDP server-client protocol in twisted and I want a broadcasting server (which I tried with ‘setBroadcastAllowed’, but while writing in IUDP transport I have to give address of client, but actually I want a server which will just broadcast and whichever client comes in its range will get that data..) could you please shed some light on it.?
Can we add interrupt in UDP protocol..?

Thanks again

$ twistd fastpoetry –help
Usage: twistd [options]
Options:


words A modern words server
xmpp-router An XMPP Router server

/home/pspalit/.local/bin/twistd: Unknown command: fastpoetry

Try setting up your python path explicitly, e.g.
PYTHONPATH=”/path/to/twisted-intro” twistd fastpoetry –help

Leave a Reply to BenCancel reply

Discover more from krondo

Subscribe now to keep reading and get access to the full archive.

Continue reading