[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