Lazy Start Bundles
- 1 Overview
- 2 Terminology
- 3 Problem Description
- 4 Requirements
- 5 Technical Solution
- 5.1 The Lazy Start Policy
- 5.2 The Bundle-StartPolicy Header
- 5.3 Security
- 5.4 Issues
This design defines a mechanism for activating bundles upon the first class or resource request from them. This allows a system to activate bundles lazily the first time they are needed. Using this model the Framework can be started with as few active bundles as possible and only activate other bundles as new classes are loaded.
For the purpose of this design, lazy start is a life cycle policy that mandates a bundle MUST be activated upon the first request to load a class or resource from that bundle. Sometimes this is referred to as auto starting because the bundle is automatically started upon first class load request. This design will always use the term lazy instead of auto because auto starting can imply that a bundle is always automatically started every time the framework is launched.
Lazy Start Bundle
For the purpose of this design, a lazy start bundle is a bundle that has declared the lazy start life cycle policy.
Local Class Store
For the purpose of this design, a local class store is all classes contained in a bundle’s classpath, including classes contained in the classpaths of attached fragment bundles.
The Life Cycle Layer provides an API to control the security and life cycle operations of bundles. This layer gives management agents the ability to install, uninstall, update, start and stop a bundle. In a typical environment a management agent will start every bundle in the framework that it thinks will be needed while the framework is running. Most bundles need to be activated for the following reasons.
- Run some initialization code (open a port, start a thread etc.)
- Obtain the BundleContext to participate in the service layer or register listeners with the Framework.
In a Framework where 1000s of bundles are installed it is not reasonable to activate every bundle upon framework launch. In a user driven application it is not possible for a management agent to know what bundles should be activated ahead of time. For example, when a user selects a menu item in a GUI it could start a feature of the application. This feature could be implemented by a bundle that should be activated once the menu item is selected by the user.
- Bundles MUST be allowed to specify a lazy start life cycle policy.
- Backwards compatibility MUST be maintained.
- There SHOULD NOT be any undesirable side-effects of lazy starting a bundle. Unfortunately there may be undesirable side-effects in some circumstances. See section 5.4
- When a bundle is activated using the lazy start policy then it MUST not be marked for persistent start. In this case the bundle MUST not be activated when the Framework is relaunched.
- When a bundle is explicitly started using the Bundle.start method then it MUST be marked for persistent start even if the bundle specifies a lazy start life cycle policy. In this case the bundle MUST be activated when the Framework is relaunched as long as it’s start-level is met.
- During the Framework shutdown process; bundles which were activated with the lazy start policy must not be reactivated as other bundles are stopping.
- During the Framework shutdown process; bundles which were activated with the lazy start policy SHOULD be shutdown in reverse dependency order. This SHOULD help prevent bundles from loading classes from already stopped lazy start bundles. In the case of bundle cycles the stop order is non-deterministic.
- During the Framework shutdown process; if an attempt to load a class from an already stopped bundle occurs and that bundle is lazy started, then a ClassNotFoundException must be thrown with an appropriate error message. TODO is a CNFE really what we want? (this is what Equinox does) Another option may be to publish a FrameworkEvent of type ERROR and let the class load.
- Lazy start bundles MUST be able to participate in the service layer before the first class is loaded from the lazy start bundle. This may require that the BundleContext for a lazy start bundle be available to component systems (e.g. Declarative Services, Spring-OSGi etc) before the bundle enters the ACTIVE state.
- The design of the lazy start policy MUST consider secure operations with respect to activating a bundle.
The Lazy Start Policy
The lazy start policy activates bundles when the first class load request is made from a bundle’s local class store. This policy is implemented by the Framework instead of in a management agent because only the Framework knows when the first class load request is being made.
Lazy Start Bundle Life Cycle
A lazy start bundle has the following life cycle state transitions
- When a lazy start bundle’s state changes from INSTALLED to RESOLVED then it must transition into the STARTING state. A BundleEvent of type STARTING must be fired for the lazy start bundle. While in this state the method Bundle.getBundleContext (proposed method) must return the bundle’s BundleContext.
- If a lazy start bundle is stopped (by calling Bundle.stop) while in the STARTING state then the bundle state must transition to the RESOLVED state. A BundleEvent of type STOPPING must be fired, then the bundle’s state must be set to RESOLVED, finally a BundleEvent of type STOPPED must be fired for the bundle. Upon a Framework re-launch the lazy start bundle must be allowed to enter the STARTING state again if it is still RESOLVED.
- If a class load request is made from the local class store of a lazy start bundle while in the STARTING state then the bundle must be activated before allowing the class to load.
- If the lazy start bundle fails to activate then a BundleEvent of type STOPPING must be fired, then the bundle’s state must be set to RESOLVED, finally a BundleEvent of type STOPPED must be fired for the bundle, finally a ClassNotFoundException must be thrown. Need to decide if we should ALWAYS throw ClassNotFoundExceptions until the bundle is refreshed to start the cycle over again or let the activation attempt happen again the next time a class load request is made. Currently Equinox lets the activation attempt happen again and again. But I think it is cleaner to always throw the CNFE.
- If the lazy start bundle is activated successfully then the bundle’s state must be set to ACTIVE followed by a firing a BundleEvent of type STARTED, finally the class load request is allowed to continue. The bundle must not be persistently marked for activation when the Framework is re-launched.
- If a class load request is made from the local class store of a lazy start bundle while in the RESOLVED state then a ClassNotFoundException must be thrown. This implies that a class may not be loaded from a lazy start bundle if the start-level for the bundle is not met or if it failed to activate in step 4. Again we need to decide if this is OK. Equinox LETS classes load from bundles that are NOT at the proper start-level. This does not seem correct or consistent
- If the Bundle.start method is called on a lazy start bundle, then it MUST be persistently marked for activation when the Framework is re-launched.
When the Framework launches the Framework’s start-level is incremented to the initial start-level. For each start-level if a lazy start bundle’s start-level is met AND it is RESOLVED then if the bundle is persistently marked for start then it transitions to the ACTIVE state; otherwise it transitions to the STARTING state.
When the Framework is shutdown the Framework’s start-level is decremented to zero. For each start-level the following steps need to be taken:
- If no lazy start bundles exist with the start-level then the bundles with the start-level are shutdown normally.
- If at least one lazy start bundle exists with the start-level then all the bundles with the same start-level are sorted according to dependency order. Then the bundles are shutdown ordered by dependencies such that all bundles which depend on a given bundle are shutdown before the given bundle. In case of cycles the bundles shutdown order is not specified.
This helps prevent class load requests from lazy start bundles that have been stopped during the shutdown process.
Need more information to be filled in here. In Equinox the lazy-start bundles are shutdown in dependency order just before the start-level of the framework starts decrementing. This causes problems if non-lazy start bundles attempt to load classes from an already stopped lazy start bundle while the non-lazy start bundles are stopping.
It is probably more consistent to shutdown the lazy start bundles with each start-level, but the lazy-start bundles within the same start-level should be shutdown in dependency order. I think we should also shutdown other non-lazy start bundles in dependency order also if there is at least one lazy start bundle at the same start-level.
The Bundle-StartPolicy Header
The Bundle-StartPolicy header is introduced to allow a bundle to specify a bundle start policy. For the purpose of this design only the lazy start policy is architected. In the future other policies could be architected.
Bundle-StartPolicy ::= policy-type (';' attribute)*
policy-type ::= token
attribute ::= name '=' value
The only defined policy type is lazy. The lazy start policy has the following architected attributes:
- include – a string with no default value. This attribute has the following syntax:
include ::= ‘”’ package-name ( ‘,’ package-name )* ‘”’
A list of package names that require the lazy start policy. If not specified then all packages require the lazy start policy
- exclude – a string with no default value. This attribute has the following syntax:
exclude ::= ‘”’ package-name ( ‘,’ package-name )* ‘”’
A list of package names that do no use the lazy start policy. If not specified then all packages require the lazy start policy
Equinox defines the Eclipse-LazyStart header. An alternative would be to define a Bundle-LazyStart header with the following syntax:
Bundle-LazyStart ::= enabled (’;’ attribute)*
enabled ::= ‘true’ | ‘false’
The Bundle-LazyStart header has the following architected attributes
- exceptions – a string with no default value. This attribute has the following syntax:
exceptions ::= ‘”’ package-name ( ‘,’ package-name )* ‘”’
If the lazy start policy is enabled then the exceptions attribute defines a list of package names that do not use the lazy start policy. If not enabled then the exceptions attribute defines a list of package names that do use the lazy start policy.
If new start policies are needed then a new header would be needed to define the new policies.
The need for defining specific packages (with include, exclude and exceptions attributes) is questionable. Very few bundles in Equinox use such a policy. An alternative would be to not define any architected attributes for the lazy start policy. In this case all packages in a bundle would participate in the lazy start policy.
The Bundle.loadClass method may result in activating a bundle. The action AdminPermission.CLASS will allow the activation of lazy start bundles when Bundle.loadClass is called.
During the execution of java code additional classes get loaded, this will result in bundles being activated. Loading classes while executing java code must not require a bundle to have the AdminPermission.EXECUTE action.
If bundle classloaders are exposed (with a Bundle.getClassLoader method) then the rights to activate a bundle will be exposed with the classloader.
Class Circularity Errors
Certain scenarios will cause Class Circularity Errors to occur. This can occur when the first class load request for a class “Foo” is made from a bundle. This will result in the bundle being activated on the same thread which made the class load request for “Foo”. If during the execution of the BundleActivator the class “Foo” is needed then another request to load “Foo” will occur again using the same thread. This will result in a circularity error.
A workaround could be to load the first class before activating the bundle, but this has other issues if a static initializer expects the bundle to be activated. We have tried this in Equinox and have run into other issues that ended up being worse that the circularity errors.
Another possible solution could be to catch the circularity error while trying to activate the bundle. If the error is caught then allow the class to load and attempt to activate the bundle again after the class has been defined and before the class is returned from the bundle’s classloader. In this case we should publish a FrameworkEvent.ERROR to indicate what went wrong.
Activation Dead Lock
Certain scenarios can cause threads to hang while activating a lazy start bundle. This can occur if a lazy start bundle starts additional threads while activating. If the bundle activator loads additional classes after it has started the threads then more than one thread could be attempting to load classes from the lazy start bundle before it has finished activating. This can cause the threads to dead lock while waiting to start the bundle. Eventually they will timeout while waiting for the other thread to complete the bundle activation. Even using small timeouts will result in severe performance issues.
A way around this issue is to only start the threads at the very end of the lazy start bundle’s BundleActivator.start method. This will allow the bundle to start with out causing additional classes to load after the threads have been started.