asyncore deficiencies

Josiah Carlson jcarlson at
Sun Mar 28 11:27:33 CEST 2004

> After 2 days or so of fiddling about, I have decided, as many before
> me have, that the current asyncore module is the most inflexible,
> brain-dead, and useless module to be found on the face of the earth!

A bit of subclassing goes a long ways towards making it easier to handle.

> It appears that my next stop for well-engineered event-driven
> progamming is Twisted, but that is quite a hop from the 500 lines that
> is asyncore. I have decided in the meantime to stick with asyncore,
> despite it's failings. It has got me thinking though, shouldn't there
> be a standard module that does this sort of thing correctly, on a
> scale comparable to asyncore? ie. asyncore2

Indeed.  Twisted is a big jump.  Those that have gotten used to it seem 
to enjoy it quite a bit.  It seems to have most every protocol already 
implemented, and it seems to interact nicely with various GUI toolkits, 
databases, etc.  I've stuck with asyncore.

I agree that there should probably be some sort of asyncore2; I have a 
handful of boilerplate servers and clients that I use as a base any time 
I need something.  Optimally, an asyncore2 library would have simple 
clients and servers ready to be subclassed and customized as needed.

> My biggests gripes with asyncore so far have been:
> - Exception handling: namely, it's fairly braindead. As an example, if
> you are using a private map for loop(), when an Exception is caught it
> is dumped to stderr, then the dispatcher causing it is destroyed.
> However, the dead dispatcher is not removed from your private map,
> resulting in the loop iterating again, and your dead dispatcher
> causing an "invalid file descriptor" error.

I'm curious as to why you need to use a private map.  Most uses of 
private maps I've seen haven't added any sort of meaningful 
functionality that couldn't have been handled by the default map.  Yours 
could be an exception, but I'm still curious.

> - Unclear API: asyncore.loop(timeout = ...) is a parameter passed on
> to the underlying poll() or select(). I can see no circumstance for
> ever needing to change this. However, I did manage to mistake it for a
> "maximum execution time" of the poll function, assuming that after
> this time it would return control to the caller. Again, it's unclear.

asyncore.loop loops forever.  asyncore.poll does the one-shot deal. 
more on this later.

> - Lack of flexibility. I have to use SIGALRM or an external process
> connected via a socket to get asyncore to change execution path based
> on non-socket events. The only other way of accomplishing this, is to
> temporarily empty your map to break out of loop(), then repopulate it,
> probably using a couple of global variables while you're at it.

Again, you need to use asyncore.poll.

> - Unclear interfaces: you must override all methods inherited from a
> dispatcher even if you don't use all of them - this makes for verbose
> code when it is not necessary.

Agreed.  I have a few private classes that I subclass from that take 
care of the "unimplemented" warnings.

> - Incorrect interfaces: (this is assumed) for a UDP socket,
> handle_connect() gets called on first read, despite the fact that UDP
> is connectionless, and it doesn't make sense to make an exception of
> the first received packet. The idea of taking the strange semantics
> detected from select() or poll() and turning them into nice meaningful
> names is a good one, however it could be improved.

I've never used asyncore with UDP sockets, but maybe using 
"handle_connect = handle_read" in your subclass definition could be 

> So far, I'd like to see:
> - Less stuffy API: move select() and poll() choice to either an
> internal decision based on the number of active dispatchers (bad),
> system call availability (better), a module-level configuration value
> (ish), or simply use one or the other. Polling objects translate quite
> well into how asyncore works internally (but they're not available
> everywhere).

That is exactly why polling is optional.  Select is available on every 
platform, but poll is faster on platforms that support it...

> - A 'process_once' to allow you to write your own loop()s.

asyncore.poll (or the equivalent poll2 and poll3 if poll is available on 
your platform) is what you are looking for.  I use it quite often for 
things like:

while not QUIT:
     #handle any sort of background processing if necessary

It is also quite useful to be able to drop in an asyncore.poll(.001) 
inside a GUI application.  What I commonly do in wxPython apps is...

         #inside __init__
         wx.Timer(self, 10001).Start(10, wx.TIMER_CONTINUOUS)
         wx.EVT_TIMER(self, 10001, self.PollSocket)

     def PollSocket(self, event):
         #pull any sort of waiting packets
         #from the properly buffered dispatchers

> - Non braindead exception handling: if an exception occurs, I want to
> see it propagated back up to the parent, as happens everywhere else,
> not passed to a handle_error() method or "verbosely ignored".

The trick with doing this is that you end up interrupting other 
potentially ready sockets with handling an exception.  If you are 
feeling fiesty, I believe you can use the following and get the error 
propagated back up:

     def handle_error(self):

> - Decoupling from socket objects: removal of the __getitem__ magic
> which causes screenfuls of abuse for a simple typo at the wrong stage
> in execution.

Infinite recursion in asyncore?  Yikes, I've not hit that bug before. 
Care to post some offending code?

> - Possibly combined with the above, a more abstract way of watching
> for events from elsewhere? Doing this might ruin the overall
> simplicity of the module.

Something like...

     def handle_read(self):
         socket_post_event(self, READ_EVENT)

One could even have a framework for registering even handlers...yikes, 
this is starting to sound like wxPython.  I don't know if that is a good 
idea or not.

More information about the Python-list mailing list