[Twisted-Python] What to do when a service fails to start, also, deferred and startService

Terry Jones terry at jon.es
Sat Feb 21 22:41:12 EST 2009

Hi again Glyph

>>>>> "glyph" == glyph  <glyph at divmod.com> writes:
glyph> On 28 Nov, 03:38 pm, terry at jon.es wrote:
>> Thanks for the detailed reply.

glyph> No problem, sorry it took so long to get back to this; I definitely
glyph> left the conversation halfway through.

And this followup is even slower, sorry.  Here's a summary of the
conversation I'd like to continue:

>> So in my case I want to indicate to twistd that the service that my
>> class creates a makeService method to create, but which I do not set in
>> motion, has failed to start and that twistd should exit, or do something
>> other than cheerfully tell me that there's been an Unhandled Error.

>> Does that make more sense? Sorry, I should have said I was using 
>> twistd.

glyph> Yes.  And I think that this is a good use-case for making IService a
glyph> bit deeper than it is.

glyph> My previous messages were basically saying "you can't do what you
glyph> want with IService".  i.e. you can't write your own code to do
glyph> something clever and somehow have dependencies and notifications to
glyph> users of "twistd" fall out of that.  But that shouldn't be taken as
glyph> an indication that twistd itself shouldn't be improved.

glyph> It's not that you've failed to understand something - you have
glyph> correctly identified that there is nothing to understand :).  You
glyph> need to go find that thing ("whatever it is" ;-)) in twistd that's
glyph> invoking the very first call to IService.startService (and
glyph> privilegedStartService as well) and propose a concrete way to make
glyph> it smarter.

OK, here's a concrete proposal. Let's forget about twistd for the time
being and just address the central things I'm trying to do:

  1. I want a service to be able to initialize itself using code that
  receives deferreds. Right now, startService is not expected to return
  anything. I can go ahead and call a deferred-returning function in
  startService but I wont know that it's done unless I set a flag somewhere
  in a callback, and the rest of the methods in my service have to check
  that flag to see if the initialization is complete. In case it's not
  clear, I'm imagining the case of a MultiService, one Service of which
  performs the start and stop for the overall service.

  2. Because the other Services in the MultiService depend on the
  successful execution of the initialization, I'd rather they didn't get
  started at all if the initialization fails.

  3. It would be nice to get my hands on a deferred that had errbacked as a
  result of an unsuccessful start or stop.

You say: "making IService a bit deeper than it is" and then "you can't do
what you want with IService".

And Esteve Fernandez has already pointed me to this:

> "duuuuuude. don't. inherit. multiservice."

But it turns out you can do exactly what I want with a very simple subclass
(or rewriting of) MultiService.  BTW, this code is not tested - I want to
float the idea before doing more. Here's the simple version:

    class GuardedMultiService(service.MultiService):

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

        def startService(self):
            d = defer.maybeDeferred(self.initService.startService)
            d.addCallback(lambda _: service.MultiService.startService)

        def stopService(self):
            d = service.MultiService.stopService()
            d.addCallback(lambda _: self.initService.stopService)
            return d

This, I believe, solves 1 and 2 above. It has the nice property that all
the regular services add to this class will not be started until after the
possibly deferred-returning initService.startService is done. So there's no
need for any checking later to see that you really are initialized.

And to solve 3, you can add (all defaulting to None) args to __init__ to
specify two errback functions and their arguments, and use addErrback to
attach these to the deferreds in startService and stopService.  If we also
make initService have a default of None, the result will be backward
compatible with the existing MultiService, and could just replace it. The
new version simply gives a way to deal with 1, 2 and 3, if you need it.

BTW, in your original reply you wrote

glyph> This might seem a bit inconsistent, since stopService uses the
glyph> return of a Deferred.  However, this is for a very specific reason,
glyph> not a generalized error-handling case: you may need to prevent the
glyph> *rest* of the system (specifically, the reactor) from completely
glyph> shutting down

My #2 above is like the flip side of this: if the initialization of a
service fails, you may need to prevent the *rest* of the system from coming
up, and (if you can't fix things in the errback) you might want to stop the
reactor. I say this specifically with regard to services launched by twistd.


More information about the Twisted-Python mailing list