Library Unloading API |
![]() |
The API enables an effective way of unloading libraries in a centralized way. The mechanism ensures that libraries, which are being used are not unloaded. This prevents crashes, if someone tries to execute library code after the library has been unloaded. The unloading mechanism currently only works with libraries which contain UNO services. A library cannot be unloaded if one of the following conditions apply:
- An instance is still referenced
- A module has been loaded without registering it
- The library contains a one-instance-service which has been instantiated
- A service instance has been added to an UNO context
- A factory has been registered with the service manager ( XSet::insert)
Risks
The API is not entirely thread safe. Therefore, using this API might cause an
application to crash. This problem originates in the implementation of
component_canUnload
. The function returns sal_True
if the module can be unloaded,
which suggests that all component instances have died. But that makes it
necessary to recognize when an instance is about to destroy itself. This can be
easily achieved by employing a module wide counter, whose value represents the
number of running instances. A C++ component would increment the counter in its
constructor and decrement it in its destructor. A thread which is running a
destructor might be suspended after decreasing the counter. If now, the counter's
value is null, the module will be unloaded by the unloading mechanism when
someone called rtl_unloadUnusedLibraries
. Then, when the suspended thread is
being scheduled again, it tries to run code which does not exist any longer, and
the application crashes. This is obviously a synchronization problem. But
synchronizing every call that could destroy a component, that is, synchronizing
XInterface::release
, would entail a big performance loss. To solve this problem
one needs a mechanism that notices when a thread is out of the component's
library. There are ways of achieving this, but they all require
additional code on the client's side. Since that complicates the use of UNO and
is error prone as well, it would be desirable to encapsulate that mechanism
within proxy instances. This way, a client would interact with a proxy rather than
with the actual component. The proxy solution, however, takes up a lot of
performance and memory.
Library Requirements
A library which supports unloading has to implement and
export a function called component_canUnload
.
Parameter | pTime - time since the module has not been used |
---|---|
Return | sal_True - module can be unloaded, sal_False otherwise |
If the function returns sal_True
then the module can be safely unloaded. That
is the case when there are no external references to code within the library. In
case a module houses UNO components, then the function must return sal_False
after the first factory has been handed out. The function then continues to
return sal_False
as long as there is at least one object (factory or service
instance) which originated from the module.
Libraries which not only contain UNO components (or none at all), have to provide a means to control
whether they can be unloaded or not; however, there is no concept of this, as yet.
The argument pTime
is an optional out-parameter and can be NULL. If the return value is
sal_True
then pTime
reflects a point in time, since when the module could have
been unloaded. Since that time, the function would have continually returned
sal_True
up to the present. The value of pTime
is important for the decision as
to whether a module will be unloaded. When someone initiates the unloading of modules by
calling rtl_unloadUnusedModules
, then the caller can specify a time span to the
effect that only those modules are unloaded which have not been used at least for that
amount of time. If component_canUnload
does not fill in pTime
and returns sal_True
, then the module is
unloaded immediately.
component_canUnload
is implicitly called by rtl_unloadUnusedModules
. There is
no need to call the function directly.
Registering Modules
By registering a module, one declares that a module supports the unloading mechanism. One registers a module by calling
Parameter | module - a module handle as is obtained by osl_loadModule |
---|---|
Return | sal_True - the module could be registered for unloading, sal_False otherwise |
A module can only be unloaded from memory when it has been registered as many
times as it has been loaded. The reason is that a library can be
"loaded" several times by osl_loadModule
within the same process. The
function will then return the same module handle because the library will
effectively only be loaded once. To remove the library from memory it is
necessary to call osl_unloadModule
as often as osl_loadModule
was called. The
function rtl_unloadUnusedModules
calls osl_unloadModule
for a module as many
times as it was registered. If, for example, a module has been registered one
time less than osl_loadModule
has been called and the module can be unloaded,
then it needs a call to rtl_unloadUnusedModules
, and an explicit call to
osl_unloadModule
to remove the module from memory.
A module must be registered every time it has been loaded; otherwise, the unloading mechanism is not effective.
Before a module is registered, one has to make sure that the module is in a
state that prevents it from being unloaded. In other words,
component_canUnload
must return sal_False.
Assuming that component_canUnload
returns sal_True
and it is registered, regardless, then a call to rtl_unloadUnusedModules
causes
the module to be unloaded. This unloading can be set off by a different thread,
and the thread which registered the module is "unaware" of this. Then,
when the first thread tries to obtain a factory or calls another function in the
module, the application will crash -- because the module has already been unloaded.
Therefore, one has to ensure that the module cannot be unloaded before it is
registered. This is done by simply obtaining a factory from the module. As long
as a factory or some other object, which has been created by the factory, is
alive, the component_canUnload
function will return sal_False
.
Loading and registering have to be done in this order:
- load a library (
osl_loadModule
) - get the
component_getFactory
function and get a factory - register the module (
rtl_registerModuleForUnloading
Usually, the service manager is used to obtain an instance of a service. The service manager registers all modules which support the unloading mechanism. When the service manager is used to get service instances, then one does not have to bother about registering.
The function
Parameter | module - a module handle as is obtained by osl_loadModule |
---|
revokes the registration of a module. By calling the function for a previously registered module, one prevents the module from being unloaded by this unloading mechanism; however, in order to completely unregister the module it is still necessary to call the function as often as the module has been registered.
rtl_unloadUnusedModules
unregisters the modules which it unloads;
therefore, there is no need to call this function unless one means to prevent the
unloading of a module.
Notification Mechanism
The notification mechanism is provided for clients which need to do clean up, such as, releasing cached references in order to allow modules to be unloaded. As long as someone holds a reference to an object whose housing module supports unloading, the module cannot be unloaded.
Because of the inherent danger of crashing the application by using this API, all instances which control threads should be registered listeners. On notification, they have to ensure that their threads assume a safe state, that is, they run outside of modules which could be unloaded and do not jump back into module code as a result of a finished function call. In other words, there must not be an address of the module on the thread's stack.
Since current operating systems lack APIs in respect to controlling the position of threads within libraries, it would be a major effort to comply with that recommendation. The best and most efficient way of handling the unloading scenario is to let all threads, except for the main thread, die in case of a notification.
Listeners ( the callback functions) must be unregistered before the listener
code becomes invalid. That is, if a module contains listener code, namely
callback functions of type rtl_unloadingListenerFunc
, then those functions must
not be registered when component_canUnload
returns sal_True
.
Listeners are registered with the following function:
sal_Int32 SAL_CALL rtl_addUnloadingListener( rtl_unloadingListenerFunc callback, void* this);
Parameter | callback - a function that is called to notify listeners. |
---|---|
this - a value to distinguish different listener instances | |
Return | identifier which is used in rtl_removeUnloadingListener |
callback is a function which is called when the unloading procedure has
been initiated by a call to rtl_unloadUnusedLibraries
. The second argument is
used to distinguish between different listener instances and may be NULL. It will
be passed as an argument when the callback function is being called. The return
value identifies the registered listener and will be used for removing the
listener later on. If the same listener is added more then once, then every
registration is treated as if made for a
different listener. That is, a different cookie is returned and the callback
function will be called as many times as it has been registered.
The callback function is defined as follows:
Parameter | id - The value that has been passed as second argument to rtl_addUnloadingListener |
---|
To unregister a listener call
Parameter | cookie is an identifier as returned by rtl_addUnloadingListener function. |
---|
The callback functions can reside in modules which support the unloading mechanism; therefore, a listener must revoke itself as listener, before it becomes invalid, and the module can be unloaded.
The service manager as obtained by createRegistryServiceFactory
(cppuhelper/servicefactory.hxx), createServiceFactory
(cppuhelper/servicefactory.hxx), bootstrap_initialComponentContext
(cppuhelper/bootstrap.hxx), and defaultBootstrap_initialComponentContext
(cppuhelper/bootstrap.hxx), registers itself as unloading listener. On being
notified, it releases references to factories which
- have not been registered with the manager by calling
XSet::insert
- do not implement
XUnloadingPreference
- implement
XUnloadingPreference
andXUnloadingPreference::releaseOnNotification
returnstrue
Unloading
To trigger the unloading of modules call the function
Parameter | libUnused - span of time that a module must be unused to be unloaded. The argument is optional. |
---|
The function notifies the unloading listeners in order to give them a chance to do
cleanup and get their threads in a safe state. Then, all registered modules are asked if they can be unloaded.
That is, the function calls component_canUnload
on every registered module.
If sal_True
is returned then osl_unloadModule
is called for
each module as often as it was registered.
A call to osl_unloadModule
does not
guarantee that the module is unloaded even if its component_canUnload
function
returns sal_True
.
The optional in-parameter libUnused
specifies a period of time, for which a
library must be unused, in order to qualify for being unloaded. By using this
argument, one can counter the multithreading problem as described above.
It is the responsibility of the user of this function to provide a timespan
long enough to ensure that all threads are out of modules ( see
component_canUnload
).
The service managers which have been created by functions such as createRegistryServiceFactory
(declared in cppuhelper/servicefactory.hxx) are registered listeners and
release the references to factories on notification. Some factories are treated
differently, see paragraph about one-instance-services.
Default Factories
Default factories can be obtained by means of helper functions, such as
createSingleComponentFactory
. They keep a pointer to a function within a module that
creates a service instance; therefore, a library must not be unloaded as long as
there are default factories around. This is achieved by the factories which
increase the module counter on instantiation. When a factory is about to destroy
itself, then it decreases the counter. In order to realize this new functionality,
the relevant creator functions now have another parameter. These are the
new signatures ( for complete declarations refer to cppuhelper/factory.hxx).
Reference< XSingleComponentFactory > SAL_CALL createSingleComponentFactory( ComponentFactoryFunc fptr, OUString const & rImplementationName, Sequence< OUString > const & rServiceNames, rtl_ModuleCount * pModCount = 0 ) SAL_THROW( () ); Reference< XSingleServiceFactory> SAL_CALL createSingleFactory( const Reference< XMultiServiceFactory > & rServiceManager, const OUString & rImplementationName, ComponentInstantiation pCreateFunction, const Sequence< OUString > & rServiceNames, rtl_ModuleCount * pModCount = 0 ) SAL_THROW( () ); Reference< XSingleServiceFactory > SAL_CALL createOneInstanceFactory( const Reference< XMultiServiceFactory > & rServiceManager, const OUString & rComponentName, ComponentInstantiation pCreateFunction, const Sequence< OUString > & rServiceNames, rtl_ModuleCount * pModCount = 0 ) SAL_THROW( () );
rtl_ModuleCount
is declared in sal/rtl. See paragraph Implementation below for further
information.
Custom Factories
Custom factories have to be implemented in such a way that the component_canUnload
function of the module containing the service returns sal_False
, as long as a
factory exists. Because programmers have full control over the factory
implementation they can choose whatever mechanism they think fit.
One-Instance-Services
A factory of a one-instance-service (OIS) always returns the same instance. So far, the service manager caches references to the factories with the effect that an instance lives as least as long as the service manager; the service manager keeps the factory alive, which in turn keeps the service instance alive. That fact has been taken advantage of by some developers to implement services whose instances must not die; otherwise, important data would be lost. Now with the advent of the unloading mechanism, we face the problem that factories do not tell what kind of service they provide. But that is important for the service manager to decide whether it releases a factory when being notified during the unloading process. The service manager must not release the OIS; otherwise, the following could happen:
- An OIS instance is being referenced by its factory and other clients.
- The service manager gets a notification and releases the factories.
- Now, the factory could die and release the OIS, or the factory lives on because it is kept by other clients or the OIS itself.
- Either way, when the service manager is asked to create the service again, then it will create a new instance and there are in fact two instances of an OIS around.
- Consider that the former instance may contain crucial data, but a client cannot get to them via the service manager anymore.
The service manager currently keeps hard references to factories. To relieve
this problem with OISs the manager could keep weak references, but then the OIS
instance must keep a reference to its factory so that the weak reference, as kept
by the service manager, remains valid. That was not necessary, so far, and
developers were negligent, in this regard, with the result that a lot of OISs
needed to be changed. There is also a design flaw with the default OIS
factory ( createOneInstanceFactory
), namely, the factory keeps a hard reference
to the OIS instance. If the instance is properly implemented and keeps a
reference to the factory, then there is a ring reference which causes a memory
leak. That, actually, calls for a new type of default factory which keeps a weak
reference to the service OIS instance.
But even then, there is a problem that some OISs rely on the fact that they stay alive once they have been created. An that is not achieved with the idea as outlined above.
To prevent the factory of an OIS from being released by the service manager, it implements a new interface.
module com { module sun { module star { module uno { interface XUnloadingPreference: com::sun::star::uno::XInterface { boolean releaseOnNotification(); }; };};};};
The interface contains a function
releaseOnNotification
, whose return value indicates whether a
notification listener should release its references to the implementing object,
in case of a notification. A listener should always ask objects for this
interface, be it factories or other objects. If objects do not implement that
interface then the listener should release references to those objects as is the
case when XUnloadingPreference::releaseOnNotification
returns true.
This interface will be implemented by the default factories. releaseOnNotification will return false when called on the default one-instance-factory. The table shows what the other implementations return:
Function that creates the factory | Return value of XUnloadingPreference::releaseOnNotification |
---|---|
createSingleComponentFactory | sal_True |
createSingleFactory | sal_True |
createOneInstanceFactory | sal_False |
createFactoryProxy | Delegates call to the wrapped factory if it implements XUnloadingPreference; otherwise, sal_True is returned. |
createSingleRegistryFactory | sal_True as long as the actual factory has not been loaded; otherwise, the call is delegated to the loaded factory. If that factory does not implement XUnloadingPreference, then sal_True is returned. |
createOneInstanceRegistryFactory | sal_True as long as the actual factory has not been loaded. When the factory has been loaded and has created an instance, then the return value is sal_False; otherwise, sal_True. |
The service manager releases references to factories, even if they do not implement this interface. This makes it necessary that custom factories of one-instance-services need to implement this interface in order to guarantee proper behavior of the service.
Implementation
To facilitate the implementation of modules and default factories which support the unloading mechanism, we will introduce new types.
// rtl/unload.h typedef struct _rtl_ModuleCount { void ( SAL_CALL * acquire ) ( struct _rtl_ModuleCount * that ); void ( SAL_CALL * release ) ( struct _rtl_ModuleCount * that ); } rtl_ModuleCount;
Currently, this type is only used with the creator functions of default
factories. If default factories are used, then the module should have one
instance of rtl_ModuleCount
that is initialized, while the module is being
loaded. The UDK will provide helper types and default function
implementations.
// rtl/unload.h typedef struct _rtl_StandardModuleCount { rtl_ModuleCount modCount; sal_Bool ( *canUnload ) ( struct _rtl_StandardModuleCount* this, TimeValue* libUnused); oslInterlockedCount counter; TimeValue unusedSince; } rtl_StandardModuleCount;
#define MODULE_COUNT_INIT \
{ {rtl_moduleCount_acquire,rtl_moduleCount_release}, \
rtl_moduleCount_canUnload, 0, {0, 0} };
rtl_moduleCount_acquire
, rtl_moduleCount_release
, and
rtl_moduleCount_canUnload
are default implementations.
To support unloading, one has to provide this code in a module.
//one global instance of rtl_StandardModuleCount rtl_StandardModuleCount g_moduleCount= MODULE_COUNT_INIT; sal_Bool SAL_CALL component_canUnload( TimeValue* libUnused) { return g_moduleCount.canUnload( &g_moduleCount, libUnused); } // Example class for a service implementation MyService::MyService() { g_moduleCount.modCnt.acquire( &g_moduleCount.modCnt); ... } MyService::~MyService() { ... g_moduleCount.modCnt.release( &g_moduleCount.modCnt); } ...
Author:
Joachim Lingner. ($Date: 2004/12/15 12:49:51 $) Copyright 2001 Sun Microsystems, Inc., 901 San Antonio Road, Palo Alto, CA 94303 USA. |