[Twisted-Python] one-shot reactor?

Alec Flett alecf at metaweb.com
Fri Dec 5 13:47:36 MST 2008


Hey folks -
So I'm trying what seems to be a fairly unusual use of Twisted, but  
I'm hoping that someone out there has tried the same thing as me and  
can offer some pointers. Bear with me as I explain how we're set up.  
The issues I'm having are at the end..

We're running Pylons as our appserver, but almost all of our internal  
requests from the appserver are actually retrieved over HTTP - i.e.  
database requests, LOBs, etc... we also have one or two other  
proprietary connections that would benefit from asynchronous TCP  
access.. For years I've heard people raving about twisted, and I hate  
threads, so I thought I'd give it a shot.

So here's how this is working, at least in my early prototypes: Pylons  
is a WSGI environment, which means it needs a real callstack for each  
request.

Each request is more or less its own single-threaded environment, and  
the thread is only making new requests to internal services, not  
listening on any ports. So really Twisted is a client here, not a  
server. For each HTTP request, I'm running a "private" twisted reactor  
that simply runs until it runs out of reads/writes/delayedCalls.  My  
theory is this: in a single-threaded environment that is not listening  
for new connections.

So what I've done is make twisted.internet.reactor into a threadlocal  
object with Paste's StackedObjectProxy.

So far I have this mostly working, but I've hit a few stumbling blocks  
along the way:

1) it would be nice if the standard twisted reactors had an API for  
running in a one-off client mode - something like a  
reactor.runUntilExhausted(). I did write a kind of scheduler that does  
this for me (more on that below)

2) it's vaguely annoying that reactors aren't restartable - it means I  
have to destroy any reactor that's left around from the last request.  
Not a huge deal, but I'd much rather just create a single reactor that  
lives the life of the thread, and be able to call .run() / .stop()  
over and over.

3) I've attached my scheduler below. I hook it up with  
reactor.callLater(0, stop_when_complete).

The problem I'm running into with this approach is that many APIs like  
getPage() set a 30 second timeout, and then cancel the timeout later  
when it successfully retrieves the page.

My scheduler picks up the fact that there is a 30-second timeout, but  
because HTTPClientProtocol cancels the timeout, my scheduler isn't  
aware of that, and has already scheduled itself for 30 seconds into  
the future, so it can't call reactor.stop(). So instead, I wake up at  
most every 0.1 seconds - but that kind of defeats the point of the  
reactor blocking on select()/poll() if I have to keep waking up!

Maybe there's a better approach? What I kind of want is a hook into  
the reactor's runUntilCurrent() so I just get notified right before  
the select()/poll(). I'm considering just subclassing the reactor to  
hook into this.. ?


def stop_when_complete(reactor, running=False):
     all_pending = (reactor.getReaders() +
                    reactor.getWriters() +
                    reactor.getDelayedCalls())

     # depending on the platform, the waker is probably in there, and
     # shouldn't count as a pending event
     if reactor.waker in all_pending:
         all_pending.remove(reactor.waker)

     # ok, are we done? if so, tell the reactor to stop after its next
     # iteration.
     if not all_pending:
         reactor.stop()
         return

     # if we got here, we need to basically wait again to see if
     # there's anything left. We get the timeout for the next event, so
     # that we don't wake any more than we would have (this is how
     # ReactorBase.mainLoop works)
     timeout = reactor.timeout()
     if timeout is None:
         timeout = 0.0
     print "Sleeping for no more than %s seconds" % min(timeout, 0.1)
     reactor.callLater(min(timeout, 0.1),
                       stop_when_complete, reactor, running=True)





More information about the Twisted-Python mailing list