[Zope3-dev] Asyncore (and Twisted)

Shane Hathaway shane@zope.com
Fri, 01 Nov 2002 02:03:27 -0500


Itamar Shtull-Trauring wrote:

> The design issues can however be worked around, of course. But look at
> http://twistedmatrix.com/products/echo_server -- notice the Twisted
> protocol doesn't have to know anything about the transport, whereas the
> asyncore code is tied to socket module, TCP and is in general ugly
> (admittedly dispatcher_with_send would solve some of the ickyness).

I just downloaded Twisted 1.0 and looked at twisted/internet/default.py, 
Twisted's default reactor code (there are multiple implementations). 
I've talked about this code with Itamar and Glyph, but never actually 
looked at it myself.  Yet at first glance I could read the whole module 
with ease.  This makes me feel as if this is supremely well written 
code.  In addition to good variable and method names, proper use of 
comments, adherence to standard style, and use of interfaces, using all 
lowercase for module and package names seems to make an important 
difference.

You can think of a reactor as the (far better designed) object-oriented 
replacement for asyncore's main loop.  This is the "really small piece 
of functionality that you'll need no matter how you slice it" that Guido 
mentioned earlier.

> >The one problem with asyncore is that it doesn't work well in a
> >threaded environment: if thread A is executing the asyncore main loop
> >and blocked in a select() or poll() call for file descriptors x and y,
> >and thread B adds file descriptor z to the set of filedescriptors of
> >interest, thread A won't wake up until x or y is ready, or until it
> >times out.  The solution for this is pretty icky (in part because
> >asyncore doesn't provide one itself, so the solution has to hack into
> >asyncore's internals).  I don't know what Twisted does -- is it any
> >better?
>
>
> Twisted doesn't let you directly write to sockets from threads. However,
> it lets you wake up the event loop thread, and you can do:
>
>    reactor.callFromThread(someNonThreadSafeAPICall, arg1, a=b)
>
> and the event loop thread is woken, and the method will be called in the
> next iteration of the event loop in the event loop thread.

In my "twisted asyncore" mindset ;-), I think of every reactor as having 
a built-in "trigger", which is what makes callFromThread() work 
efficiently.  In asyncore, triggers are an add-on, but Twisted 
recognizes how important they are and puts them in the core.

> This queuing can be a performance issue for situations where you want to
> have threads doing the networking directly (as Zope3 does), but given
> the motivation the APIs might be extended to support networking calls
> directly from threads.

Well, I don't think networking from threads is the real goal.  What's 
important is that Zope's ZODB connections are scarce and precious, so 
time spent in application code must be minimized.  To help achieve this, 
Zope threads must never block for I/O except in rare cases.  Therefore 
all input and output must be fully buffered.

But when Zope replies to a request with a very large file, the output 
buffer can be a problem.  A minimal strategy would put the entire file 
into RAM or a disk file before sending it over the wire, which seems 
wasteful.  It is desirable to start emptying the output buffer as soon 
as Zope starts filling it.  Zope 3 currently does this in the 
application thread (without blocking), resulting in the earliest 
possible response to requests.

I think that desire isn't important enough to hold up integration of 
Zope 3 and Twisted, though.  We're talking about an optimization here, 
but currently, Zope 3 is explicitly un-optimized.  We should move 
forward without the optimization.

Shane