[this was originally part of a private E-mail exchange, which I'm forwarding to the list with Cyrus's permission, because I thought it might be of interest to other people who might wonder why I used Qt in the SST prototype, or who are considering alternatives for asynchronous network programming in C++... Apologies if this rant diverges a bit from core P2P issues...]

Hi Cyrus,

On Jul 12, 2007, at 2:34 PM, Cyrus Hall wrote:
This library looks extremely interesting for the P2P community as a
whole, but the QT dependency may be an impediment to adoption.  Since
you are already in C++, have you thought about using the boost asio
library? It was formally accepted earlier this year into boost, and is
a pleasure to use.  See http://asio.sourceforge.net/ for more
information.  If you've looked at asio before, and decided to go with
the QT libs instead, I'd be interested to know why.

I haven't used of asio, and hadn't heard of it until now, but I've used a libasync variant called yam (Yet Another Mainloop, written by my colleague Sean Rhea), which implements a similar async programming model in C++ using Boost's "bind" module instead of libasync's "wrap" to provide function closures in C++. It appears to me that libasync, yam, and boost.asio are all part of the same general family of closure-based async toolkits for C++.

Although I didn't know about asio, I consciously decided against either libasync for yam when I started SST for two reasons: one that applies generally to any "async library X versus async library Y" argument, one specific to Qt versus libasync/yam/asio.

First, the general issue: because async programming is not native to the API of any Unix-based system (including Mac OS X), and there is no "standard" OS-level async API that everything else can build on, every Unix-based application that wants to do async programming has to have a main loop, and has to decide among the many possible async libraries that want to provide this main loop. If you want to use two different async libraries (or two different higher-level libraries that in turn depend on different async libraries), you can't just link them both in and go, because each wants to provide its own main loop. So you have to either: (a) build some kind of custom "master main loop" that replaces each async library's main loop, (b) pick one of the libraries' main loops to use and somehow hack the other libraries to use the selected library's main loop instead of their own, or (c) run each library's main loop on a separate thread, and deal with all the inter-thread communication and synchronization problems that creates between the different parts of your application. As much as it personally pains me to say anything nice about the Windows platform, this is one way in which the Windows API beats every flavor of Unix I know of hands down: it provides a standard basic async programming facilities that diverse libraries can take advantage of without affecting the application's main loop.

So the upshot is that no matter what async programming framework I were to choose to build SST on, any application developers that prefer a _different_ async programming framework will have to work a bit to integrate SST's chosen async programming framework with their own. On any platform except Windows, there's just no way around that. If I had built SST on boost.asio, then Qt/Kde developers would no doubt make exactly the opposite complaint as you did. At least with Qt I know there is a clean way to integrate Qt's event loop into any other (by subclassing QAbstractEventDispatcher), and I intend to add some functionality in the SST library to make this even easier to do. Upon a quick perusal of boost.asio's documentation, it's not obvious to me how you might integrate asio's "io::run()" event loop into another main loop such as Qt's or Gtk's, although I would hope that the asio developers have thought of this and provided some reasonable method. In any case, given the state of the world today, any choice of async framework will make some people happy and piss others off, and Qt is already a number of years more well-established than boost.asio and at least as well-established as libasync.

The second issue specifically concerns the async programming model that Qt provides versus the libasync/yam/asio model. I've used both models, and while both are technically usable, I find Qt's model orders of magnitude more robust and less error-prone. In my experience, the difference is night and day: bugs in Qt programs tend to be obvious and easy to find by looking at stack backtraces and such, whereas libasync-style code even by the most competent programmer just oozes tricky race conditions leading to subtle dangling pointer bugs that can take weeks to track down, if they are ever found at all. There is a clear and simple reason for this. The libasync/yam/asio model is based on function closures (what boost's "bind" module does), a concept taken from functional languages with automatic memory management, and shoehorned into a very non- functional language (C++) without garbage collection. In libasync/ yam/asio, whenever you register an asynchronous callback for any purpose, you generally create and pass a function closure that "wraps" some arguments that the callback will need when it eventually gets called. Those function arguments almost invariably have to include pointers or references to objects in the heap that represent parts of the system's state - otherwise the callback couldn't know exactly what it is supposed to do or with which objects. But those function closures, and the pointer/reference arguments they contain, don't automatically have any connection to the lifetimes of the specific heap objects those arguments refer to. (You can wrap reference-counted pointers in closures, of course, but those are likely to create cyclic references...) The closure model essentially puts the programmer into a "fire and forget" frame of mind - that's why it _seems_ like such "a pleasure to use" as you say - but this frame of mind is really a giant sand trap because if you _do_ actually "forget" what async actions you've actually fired off and accidentally delete an object they refer to before all async actions referring to them have completed, you're hosed. And those are precisely the kind of bugs that almost never happen except at random times when your system comes under heavy load, objects start getting created and deleted more frequently, and the race conditions start emerging from the woodwork like cockroaches.

In Qt's signal/slot event delivery model, in contrast, the lifetime of any asynchronous notification is tied to the lifetimes of both the object originating the signal and the object to which it is delivered: if either object is destroyed, Qt silently and harmlessly severs the connection between them. Qt's model does not tempt the programmer with the false promise of a "fire and forget" model that doesn't actually work in a language in which objects change state and can be deleted at any time leaving dangling pointers. If you want to get an asynchronous notification of an event in Qt, you have to have an object around to receive it; if you delete the object, you won't see the event. Which is far more robust and almost always exactly what you want. In summary, I think that use of the wrap/bind/ function closure model for async programming in C++ is a terrible idea, and frankly something like asio built on that model should never have been accepted into a library that purports to encourage clean, robust programming practices.

These are just my personal opinions, of course - no doub others will disagree. As I said in my earlier E-mail, though, I'm happy in any case to work with anyone interested in using SST regardless of what async toolkit they've chosen (or rolled their own), and will do my best to make the prototype easy to incorporate into applications in the future.

Cheers,
Bryan



_______________________________________________
p2p-hackers mailing list
[email protected]
http://lists.zooko.com/mailman/listinfo/p2p-hackers

Reply via email to