David M. Wilson
dw-google.com at botanicus.net
Sun Mar 28 05:28:35 CEST 2004
I finally got around recently to doing something serious using Python
for serving network clients asynchronously, deciding on asyncore as my
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!
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
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.
- API wasted on less than useful features: of the few configurable
parts of asyncore, one of them is the choice between select() and
poll(), which I find to be completely useless, as larger projects
(those that may wish to use poll()) would in my books already have
migrated away from asyncore, and the smaller projects are more
interested in getting code written, rather than worrying about some
low level events strategy. It also makes the API rather tied to
- Poor __getattr__ use: this has caused numerous "recursion limit
exceeded" type exceptions, formatted using asyncore's less-than-ideal
so called "compact exception formatter". This results in errors that
are practically uncomprehendable, even as an experienced programmer
you know to look at the top for the true source. The __getattr__ hack
causes behaviour that is far from less than obvious.
- 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.
- 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.
- 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.
- 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.
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
- A 'process_once' to allow you to write your own loop()s.
- 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".
- Decoupling from socket objects: removal of the __getitem__ magic
which causes screenfuls of abuse for a simple typo at the wrong stage
- 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.
Does anyone have any other ideas? I'm sure there are at least a few I
have forgotten. If there was support for an improved 'asyncore2', I
would very much like to see an improvement on the existing module, not
a silly overcomplication with needless feature adding and
overabstraction (let's not name names here ;).
More information about the Python-list