[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Mnemonic MP document v0.75
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). I implemented a
mHandleURL message, and as it gets passed between potential handlers,
they must work it out among themselves.
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
routes messages based on message type only (and who is interested in that
message type), and the second method routes messages based on the type of
message AND a connection identifier. The second method will be used to
send data, and status messages for specific connections. The first method
is used for feature requests (Get this URL, etc)
For both cases, 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 the feature request messages may trigger a module
load event.
Proposed Naming Scheme:
All message objects that are not routed by id will be named
mMessageType, as in mFetchURL, or mNewID.
The message objects that are routed by id will be named
midMessageType.
Examples:
Registering an Interest
First case:
interest = new InterestByID<MyClass,mMessageType>(this,&(this->CallBack));
interest->AddID(id_I_am_interested_in);
interest->RemoveID(id_I_hate_now);
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).
We may want to define a new header file, netmanager.h which declares
the interface to the network manager as wrapper around the message
creation and sending calls. That way, message sending is transparent
to the programmer.
ajb / 970915 / version 0.75