Run-Time Wrapper

Home/Documents/Development/High-Level Design/Run-Time Wrapper
 Project Closed
 Site Index
 User Docs
 Introduction to ANet
 High-Level Design
 Run-Time Wrapper
 Client Connection Modules
 Data Transformation Modules
 Client SDK
 Cluster Group Modules
 Cluster Filter Modules
 Core Modules
 Handshaking Protocol Modules
 Packet Protocol Modules
 Connection Protocol Modules
 Bandwidth Manager Module
 Document Type Definition (DTD)
 Low-Level Design
 Protocol Specifications
 Task List
 Development Roadmap
 Other Docs
 Mailing Lists
 Contacting Us


by Benad

The Run-Time Wrapper "wraps" all the modules contained all the modules in the ANet deamon in an environment where memory, file, configuration and log management are abstracted.

Table of Contents


The Wrapper

The "wrapper" is, simply said, the program that the user has to launch to run the ANet deamon. Actually, is is the running program, because it controls the data flow inside the deamon. The actual implementation in done within what we call "Modules", so the wrapper has to load, set-up and run those modules. Also, it has to transfer the data between the different modules.

The wrapper might run as a single thread. Thus, any long Input/Output should be done asynchronously, or the whole process will be blocked.

The data flow is done in "passes", in two directions: upwards, from the network to the client interface, and downwards, in the other direction.

What is a Module?

A module is an isolated group of code that can have one or more running instances in the ANet deamon. It is isolated by not being able to make any assumption about its environment, other than the run-time wrapper and the other modules it is allowed to communicate with.

A module can be stored in a static library, a dynamic loading library (DLL) or compiled with the rest of the deamon.

A module can have multiple running instances in the deamon by having more than one "context". The context is some global data, unique for each different instance, and maintained by the wrapper.

Loading Modules

First, the wrapper has to load the code of the module. It does so by assuming that all modules will implement a function that returns a list of the function names supported by the module, the name of the module and its version.

Secondly, the wrapper has to load instances of the module. It does that by calling an initialization function, if implemented(1), in the module, and then assigns an unique number, the module instance ID, to the new instance.

Thirdly, it has to assign some "global space" (more on this below) to the module instance. Whenever a function in a module is called, the module instance ID is passed to the function, allowing it to restore the global space of the current instance.

Memory Management

I know. Everybody loves the "new" operator in C++. Or the "malloc" function in C. But then, in something as modular as ANet, using them could break up the whole program. This is not only for controlling memory leaks, but especially to control, at all times, the entire data used by modules outside the program stack.

An example. Let's say you want to close the ANet deamon for 5 minutes, then restore ANet to its exact same state as before. If every module does its own memory management, then the modules may not be "smart" enough to store to the hard disk the data they allocated themselves.

More importantly, each module has multiple instances running in ANet, and thus the data used by a module, that should stay in memory between calls to the module, has to be maintained by whatever "knows" what are those instances. To solve all of these problems, the run-time wrapper has to do two things.

First, no module should do any memory management other than the one offered by the run-time wrapper. Given a module instance ID and the size of the wanted memory block, the wrapper can create a new "memory tag" that can be used to get the memory location of the memory block. Only those "memory tags" should be remembered, as the actual memory locations can change between the different calls to a module. A module can change the size of a memory block, lock its position in memory, mark it as temporary so its memory could be freed if memory is tight, and so on(2).

Second, whenever a function in a module is called, the module instance ID is passed to the function. With that, the module instance can get the address of its own "global space". A module instance's global space is a small block of memory that is created when the instance is created. The module instance can get its global space with no other information than its own ID (which it always gets), and, for obvious reasons, the global space cannot be marked as temporary or deleted. Usually, instances should use their global space to store the other memory tags that they allocated(3).

File Management

The wrapper will allow modules to do several things with the file system:

Once opened, files and directories are identified by an ID. The files and directories can be identified on the file system with three kind of paths:

Those three kind of paths will be supported on all operating systems that ANet will support, with the following restrictions:

As said before, the wrapper could be a single thread. So, all the file management functions are only asynchronous; they won't have any synchronous version. This is to make sure that no module will block the entire deamon because it is waiting for some Input/Output from the file system.

Obviously, some timing functions will be available, so that modules can know when their I/O functions have timed out.

Configuration Management

The run-time wrapper will also manage the configuration files. The configuration files are XML[1] files(4). At least one of these files has to contain the root element, which represents the whole ANet deamon, and contains the settings of the run-time wrapper.

All module instances have their settings in the XML files. Thus, when an instance is initialized, it is given access to their XML tags. The instance is free to change their own XML tag and the tags it contains, and the modified XML can be seen or saved back to the hard disk, if the user wants it.

Log Management

The run-time wrapper will also offer functions that will allow module instances to log messages to the hard disk. The wrapper will try to write those logs as fast as possible, so the write cache will be small. This can really help if you want to log something before crashing. Because of this small cache, even if the output to the disk is asynchronous, it could result is a severe speed hit if abused, by being too "verbose". So, don't abuse of it.

Speaking of crashes, module instances can call a "die" function, which logs something before terminating the module instance(5). Since the global space and all memory allocated to the instance is destroyed, the instance should return from the function call as soon as possible.

Inter-Module Communication

Finally, the run-time wrapper will allow module instances to exchange information through function calls in ways that are not explicitly required by the wrapper. Basically, an instance tells the wrapper "I want to call function X with arguments Y in module instance M". Then, the wrapper will call a function in M that will return true only if, for the given information from the calling instance, M accepts to get called by the other module. Then, if that's the case, then function will be called with the arguments.

Actually, before trying to make an inter-module call, the instance will have to ask from the wrapper a list of instances that can accept inter-module calls. Based on that information, the instance decides which instance to call.

Implementation Notes


See the complete DTD for more information.

<!-- Root element -->
<!ELEMENT ANet (ClientConnection)+, BandwidthManager, (ClusterGroup)+, CoreModules, (Cluster)+>
<!ATTLIST ANet %security;>


(1) From the previous step, the wrapper already "knows" all the functions that are implemented, so it already "knows" if the initialization function exists.

(2) This was "inspired" by, you know...[2]

(3) Unless you're already having some fun time making memory leaks...

(4) Why XML? The first thing is that I don't want the ANet configuration files to be too difficult to edit. Since ANet needs to have a structured configuration file, anything else won't be as good. Also, you can use multiple XML files that will "patch" each other, in the order they are loaded. Finally, with all the hype around XML (why all this hype anyways?), a lot of tools and code is already available.

(5) A bit like the "die" function in Perl[3].


About the references...

[1] The World Wide Web Consortium: "Extensible Markup Language (XML)". External link.
[2] Apple: "Inside Macintosh: Memory". External link.
[3] Perl Mongers. External link.

Last update for this document: August 24, 2001, at 1:34:25 PST