Apache Felix Bundle Cache
Introduction
The OSGi specification states that the framework must cache bundles and their run-time state, but it does not explicitly define how this should be done. As a result, each OSGi framework implementation is likely to cache bundles differently. This document describes in detail how Felix handles bundle caching by default and also describes the mechanisms to modify this default behavior.
Default Behavior
Felix creates a local cache directory, called .felix, in the home directory of the user; the location of the user's home directory varies depending on the operating system. Felix does not directly cache bundles into the local cache directory, since this would only allow one set of bundles to be installed for each user. Instead, Felix introduces the notion of a profile, which is an arbitrary name given to a set of installed bundles. When starting Felix from the command line, the user is prompted for a profile name. Felix creates a sub-directory in .felix named after the profile. All bundle information is then cached inside of the profile directory. The benefit of this approach is that the user is able to have different profiles for different purposes, such as ones for debugging, testing, or experimenting.
The structure of a profile directory is reasonably simple, it contains a directory for each bundle, where the directory name corresponds to the bundle identifier number. Each bundle directory contains a file for the bundle's location, identifier, start level, state, and a directory containing the bundle JAR file and any extracted embedded JAR files or native libraries if any exist. As an example, the profile directory for the simple.jar example bundle looks like this:
The above directory structure indicates that the simple.jar bundle is in the profile named "example" and that "4" is its bundle identifier. Additionally, besides the bundle JAR file, the bundle has one embedded JAR file and one native library. The naming convention is rather straightforward and consistent. All bundle directories will follow the naming pattern displayed above, except for the names of the embedded JAR files and native libraries. Embedded JAR files and native libraries use the names specified in the bundle manifest and may also include sub-directories. The naming convention for the directory containing the bundle JAR file, version0.0, requires further explanation.
The bundle JAR directory uses a numbering scheme to keep track of the number of times a bundle is updated and the number of times it is refreshed; the name of the directory maps like this: version<refresh-count>.<revision-count>. The reasoning behind this is tricky. It is necessary to keep track of the revision count because the OSGi specification requires when a bundle is updated that the update takes effect immediately. However, it also requires that old packages from older revisions of the updated bundle are kept available until a refresh of the framework is performed. As a result, it is possible for multiple revisions of a bundle JAR to be providing packages at a given time.
For example, if a bundle provides package foo and it is updated and now provides packages foo and bar, then after the update foo will be supplied from the older revision and bar will be supplied from the newer revision. This is possible for any number of updates, thus the bundle JAR directory must keep each revision around until a refresh is performed. Such "revision directories" are generally only run-time directories and are removed when the framework is shutdown or refreshed, they are not intended to exist for multiple sessions of execution.
To illustrate, upon initial installation, the bundle JAR file is placed into a revision directory named revision0.0. When an update is performed, the updated bundle JAR file is placed in a directory named revision0.1. If another update is performed without a refresh of the framework, the newer revision will be placed into a directory named revision0.2 and so on. When the framework is refreshed or shutdown, all revision directories are purged from the bundle cache and only the most recent revision directory is maintained.
Simply purging the old revision directories may appear adequate for refreshing the framework, but it is not due to how the JVM handles native libraries. When a native library is loaded it is associated with a specific class loader; no other class loader can load the same native library. The uniqueness of a native library is determined by its absolute path in the file system. Consequently, when the framework is refreshed, it is necessary to recreate the class loaders for all refreshed bundles. If a refreshed bundle has a native library, then this would result in an exception since the native library is still associated with the prior class loader; the refresh counter remedies this situation.
After purging all old revision directories, the current revision directory is renamed based on the current refresh count. By renaming the directory, it is possible to re-load the native library since its path in the file system has changed. The old class loaders and native libraries will eventually be garbage collected. The current refresh count is stored in a file, called refresh.counter, in the bundle's directory. To illustrate, if a bundle was updated and has two revision directories, revision0.0 and revision0.1, after a refresh the older revision directory will be deleted and the newest revision directory will be renamed to revision1.0. If the bundle is refresh again, its revision directory will become revision2.0 and so on. Note: Bundles may be refreshed when they are updated or when they depend on other bundles that have been updated; either way it is necessary to increment the refresh count and rename the revision directory during a refresh operation.
Configuring Default Behavior
It is possible to modify the default behavior of Felix' bundle cache by setting certain configuration properties; see the usage document
for information on how to set configuration properties for Felix. Felix' bundle cache recognizes the following configuration properties:
- felix.cache.bufsize - Sets the buffer size in bytes to be used by the cache; the default value is 4096. The integer value of this string provides control over the size of the internal buffer of the disk cache for performance reasons.
- felix.cache.dir - Sets the directory to be used by the cache as its cache directory. The cache directory is where all profile directories are stored and a profile directory is where a set of installed bundles are stored. By default, the cache directory is .felix in the user's home directory. If this property is specified, then its value will be used as the cache directory instead of .felix. This directory will be created if it does not exist.
- felix.cache.profile - Sets the profile name that will be used to create a profile directory inside of the cache directory. The created directory will contained all installed bundles associated with the profile.
- felix.cache.profiledir - Sets the directory to use as the profile directory for the bundle cache; by default the profile name is used to create a directory in the .felix cache directory. If this property is specified, then the cache directory and profile name properties are ignored. The specified value of this property is used directly as the directory to contain all cached bundles. If this property is set, it is not necessary to set the cache directory or profile name properties. This directory will be created if it does not exist.
To be clear, the location of the profile directory where all installed bundles are cached is computed like this: ${felix.cache.dir}+${felix.cache.profile}, where felix.cache.dir has the default value ~/.felix. There is no default value for the profile name, so at a minimum the profile name property (i.e., felix.cache.profile) must be specified. However, if felix.cache.profiledir is specified then the profile directory is not calculated at all and this specified value is directly used as the profile directory.