[Home]Multiplexing/AaronsMultiplexingIdeas

BOOST WIKI | Multiplexing | RecentChanges | Preferences | Page List | Links List

Aaron's Principle of Waitable Interfaces: Any abstract datum or property that may be changed during the course of the execution of a program must have an interface that allows an arbitrary user to be notified when it is changed in an efficient and timely manner.

To allow interfaces to meet this requirement, a "universal demultiplexor" must be created.

I have a prototype of a concept universal demultiplexor, and some examples, under construction. A version of it shall be made availible from this page by January 10, 2005.

What follows are some earily notes I made on a demultiplexor design. One person complimented them as "terse," but they're probably better described as "incoherent and confusing."

Aaron's Informal Proposal for a Universal Demultiplexor for Boost

This description is partly from the design of a demultiplexor library I created and used privately, based on experience with previous unsatisfactory attempts.

Firstly, lets ignore preconceived notions of what a demultiplexor is supposed to look like, and forget all about the event handling patterns. There are plenty of big, clunky, monolithic demultiplexors out there. These were probably created because someone started with select() and tried to figure out a way to wrap it up in something that looked pretty. Lets not repeat this exercise.

To start with the end, what we really want to do is this:

socket my_socket;
// ...
watch w = my_socket.on_close(my_close_handler);

In other words, when the connection referred to by my_socket is terminated, we want my_close_handler to be called. (Lets ignore watch for now.)

Beyond this, to the end user, most everything else is an implementation detail, with a few exceptions. But on to the details first.

my_socket would love to do this, and it knows how, too, since it knows about the underlying system API, which might be UNIX. The only problem is that it needs someone to tell it when the socket has closed. So, we need an object that the socket class can use to get close notifications from, perhaps called global_multiplexor.

class socket {
  public:

    // ...

    template<typename functor_type>
    watch on_close(functor_type functor) {
      return global_multiplexor.on_file_readable(socket_fd,
        if_then(bind(is_eof, this), functor));
    }

    // ...
  private:

    static bool is_eof(socket &);

    int socket_fd;

    // ...
};

What exactly is global_multiplexor? well, its an instance of a class that conforms to a set on concepts describing what a UNIX multiplexor class must provide, such as the on_file_readable member.

What if one wants to use some other multiplexor? There should be an overload (or default parameter) for on_close that specifies the other multiplexor that should be used for that operation. (The issue of whether specified multiplexor should have object granularity or operation granularity, or both, needs to be considered further.) This allows, for example, dispatching events into other threads, or to multiplexor adaptors for third-party libraries.

    template<typename functor_type, multiplexor_type>
    watch on_close(functor_type functor, multiplexor_type multiplexor);

global_multiplexor itself is a namespace variable declared by the Boost, and is thread-specific, allowing reasonable defaults to be set per-thread. (For example, all events generated in thread X could be processed by thread Y.)

Now, returning to the initial example, it might be the case that the user is actually writing a Gopher client, and so he's probably less interested in when a particular socket closes, and more interested when the Gopher server closes. For example:

gopher_server my_gopher_server;
// ...
watch w = my_gopher_server.on_disconnect(cout << constant("Disconnected!"));

gopher_server::on_disconnect is then implemented as a thin wrapper over socket::on_close (and whatever other logic is needed), similar to how the latter function is implemented over template_multiplexor_type::on_file_readable. In general, high-level events are formed from chains of lower-level events.

After a while, the user of gopher_server might no longer care to have the "Disconnected!" message printed, for example after it has already been disconnected, or the application is quitting. Remember that 'watch' object? As soon as it is destroyed (in this example, when w goes out of scope), the creator of the watch (in this case, the top-level multiplexor) will no longer generate events of that requested type, free all associated resource, and destroy the callback functor.

A watch object is actually implemented as a smart pointer (such as boost::shared_ptr) to a type that knows how to notify its creator when it is destroyed. Oftentimes, simply passing an upstream watch object to a caller is unsufficient, and a new watch (that stores the old watch as a private data member) must be created. The demultiplexor library must provide a set of templates that makes creating these objects quick and painless.

The only major element thats still missing is the implementation of the multiplexor template itself. The multiplexor template must fullfill a system-specific concept that allows user classes to hook into the features they need. There is also a generic concept it must fullfill, including the timer interface and generic queued callbacks (used to break dispatch cycles, allow cooperative multitasking, and queue work from one thread to another.) The demultiplexor library provides a policy-based implementation of such a multiplexor. Policies include:

template<class allocator, class locking, class core /* ... */>
class basic_multiplexor : private core<allocator, locking, /* ...*/ > {
  // ...
  template<typename functor_type>
  watch on_file_readable(int, functor_type functor);
  // ...
};

An important item neglected so far is priority. Experience has shown that priority is important, and many patterns will require some notion of priority to work correctly. So far, a simple integer has proven sufficient to express priority. Priority could be specified in the same places a multiplexor can: to individual operations, possibly to individual objects, or to entire threads.

More information, examples, and diagrams to come, as I get feedback.

Aaron W. LaFramboise? aaronrabiddog51@aaronwl.com Tuesday, September 14, 2004


BOOST WIKI | Multiplexing | RecentChanges | Preferences | Page List | Links List
Edit text of this page | View other revisions
Last edited December 23, 2004 1:52 am (diff)
Search:
Disclaimer: This site not officially maintained by Boost Developers