[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Mnemonic MP Document / v 0.52



                  Mnemonic MP Design Document
                      (or how I learned to love C++)

Introduction:
 Imagine a framework that allows transparent extensibility by dynamically
 loaded objects.  Imagine a Browser written on top of that framework.

 Can you visualize Mnemonic?

 This is the new Mnemonic.  After months of design work the Mnemonic
 Team has decided on a framework to use: message passing.  Not just
 your everyday run-of-the-mill message passing but dynamic message
 passing: where message passing is tied into dynamic object loading.

 This framework allows us to extend the global Mnemonic API at runtime
 -- we are not limited by having fixed abstract classes that describe
 interfaces.

Why not abstract interfaces:
 Experience working with the project trying to make everything fit into a nice
 interface shows that you can spend weeks working on an interface and still you
 are guaranteed to find something that doesn't easily work through it.  You end
 up using flags, and other silly C'isms to try and generalize the interfaces,
 and in the end, you end up with a big mess.  Well, maybe you don't, but we
 did.  A browser is an extremely complicated mess, and trying to design for
 extreme extensibility makes it even harder.

 In the end, with our MP scheme, we end up re-implementing a version of
 'virtual functions', but we lose the restrictions of fixed interfaces.  It
 is very useful to view the entire message passing scheme as a
 re-implementation in a more general form of C++ virtual functions, and
 inheritance.

Potential Problems:
 There are problems with this approach, message proliferation is a major
 one, but we have adopted a "manager, handler, slave" layout of objects
 which should prevent this sort of problem by breaking up the effective
 namespace of messages into separate domains.  This is the same problem
 we would have faced without message passing as features started showing
 up -- and the same solution.

 We also have to worry about the overhead of message passing.

Overhead:
 The overhead of a message send as opposed to a virtual function
 call is very little, since a message send is basically a function call
 and then a lookup in a linked list, and another function call.  There is
 overhead in terms of space though, we use a bit more memory for the
 message objects and the message sending objects:

  There is the one time cost of the message sending and handling objects,
  these are about 2-3K for each message (based solely on the size of the
  mFetchURL.o file).  This cost is entirely due to the fact that we now
  allow multiple receivers as opposed to a standard virtual function
  call which only allows one implementation.  So far I estimate
  about 15 messages for the entire network manager.

  There is the cost of creating messages to send them, this should be
  equal to the data inside them (the same amount as it would be in
  building a stack for a function call, including the return stuff) plus
  a few more bytes.

 We aren't talking thousands of message types here.  And the areas where
 message passing won't have a beneficial effect will be left as fixed
 interfaces.
 
Dynamic OIL Loading and MMP:
 If we have an object that sends a message that we have no receiver for,
 then we need to try and find an object to handle it (the corresponding
 case for a fixed interface is you make a call through an abstract base
 class which has no corresponding implementation yet).  The ObjectManager
 and RegistryManager handle the details of finding the correct OIL, etc.
 I need someone to implement this aspect of the OM/RM (joev -- this was
 your idea :).

 The details of handling dynamic loading are a little tricky to work out,
 we have to make sure that the appropriate message senders get loaded,
 message types registered, etc.  I have worked out that we can load the
 message types and senders dynamically relatively easily (see my example
 mailed to the list as testMessage.tar.gz).

 This should be easier to implement than our initial scheme for module
 loading:
  The include file situation is a lot simpler -- clients only have to
   include the mMessageType.h file for each message they wish to use.
  We do our own dynamic binding through the Interest objects (which is
   more general than binding by what interface the object satisfies --
   unless you want to have one interface definition for each function
   call -- which is still limited at compile time.)

Upgrading transparently and Exclusivity:
 If we can replace a feature by just loading a new module that handles a
 certain message, we can easily upgrade the browser.  Of course, we need
 a nice scheme to handle conflicts between modules that demand the
 ability to exclusively handle a certain message (think about proxying,
 and URL filtering).  This is an official request for someone to
 volunteer to work on this mechanism (handling one request with multiple
 objects responding to it -- which one gets it, etc).

Manager/Handler/Slave:
 We are not throwing away the Manager/Handler/Slave description of object
 relationships.  They serve now to guide us in determining the layout of
 messages and interests.  In other words, we still are following a basic
 OO layout, but now the interactions between these three levels are handled
 as messages.  Only in cases where two classes are very closely related
 is this mechanism short-circuited (as in the interface between the html
 processor and the page display engine) for performance reasons.

Mnemonic Message Passing:
 There are two types of message passing implemented in Mnemonic.  The first
 is a group based message passing scheme (the old MessageManager scheme)
 which is specialized for passing data indexed by "connection ids".  In other
 words as opposed to using the message type as the way of registering an
 interest, the numerical identifier of a connection is used instead.   This
 way we can have DataMessages that get 'routed' by id number instead of just
 going to all people who want data messages (example, DataMessages and
 StatusMessages).  We should re-write this scheme so that we can do selection
 by type of message as well (ie, specialize the below method to separate things
 by id as well, for example: we don't want DataMessage going to the Browser,
 only to the Filter stacks, and we don't want StatusMessages going to
 the filters, only to the Browser.  Yet, both are connected to the same
 message group in our current design). Suggestions as to how to do
 this nicely would be appreciated (currently, I would just expand the
 MessageSender / Interest classes to work by id and message type, but
 I think there is a better way to do this).

 The second type is the dynamic message passing described above but without
 the id stuff.   Each object may register an interest in a MessageType, and
 specify a callback function to handle that message.  Return values are
 effected by using data members in the messages themselves (see mNewID for
 an example).  Only this type may trigger a module load event.

Proposed Naming Scheme:
 All messages objects will be named mMessageType, as in mFetchURL, or mNewID.

Examples:
 Registering an Interest
 First case:
  new Interest<MyClass,mMessageType>(this,&(this->CallBack),id); // not
  // implemented
 Second case:
  new Interest<MyClass,mMessageType>(this,&(this->CallBack)); // implemented
 Third case:
  new Interest<MyClass,mBaseType>(this,&(this->CallBack)); // not implemented
  new Interest<MyClass,mSpecializedType>(this,&(this->OtherCallBack)); // " "
   (I didn't discuss this above, but it would be nice to handle
   inheritence in messages -- where you can register an interest for an
   entire class, and also the subclasses you know about.  In other
   words, if you know how to handle basic status messages by printing
   them out, then you do
   Interest<MyClass,mStatusMessage>(this,&(this->PrintStatus)); But, if
   for example you can handle a new version of status message you also do
   Interest<MyClass,mNewerStatusMessage>(this,&(this->newerStatusHandler));
   Magically the messages go to the right place (and don't get repeated)
   [note this is just like reimplementing C++ inheritance...
   SomeFunction(base *), and SomeFunction(specialized *) where class
   specialized : public base {};]  I think this is possible to
   implement, but I'm stuck on the not repeating the send to the
   subclass handler and the parent handler.  This is an EXCELLENT way to
   provide backwards compatibility and versioning while extending the
   browser.)

 Use of message:
  mFetchURL fu(...);
  fu->Send();

Future:
 We have a lot of work to do here -- we have to:
  1. Finish the design of the message senders, messages, etc.
  2. Move all the objects over to using this message passing (relatively
     easy, but a lot of grunt work).
  3. Implement an exclusive handling mechanism including conflict resolution,
     and a priority mechanism (two responses to one request, etc).  Doing this
     is amusing but requires that we finish the message passing design.
  4. Dynamic loading in response to a message send.
     The OM parts of this are not too complicated, we already have a
     registry, we just now have to expand it for message types.  Pretty
     fun once it gets finished.

 Everyone is invited to look at the mnemonic/src/netmanager/netmanager.[cc|h]
 files to see how I worked things out with the mFetchURL type, and mNewID
 type.  Note now that people no longer need to include "netmanager.h" if
 they are just using mFetchURL, or mNewID interfaces.

Use in the Network Manager:
 Inside the network manager, there will probably be ~7 external
 messages (So far we have FetchURL, PutURL, NewID and ReleaseID [maybe a
 connection identifier object would be useful], Stop, and, Query), and
 maybe that many internally used messages (So far, GetObject and
 maybe the TransferFilter building calls [AddFilter, RemoveFilter], though
 I think those work OK as a fixed PI -- but more imaginative people may
 see some circumstance where replacing those might be useful.)  Let
 us look closely at the network manager to handler communication, as I
 believe it is instructive.  The only purpose of the Network Manager is
 to provide global connection handling (ie, limit total number of
 connections, and provide control of those connections), provide
 TransferFilter stack capabilities (maybe this belongs in the Filter
 Manager), and to provide a simple Manager Level message set for other
 objects to use, oh, and to run the network handler loop, and also to
 maybe to optimize the GetObject call.  Watch:
 Browser sends out a NewID() request
  NetworkManager returns the ID
 Browser sends a FetchURL(id, url, ...) message.
 Network Manager receives it and sends out a GetObject request
   Something strange goes on if we have a collision with who wants to
   handle this (ie, ProxyHandler always wins out over others, etc).  The
   Handler does the connection setup stuff, and uses the embedded handle
   to the network manager to set up negotiation and transfer filters.
   Handler sets up a file descriptor and registers it in the NM via a
   fixed PI.
 Network Manager adds that information to its select() loop, and goes
 about its business.

 Handler to Slave object communication is once again based on fixed PIs
 (at least in the network manager), and NetManager to Handler is based
 on fixed PIs (except for the GetObject call).

ajb / 970831 / version 0.52