Language

The Free and Open Productivity Suite
Released: Apache OpenOffice 4.1.15

Library Unloading API

OpenOffice

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:

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.

sal_Bool SAL_CALL * component_canUnload( TimeValue* pTime);
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

sal_Bool SAL_CALL rtl_registerModuleForUnloading( oslModule module);
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:

  1. load a library (osl_loadModule)
  2. get the component_getFactory function and get a factory
  3. 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

void SAL_CALL rtl_unregisterModuleForUnloading( oslModule module);
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:

typedef void (SAL_CALL *rtl_unloadingListenerFunc)( void* id);
Parameter id - The value that has been passed as second argument to rtl_addUnloadingListener

To unregister a listener call

void SAL_CALL rtl_removeUnloadingListener( sal_Int32 cookie );
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

Unloading

To trigger the unloading of modules call the function

void SAL_CALL rtl_unloadUnusedModules( TimeValue* libUnused);
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:

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.

Apache Software Foundation

Copyright & License | Privacy | Contact Us | Donate | Thanks

Apache, OpenOffice, OpenOffice.org and the seagull logo are registered trademarks of The Apache Software Foundation. The Apache feather logo is a trademark of The Apache Software Foundation. Other names appearing on the site may be trademarks of their respective owners.