[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