The term "bootstrapping" means the one-time initialization of the library that is to be performed in an early stage of a process started on an executing hardware. The counterpart of bootstrapping is "shutting down" the library.
Standard bootstrapping and shutdown of ALib is performed by invoking the functions alib::Bootstrap and alib::Shutdown. Such invocation usually is performed as one of the first and last actions in the function main()
. In source code, this simply looks like this:
The following chapters explain the details behind these invocations, provide recipes to customize certain aspects of the process, and explain how - optionally - the provided mechanics can be leveraged for the bootstrapping and shutdown of custom code entities likewise.
Bootstrapping and shutdown processes involve all modules that are in need of it. Therefore, this module is always the topmost module in the module hierarchy. While this is shown in the hierarchy graph of ALib's main manual, there, only optional dependency-arrows to the next lower level camp modules are shown. This was made to simplify the graph. The truth is that an optional dependency exists to almost every ALib Module. These dependency-arrows verbally state: "If the targeted module is included in the ALib Build, then module ALib Bootstrap will care about performing necessary initialization and shutdown actions."
The following circumstances increase the complexity of bootstrapping:
1. The C++ Language:
main()
is invoked.main()
is invoked!), hence also the order of such custom code execution is random.2. Resources And Configuration Data:
4. Module Dependencies:
5. Multithreaded Access:
An example of this scenario is externalized resource string data: once such data is fetched during bootstrap, it is treated as constant and accessed read-only thereafter.
Some of the non-camp modules have a requirement to perform some bootstrap and shutdown actions. These calls are performed internally with the invocation of methods alib::Bootstrap and alib::Shutdown as sampled above.
The library here respects the module dependencies and selects code according to the ALib Build.
Thus, a user of the library does not need to care about the details but just needs to include ALib.Bootstrap.H and invoke the functions at the beginning and end of method main()
as shown in the introduction.
More complex boot strapping strategies with some user involvement can be necessary when it comes to ALib Camps, especially if a user decides to implement custom ALib Camps.
When ALib Camps are included in the ALib Build, then things can become a little more complex.
But first of all the good news: Even in the usual case that one or more ALib Camps are included in an ALib Build, bootstrapping and shutdown of the library is done exactly the same as shown above. In other words:
If the above does not apply to your field of application, all you might need to know is that functions alib::Bootstrap and alib::Shutdown are internally largely extended in the presence of ALib Camps but, it is still the same simple call.
For bootstrapping and shutdown, types derived from class camp::Camp need to implement two protected, abstract methods, namely
Both methods are invoked more than once: Bootstrapping is done in three phases, defined by the enum
type BootstrapPhases and shutdown is done in two phases, defined by enumeration ShutdownPhases. The implementations of the methods need to switch
upon the given levels and perform different tasks.
By default, each phase will be finished for all camps, before a subsequent phase is initiated. The loop over the phases and its inner loop over the list of camps is implemented with namespace functions:
With function alib::Bootstrap(BootstrapPhases, camp::Camp*, int,int,TCompilationFlags), an outer loop over all phases is initiated. An inner loop iterates over all camps, in order of their "dependency level". For each combination of phase and camp, virtual method Camp::Bootstrap is called. This ensures that for each phase, "low level camps" are initialized first and those of "higher level" are initialized next.
The three phases are defined as follows:
BootstrapPhases::PrepareResources
All initialization actions that exclusively involve static data, is deemed to be performed in this phase. For example, the registration of box-functions is to be done in this phase.
The phase received its name from the fact that the ResourcePool is already available and a camp is supposed to feed its static default resource strings to the instance retrieved with Camp::GetResourcePool.
More on this topic will be explained in a later chapter.
With function alib::Shutdown(ShutdownPhases, camp::Camp*), an outer loop over the two shutdown phases is initiated. Again, an inner loop iterates over all camps, but this time in reverse order of their "dependency level". For each combination of phase and camp, the virtual method Camp::Shutdown is called. This ensures that for each phase, "high level camps" are shut down first and those of "lower level" are shutdown later.
The two phases are defined as follows:
For default bootstrapping and shutdown, this is almost all we need to know. The only remaining question is: Where is the list of ALib Camps, which is used for the inner loops of functions Bootstrap and Shutdown, defined, so that I can plug my custom module in?
The list is a public namespace instance given with alib::CAMPS. Along with it, function alib::BootstrapAddDefaultCamps is provided, which fills this list with the camps delivered with ALib (and selected in the ALib Build) in the right order. The right order means: respecting the dependency hierarchy.
With a complete ALib Build, the list will be:
The list is traversed from 1 to 6 with bootstrapping and in reverse order when the camps are shut down.
Now, if function alib::Bootstrap is invoked without explicitly filling the list beforehand (right as it was shown in the introductory section of this chapter), then the list will be detected to be empty and function BootstrapAddDefaultCamps is automatically invoked.
In later sections of this manual, options for modifying these defaults will be demonstrated.
With the knowledge taken from the previous sections, it is now easily understood what was said in the introductory chapter:
Consequently, the simple version of bootstrapping was given like this:
int main( int argc, const char **argv ) { alib::Bootstrap(); ... ...
This standard invocation of function Bootstrap not only fills the list alib::CAMPS, but also invokes overloaded function
alib::Bootstrap(BootstrapPhases, camp::Camp*, int,int,TCompilationFlags).
with parameters:
The loop iterating the phases, starts with the first phase that the camps have not been bootstrapped with, yet, and ends with the given targetPhase. With that, all phases are executed in the default case. And because the target camp is set to the last one in the list, the inner loop covers all camps.
What was not discussed yet is when the instances of ResourcePool and variables::Configuration are created and how the corresponding pointer members Camp::resourcePool and Camp::config are set.
This is how this is done:
The following schematic summarizes this:
With this information, the standard process of bootstrapping is well-defined. The following chapters now introduce different ways to customize bootstrapping.
As explained in the previous chapter 2 Weak Support For Bootstrapping In C++, the reason why bootstrapping ALib is a non-trivial task, does not lie in specifics of this library, but in a general lack of support for bootstrapping in the C++ language. Any more complex software needs to solve this task.
Consequently, it might be a reasonable decision to adopt what this library offers and use this concept and the mechanics to bootstrap custom code units likewise.
Doing this is rather simple. The steps are:
main()
function, invoke BootstrapAddDefaultCamps.In more complex scenarios, software can also create more than one module. Each camp may be appended to list alib::CAMPS (see step 3). If these camps are following a dependency hierarchy, then the lowest (most independent) camps should be attached first. The target camp of the bootstrapping (step 4) is the latest camp added.
This recipe addresses the simple cases. When software chooses to hook itself into the bootstrap mechanism of ALib as shown above, the resources and configuration data (including for example INI-files) are shared between all modules and thus contain both, data of ALib and that of custom camps.
Often, this is not a desired scenario, because:
How these goals can be reached is explained in the next sections.
The default implementation of class ResourcePool used with non-customized bootstrapping is of type LocalResourcePool and this does not externalize the resource strings. To use either alternative, built-in type ConfigResourcePool or a custom implementation of the interface that attaches to a 3rd-party "resource pool backend", all that needs to be done is to create such instance and pass it to method Camp::BootstrapSetResourcePool of the last (!) camp in the global list alib::CAMPS.
This has to be done before the first invocation of function Bootstrap.
As documented with class Configuration, the type allows being equipped with custom mechanics for loading external data. To do so, the bootstrapping process has to be interrupted after the configuration is internally created. This is done by explicitly specifying BootstrapPhases::PrepareConfig with function Bootstrap.
Now, the configuration can be accessed with Camp::GetConfig and is manipulated as wanted. When done, bootstrapping is finalized by invoking Bootstrap a second time with argument BootstrapPhases::Final.
The schematic that shows both approaches at once, then looks as follows:
With the bootstrap process described so far,
is attached.
To also add a different configuration instances, method BootstrapSetConfig is given, just like the already introduced method Camp::BootstrapSetResourcePool.
Now, both methods may be called on arbitrary camps, before starting bootstrapping the corresponding phase! An object set this way will not be overwritten when the corresponding bootstrap phase distributes instances from higher- to lower-level camps. Moreover, if the algorithm finds an object already set, then the distribution loop continues with this object! This means that from there on, this instance is passed downward.
This allows a rather natural way to separate all camps into logical sets that share one resource pool and/or configuration.
The fact that method alib::Bootstrap(BootstrapPhases, camp::Camp*, int,int,TCompilationFlags).
is allowed to be called with setting parameter targetCamp to a "lower level" camp (one that is not the last of the list alib::CAMPS), allows bootstrapping lower-level camps separately from bootstrapping higher-level ones.
As discussed in previous section, such approach might be needed in case that custom camps are integrated into the ALib bootstrap process.
Let's consider the following sample:
main()
function, BootstrapAddDefaultCamps is invoked.The consequences from this approach are:
Alternatively the same can be reached with the following recipe:
We admit, that what was said above is a little complex. But once you understand why both recipes given above lead to the same result, you can tell that you understood ALib Bootstrapping!
With this toolset and the knowledge that:
... bootstrapping software becomes a very well defined and organized undertaking!
Customization of shutting down ALib should be a seldom need and works the same in principle. Everything is done in reverse order. Function alib::Shutdown(ShutdownPhases, camp::Camp*) has defaulted parameters targetPhase and targetCamp and if not specified, all camps in list alib::CAMPS are shutdown over both phases. For this, parameter targetCamp, this time defaults to the first camp in the list.
if targetPhase is specified as Announce then only the first phase is performed and custom actions might be taken before invoking the second phase ShutdownPhases::Destruct.
For simplicity, in contrast to bootstrapping the resource and configuration objects are destroyed right in the reverse loop of phase Destruct, in contrast to adding a post-process second loop and done with bootstrapping. Thus, a "lower" camp's resources and config objects might be already destroyed, at the moment the camp itself is destructed. Hence, phase Announce is the true last point of action in respect to an involvement of these objects.
Due to the facts that:
it is important that a compiled ALib library must be incompatible to compilation units that actually use the library. For example, the footprint (size) of library types might be different.
With C++, if incompatible compilation units are mixed, often no warning is issued (neither by the compiler nor the linker) and the execution of a process might lead to very strange behavior which is irritating and hardly to debug.
To detect incompatible compilation settings and library versions, three preprocessor symbols have been defined. Each symbol has a corresponding namespace variable compiled into the library. The symbols and variables are:
Preprocessor Symbol | Namespace Variable |
---|---|
ALIB_VERSION | alib::VERSION |
ALIB_REVISION | alib::REVISION |
ALIB_COMPILATION_FLAGS | alib::COMPILATION_FLAGS |
Along with that, namespace function alib::AssertALibVersionAndFlags is given, which expects comparison values for each of the namespace variables. The little trick here is, that the parameters are defaulted with the macros. Consequently, the parameters must not be given when the function is invoked.
Furthermore, the parameters are also silently (aka defaulted) added to overloaded functions alib::Bootstrap, which internally invokes AssertALibVersionAndFlags().
For this reason, nothing specific has to be done by a user of the library, as the checks are already performed with bootstrapping!