OSGi, Dynamics and Eclipse
I often hear this question…
“Why does Eclipse prompt me to restart if it’s a dynamic OSGi-based application?”
As a user, you’re presented with a few choices: no, apply changes (I’m feeling lucky) or yes (please restart).
To answer this question, let me tell you a story. Back in the day, Eclipse had its own module system… things that you see these days in the MANIFEST.MF file were all in the plugin.xml file:
These old plug-ins were lazy, but not dynamic in the OSGi sense. New plug-ins could come, but nothing would ever go away. In the the PDE team, we like to use the analogy of a play. So imagine a play where all the actors come out for their parts… but they never leave the stage. With the adoption of OSGi, Eclipse was able to go fully dynamic and actors can now leave the stage (and come back again!).
However, Eclipse still had to support these old plug-ins even though we moved our runtime to OSGi. How we did this is another story, but it involves translating the old plugin.xml files to OSGi manifest files on the fly.
Since we supported the old plug-in model, we had to prompt for restart. I consider this the historical reason of why the restart dialog exists. Now let me tell you the more serious reason.
The practical reason is that most people don’t code for dynamics still. Even though the Eclipse RCP stack is fully dynamic aware there may be pieces out there that aren’t. Whether it’s Eclipse code or someone else’s code, it doesn’t matter. It just takes one bundle that isn’t dynamic aware to potentially mess things up. To illustrate what it means to be dynamic aware or not, let’s look at some real code from Eclipse.
/**
* Manages argument selectors (choosers) for string variables.
*
* @since 3.0
*/
public class StringVariablePresentationManager {
/**
* String variable presentation extension point identifier
* (value "stringVariablePresentations").
*
* @since 3.0
*/
public static final String EXTENSION_POINT_STRING_VARIABLE_PRESENTATIONS =
"stringVariablePresentations"; //$NON-NLS-1$
// default manager
private static StringVariablePresentationManager fgManager;
// extension point attributes
public static final String ATTR_NAME = "variableName"; //$NON-NLS-1$
public static final String ATTR_ARGUMENT_SELECTOR = "argumentSelector"; //$NON-NLS-1$
/**
* Table of configuration elements for variable presentations,
* keyed by variable name.
*/
private Map fConfigurations;
/**
* Returns the singleton string variable presentation manager.
*
* @return the singleton string variable presentation manager
*/
public static StringVariablePresentationManager getDefault() {
if (fgManager == null) {
fgManager = new StringVariablePresentationManager();
}
return fgManager;
}
/**
* Returns an argument selector contributed for the given
* variable, or null if none.
*
* @param variable string substitution variable
* @return argument selector or null
*/
public IArgumentSelector getArgumentSelector(IStringVariable variable) {
IConfigurationElement element =
(IConfigurationElement) fConfigurations.get(variable.getName());
if (element != null) {
try {
return (IArgumentSelector)element.createExecutableExtension(
ATTR_ARGUMENT_SELECTOR);
} catch (CoreException e) {
DebugUIPlugin.log(e);
}
}
return null;
}
/**
* Constructs the manager, loading extensions.
*/
private StringVariablePresentationManager() {
initialize();
}
/**
* Load extensions
*/
private void initialize() {
fConfigurations = new HashMap();
IExtensionPoint point= Platform.getExtensionRegistry().getExtensionPoint(
DebugUIPlugin.getUniqueIdentifier(),
EXTENSION_POINT_STRING_VARIABLE_PRESENTATIONS);
IConfigurationElement elements[]= point.getConfigurationElements();
for (int i = 0; i < elements.length; i++) {
IConfigurationElement element = elements[i];
String name= element.getAttribute(ATTR_NAME);
if (name == null) {
continue;
}
fConfigurations.put(name, element);
}
}
}
Can you tell what’s potentially wrong with the above code?
Extensions are only read when the object is initialized. The code above doesn’t handle extensions in a dynamic fashion so if new bundles come and provide more string variables, the code above won’t see them. Now imagine this pattern applied to some user interface code where you could contribute menu entries. If the manager was already initialized and your bundle providing entries came along at a later time, you wouldn’t see your menu entry contribution until the system restarted.
How do we make this code dynamic aware? Well, let’s look at another real example from Eclipse:
public final class KeywordRegistry implements IExtensionChangeHandler {
private static final String ATT_ID = "id"; //$NON-NLS-1$
private static final String ATT_LABEL = "label"; //$NON-NLS-1$
private static KeywordRegistry instance;
private static final String TAG_KEYWORD = "keyword"; //$NON-NLS-1$
/**
* Return the singleton instance of the KeywordRegistry.
*
* @return the singleton registry
*/
public static KeywordRegistry getInstance() {
if (instance == null) {
instance = new KeywordRegistry();
}
return instance;
}
/**
* Map of id->labels.
*/
private Map internalKeywordMap = new HashMap();
/**
* Private constructor.
*/
private KeywordRegistry() {
IExtensionTracker tracker = PlatformUI.getWorkbench().getExtensionTracker();
tracker.registerHandler(this,
ExtensionTracker.createExtensionPointFilter(getExtensionPointFilter()));
IExtension[] extensions = getExtensionPointFilter().getExtensions();
for (int i = 0; i < extensions.length; i++) {
addExtension(PlatformUI.getWorkbench().getExtensionTracker(),
extensions[i]);
}
}
public void addExtension(IExtensionTracker tracker, IExtension extension) {
IConfigurationElement[] elements = extension.getConfigurationElements();
for (int i = 0; i < elements.length; i++) {
if (elements[i].getName().equals(TAG_KEYWORD)) {
String name = elements[i].getAttribute(ATT_LABEL);
String id = elements[i].getAttribute(ATT_ID);
internalKeywordMap.put(id, name);
PlatformUI.getWorkbench().getExtensionTracker().registerObject(
extension, id, IExtensionTracker.REF_WEAK);
}
}
}
private IExtensionPoint getExtensionPointFilter() {
return Platform.getExtensionRegistry().getExtensionPoint(
PlatformUI.PLUGIN_ID, IWorkbenchRegistryConstants.PL_KEYWORDS);
}
/**
* Return the label associated with the given keyword.
*
* @param id the keyword id
* @return the label or null
*/
public String getKeywordLabel(String id) {
return (String) internalKeywordMap.get(id);
}
public void removeExtension(IExtension extension, Object[] objects) {
for (int i = 0; i < objects.length; i++) {
if (objects[i] instanceof String) {
internalKeywordMap.remove(objects[i]);
}
}
}
}
The above code is dynamic aware via IExtensionChangeHandler
, notice the addExtension(...)
and removeExtension(...)
methods. These methods handle extensions coming and going which we would expect in a fully dynamic system.
Note, this dynamics problem applies to any OSGi system… not just Eclipse. Any OSGi system where your bundles come and go that may or may not be dynamic aware can cause problems.
How do we solve this problem? Well, the first step would be to know whether a bundle that we’re going to install and activate is dynamic aware. At the moment we have no idea. I could imagine a solution where there could be a new header: Dynamic-Aware: true
This header would give OSGi provisioning systems some metadata to make a intelligent decision whether a restart would be required or not. For now, in my opinion, the safest decision is always to prompt to restart and leave the decision in the hands of the user. In the future, I hope that we can shed this with improved education and provisioning metadata.
In summary, the problem of why Eclipse prompts you to restart is two-fold: there’s a historical reason due to the old plug-in model but there’s also the other problem of educating people how to write dynamic aware bundles. In the end, it only takes one bad bundle to ruin the dynamics of your OSGi system.