Chapter 22. Dynamic Plug-ins

Applications are often dynamic. New functions are added, old functions are removed, but the system keeps running. Equinox and Eclipse enable this kind of behavior and the RCP base plug-ins tolerate it, but the ability does not come free—you must follow certain practices to make the most of these scenarios.

This chapter discusses the unique challenges presented to plug-in writers as they attempt to handle the comings and goings of plug-ins in the environment. In particular, the following topics are covered:

• The notions of dynamic awareness and dynamic enablement

• Hyperbola as an example of an application requiring dynamic facilities

• Common dynamic plug-in scenarios

• Coding practices, mechanisms, and design approaches for handling these scenarios

22.1 Making Hyperbola Dynamic

The main goal here is to make Hyperbola react correctly when plug-ins are added or removed. The first step is to understand how the Hyperbola plug-ins are connected to each other and to plug-ins in upper layers. For example, imagine a scenario where Hyperbola had XMPP MUC capabilities provisioned on the fly or some other scenario like videoconferencing, file transfer, or any other XMPP extension.

Beyond the details of installing the function there are the issues related to getting Hyperbola to notice the arrival of new functions and the management of interconnections when plug-ins are removed from the system—these are the topics addressed in this chapter.

Figure 22-1 gives a rough outline of the Hyperbola plug-ins involved in MUC support and shows how they would relate to each other.

Figure 22-1 Hyperbola plug-in structure

image

First, let’s look at the comings and goings of content handler plug-ins. The MUC support brings along the following elements that need to be linked into Hyperbola:

Providers—These are the packet handlers that might be plugged into Hyperbola via an extension point. They are in turn supplied to Smack using Hyperbola’s provider manager.

Listeners—The MUC infrastructure needs to register various listeners, both at the UI level for selection events and such and at the Smack level to monitor message traffic, participant presence, and so on.

UI parts—The MUC UI needs to be displayed and driven by the user. This requires action sets, views, and editors contributed via extension points.

In this scenario the MUC plug-ins register various listeners, and Eclipse or Hyperbola instantiates the various contributed extensions. MUC also explicitly registers GroupChat objects with Hyperbola’s SessionManager.

The MUC support has the following requirements:

• MUC must register and unregister packet handlers as the MUC support is installed or uninstalled.

• MUC must clean up installed listeners on removal.

• Hyperbola and Smack must react to the arrival and departure of packet handlers.

• The Hyperbola UI must react to the coming and going of action contributions.

• Hyperbola must react to the invalidation of GroupChat objects.

The rest of this chapter looks at how to handle each of these requirements in the context of Hyperbola, the MUC example, and more general scenarios so that you can apply them to your domain.

22.2 Dynamic Challenges

Being dynamic is all about managing the links between types, their instances, and plug-ins. There are two main challenges of trying to operate in a dynamic world: being dynamic-aware and being dynamic-enabled. Being enabled is relatively straightforward to achieve because it’s a self-centered concern—all you have to do is to clean up after yourself. Dynamic awareness is an outward involvement, ensuring that the links you have to others are updated as plug-ins are added to and removed from the system. Because awareness is tricky to get right, most of the remaining text in this chapter outlines techniques and helpers for making your plug-ins dynamic-aware.

22.3 Dynamic Awareness

Dynamic awareness has to do with updating your plug-ins’ data structures in response to changes in the set of installed plug-ins; that is, a dynamic-aware plug-in is one that can handle other plug-ins coming and going. Dynamic awareness needs to be considered wherever a plug-in has a data structure that is based on types, objects, or contributions from other plug-ins. In the Hyperbola case, extensions such as MUC contribute packet handlers, menu entries, and chat objects. This means Hyperbola definitely has to be dynamic-aware.

Dynamic awareness comes in two flavors: addition and removal. We say that a plug-in is dynamic-aware for addition if it is set up to handle the dynamic addition of plug-ins to the system. Similarly, we say that a plug-in is dynamic-aware for removal if it can handle the dynamic removal of plug-ins from the system.

Addition is generally easier to deal with as there is less cleanup. Structures can simply be augmented, caches flushed, or new capabilities discovered on the fly. Handling the removal of relationships may be as easy as flushing some caches, or it may be as complicated as tracking contributed or constructed objects, deleting them as required, and cleaning up afterward.

22.3.1 Dynamic Extension Scenarios

The most common dynamic-awareness challenge is for plug-ins that host extension points. The extensions for a plug-in are woven into the extension registry when the contributing plug-in is resolved—when all of its dependencies are met. Similarly, if the plug-in subsequently becomes unresolved, say, when a prerequisite plug-in is removed, its extensions are removed from the registry. Section 27.6, “Bundle Lifecycle,” gives more detail on the lifecycle of plug-ins.

Both executable extensions—extensions that provide code—and descriptive extensions—extensions that provide just information—are problematic for dynamic awareness. The problems arise as a result of caching information from others in your plug-in. Whether it is caching of the extension itself or of any objects created via executable extensions, the cache’s coherence must be maintained.

In Hyperbola the core messaging model exposes two extension points that accept providers:

org.eclipsercp.hyperbola.extensionProviders

org.eclipsercp.hyperbola.iqProviders

Contributed extensions list the packet types in which they are interested and identify a class to handle such packets. When a matching packet is encountered, the contributed class is instantiated, as well as the instance used to handle the packet. The MUC plug-ins contribute executable extensions to both of these to handle MUC message flow.

If Hyperbola were not dynamic-aware for addition, it might miss the addition of these providers and thus be unable to handle MUC messages. If it were not dynamic-aware for removal, it would miss the removal of the extensions and continue trying to use the MUC handlers even though a function might have been removed. Furthermore, an uninstalled plug-in is not garbage-collected until all instances of its types are collected and all references to its types are dropped; that is, it continues occupying memory until its types and objects can all be collected. Technically, you can continue using existing types and objects even after the plug-in is uninstalled, but the state of the plug-in is not guaranteed—yet another reason why dynamic awareness is important.

The next three sections enumerate dynamic extension scenarios and how to handle them. In general, they revolve around whether or not the supplied extension is descriptive or executable and whether or not you cache values discovered in the registry or consult the registry each time you need something.

22.3.1.1 Scenario 1: No Caching

If your plug-in consults the extension registry each time a value is needed, the burden of being dynamic-aware is substantially reduced. All data lives in the extension registry and is maintained for you. The downside of this approach is that accessing the extension registry is likely slower than consulting an optimized, special-purpose data structure or cache in your plug-in. If the extensions are accessed infrequently, however, this trade-off is reasonable.

For example, Hyperbola can use this approach for the providers, but since there is no caching, the extension registry would be consulted for every data packet that does not have a built-in handler. This is fine for simple chatting, but it is likely not satisfactory if more intensive operations such as file transfer or videoconferencing use the same mechanism.

22.3.1.2 Scenario 2: Extension Caching

The performance of extension lookup can be improved by caching the structure of the extensions. For example, Hyperbola might keep an explicit, in-memory table keyed by packet type where the value is the contributing extension. This table needs to be updated accordingly when a plug-in contributing a handler is added to the system. Similarly, if an existing plug-in is removed, its contributed handlers must be removed. Updating the cache is quite trivial—the changed key/value pair is simply added or removed.

This approach improves the time to access the extensions and find the correct handler class for a given packet, but it suffers on two counts: First, it essentially duplicates the extension structure and data; and second, it requires additional infrastructure to implement dynamic awareness.

In some cases there may be no choice. For example, the Resources plug-in’s markers extension point takes marker extensions and builds a multiple inheritance hierarchy of marker types. This structure must be computed rather than read directly from the extension registry, so it inherently requires some level of caching. As such, the Resources plug-in must implement some dynamic-awareness support to clean up the cache when the set of resolved plug-ins changes. The cache cleanup is more complicated as the cached markers’ data structure is inherently interconnected.

To support this need, the extension registry broadcasts registry change events (IRegistryChangeEvent) to registered registry change listeners (IRegistryChangeListener) whenever registry contributions are added or removed. The listeners can then query an extension delta (IExtensionDelta) to find out what changed and how. This information is in turn used to update the cached data structures.

Updating the cache need not be a heavyweight operation. For example, if the cache is not critical and rebuilding is not overly expensive, flushing the entire cache on change is reasonable. This approach is sketched in the following code snippet:

image

Registry change listeners are notified serially, but you still have to be concerned about threads accessing the cache while listeners are clearing it. The code in getCache() gets a reference to the cache and uses that reference. The cache may be flushed at any point after that and the old value used. That’s acceptable because there were no guarantees about ordering here anyway. Notice that this coding pattern closes the window between getting and testing the cache state, and returning the cache as the result.

Of course, another situation may not be that easy. If cache entries are expensive to rebuild, it is better to add and remove entries incrementally. The following snippet shows how to handle change events and traverse the deltas:

image

Here the listener queries the delta from the change event for any changes to the org.eclipsercp.hyperbola.iqProviders extension point. For extension additions, you can choose to aggressively populate the cache with the new extensions or just ignore the additions and look for them later when you have a cache miss. For removals, you need to tear down any data structures and remove the extension from the cache.

22.3.1.3 Scenario 3: Object Caching

The packet handler lookup mechanism is still not as good as it could be. Even with extension caching, Hyperbola still has to look up the required class and instantiate a handler for each packet. Assuming the handlers are context-free, one of each type could be cached and used to handle packets as required.

Even if Hyperbola does not cache the extensions themselves—so Scenario 2 is not applicable—Hyperbola may hold on to either the handler class or some created handler instances. This prevents the contributing plug-ins from being properly garbage-collected. Furthermore, the created handlers cannot be left active when the contributor is removed—the handler is likely to be invalid because its plug-in has been shut down and removed.

Note

It is instructive to note that the Eclipse UI plug-ins have various extension points that cover each of these scenarios. Before the dynamic-awareness requirements were placed on the UI, it cached most extension information and created objects. In some cases this resulted in duplicate information and inefficiencies. It also inhibited the registry’s ability to flush its caches and adapt its space requirements to the current usage patterns. And, of course, it meant that the UI plug-ins would have to do considerably more work to be dynamic-aware. In the end, the approach was to remove the various levels of caching whenever appropriate and rely on direct access to the extension registry or configuration elements.

If Hyperbola is to be dynamic-aware, the handlers it instantiates must be tracked so they can be cleaned up if their contributor is removed. To make it just a little more complicated, it turns out that Smack wants to cache the handler instances for efficiency purposes.

One thing to watch in this scenario is that the cached objects often play a deeper role in the rest of your system; that is, they may be woven tightly into the fabric of the application. The challenge is to understand the interconnections and ultimately reduce them so there is less to clean up.

To update the object structure, you can use the same sort of listener as the one described in Scenario 2. This time, however, the cache contains contributor-supplied objects (i.e., handlers) rather than Hyperbola’s internal data structures. In the case of Hyperbola and Smack, it turns out that there is only one reference to any given handler. The registry change listener simply tells Smack to remove all providers contributed by the deleted extension. The following code snippet shows this in action:

image

Notice that each extension may contribute several handlers and the provider list cache has entries for each provider. Note also that the cache is primed lazily so there is no need to handle extension additions as they happen.

To handle more complex situations, the registry supplies utility classes that help with tracking and disposing object references. In the code snippet below, the HyperbolaProviderManager implements IExtensionChangeHandler and registers itself with an IExtensionTracker to get notification of changes in select extensions.

Extension trackers track objects created for an extension. These objects might be the result of using IConfigurationElement.createExecutableExtension() or an object created manually. Either way, the objects share the common trait that they should be cleaned up when the related extension disappears.

image

image

Looking through the code from the top down, we see that initializeTracker() creates an ExtensionTracker and adds the provider manager as a handler. Notice that the filter used means that the provider manager is notified of changes only to extension points in the Hyperbola plug-in’s namespace. You can narrow this to individual extension points, but this is good enough here.

The provider manager is notified of changes through addExtension() and removeExtension(). Since provider extensions are accessed on demand, we do not need to do anything for addition.

On removal, however, we do need to ensure that any registered providers are removed. The removeExtension() method receives a list of objects that the tracker is tracking relative to the given extension. This list is populated by the provider manager whenever it creates a provider, as shown in getProvider(). Ignoring the detail of how the provider is discovered, at some point a class supplied by an extension is instantiated. The resultant object is then registered with the tracker using registerObject(). The provider is then added to the provider manager’s internal data structure. It is this data structure that needs to be updated if the extension is removed. You saw that code in the method removeExtension().

Notice also that the tracker uses weak references (REF_WEAK) to track the providers. This ensures that provider objects do not stay live in the system just because they are being tracked.

The use of extension trackers is somewhat overkill in this case, but the example gives you an idea of the mechanism’s power. You can use one tracker to track many different extension points and many different objects. You can also use it as your primary data structure by calling getObjects(IExtension) to access all tracked objects for the given extension.

22.3.2 Object Handling

Hyperbola is fundamentally listener-based. Clients can listen for packets, connections, authentication messages, and so on. Every time a client adds a listener, it creates a link between itself and Hyperbola. If the client disappears, the link needs to be cleaned up.

Note

Here we talk about “listeners,” but we really mean “any object given to, and held on by, another plug-in.” It could be an actual listener or some other callback handler, a factory, or the implementation of an algorithm. If you register a reference, you should unregister it.

In a perfect world, all clients would be dynamic-enabled and would clean up after themselves. Failure to clean up listeners prevents the uninstalled client from being garbage-collected—this is effectively a leak. Here are a few strategies you can use to handle contributed objects:

Ignore—Assume that everyone is a good citizen and code your notifier robustly to handle any errors that might occur when notifying a stale listener. This tolerates the removal of the contributing plug-in but leaves dangling listeners and has the potential to leak memory.

Validity testing—Include a validity test in your listener API. Before notifying any listener, the notifier tests its validity. Invalid listeners are removed from the listener list. The registering client then invalidates all its listeners when it is stopped. This lazily removes the listeners but still has the potential to leak if the notifier never tries to broadcast to, and thus test the validity of, an invalid listener.

Weak listener list—Using a weak data structure such as WeakReferences or SoftReferences to maintain the listener list allows defunct listeners to simply disappear. Since clients typically have to maintain strong references to their listeners to support unregistering, there is little danger of the listeners being prematurely garbage-collected.

Co-register the source—Rather than just registering the listener, have clients register both themselves and the listener. You then listen for bundle events (see the next section) and proactively remove listeners contributed by bundles being removed.

Introspection—Every object has a class. The plug-in that loaded the class can be found using PackageAdmin.getBundle(listener.getClass()). With this information you can tweak the co-registration approach to use introspection and cleanup. This approach is transparent but can be a bit costly and does not catch cases where one plug-in adds a listener that is an instance of a class from a different plug-in.

In the end, there are no right answers. The different strategies have different characteristics. The point is that you must be aware of the inter-plug-in linkages and make explicit decisions about how they are managed. You should choose the coding patterns that best suit your requirements (speed, space, complexity) and apply them consistently and thoroughly.

Note

In all of these cases, there are windows of opportunity for Hyperbola to accidentally attempt to notify a stale listener. As with any notification mechanism, it is important that the notification code be robust enough to handle any errors that might occur. You should consider using Platform.run(ISafeRunnable) to help manage such errors.

22.3.3 Bundle Listeners

BundleListeners are a powerful OSGi mechanism for handling change in a running system. Whenever a bundle changes state in the system, all registered BundleListeners are notified. Listeners typically do the same sort of cache management described earlier and as shown in this snippet:

image

In this case the listener is registered as soon as the bundle is started. It listens for UNINSTALLED and UNRESOLVED bundle events and removes the affected bundle from the cache it is managing. Notice that this code is a good citizen as it removes its listener when the bundle is stopped.

22.4 Dynamic Enablement

Dynamic enablement means being a good plug-in citizen; that is, a dynamic-enabled plug-in is written to correctly handle its own dynamic addition and removal. If you don’t clean up, you become a leak. Leaks bloat the system and eventually cause it to run out of memory or become intolerably slow. In the case of MUC support, this means correctly unregistering listeners and other objects that are hooked into the base Hyperbola facilities.

Depending on the implementation of the MUC support, being dynamic-enabled may also mean disposing OS resources. The OS does not know when a bundle is stopped. To the OS, the JVM is still running, so it has to maintain all resources allocated to the JVM process. These include

• Open files

• Graphical objects such as images, colors, and fonts

• Sockets

For MUC support to be dynamic-enabled, it must clean up any such resources as they are removed or stopped.

22.4.1 Cleaning Up after Yourself

Developers often assume that when their plug-in’s stop() method is called, Eclipse is exiting. Thus, they do only mild cleanup, if any at all. These plug-ins are not dynamic-enabled. A dynamic-enabled plug-in is one that

• Ensures all objects it registers are unregistered when no longer needed

• Implements a rigorous stop() method as a backstop

Plug-ins that register listeners, handlers, and UI contributions via code or allocate shared resources must take care to unregister or dispose of such objects when they are obsolete. This is just good programming practice. If you call an add() method, ensure that you call the matching remove() when appropriate. Similarly, this should be done for alloc() and free(), create() and dispose(), and so on.

To implement a backstop, your bundle activator needs to know which objects to dispose. This can be hard-coded if the set is rather limited and known ahead of time. For example, if your plug-in holds a socket open, ensure it is closed in the stop() method.

More generally, you can track the objects needing disposal. The code blocks below sketch how this works. The activator maintains a weak set of objects that need disposal. Throughout the life of the plug-in, various disposable objects are added to and removed from the set. When the plug-in is finally stopped, all remaining disposable objects are disposed. The set is weak to avoid leaks in situations where an object is added but not removed, even though it is no longer live.

image

image

At the end of the last snippet there is an example of a registry change listener that lists itself as a disposable on creation. You can register the disposable at any point as long as it is added before the plug-in stops. When the plug-in stops, the listener is guaranteed to be removed from the event source—the extension registry in this case. If the listener is removed from the source and not the disposal list, either it becomes garbage and is removed transparently or it is unregistered from the source when the plug-in is stopped. Unregistering a listener that is not registered is a no-op.

Note

For clarity and simplicity, we have omitted the synchronization code and error checking needed to make this pattern robust.

22.5 Summary

Dynamic update and addition of functions to running applications is an important part of the total user experience—Hyperbola would be significantly diminished without it. Even with the Runtime mechanisms that support dynamic plug-ins, being dynamic is not free. It is somewhat akin to concurrent programming. You need to revisit and isolate your assumptions—likely a good thing to do anyway! In many cases the outcome is well worth the effort.

22.6 Pointers

• The OSGi and Equinox book contains more in-depth information about creating dynamic applications: McAffer, Jeff, Paul VanderLei, and Simon Archer. OSGi and Equinox: Creating Highly Modular Java Systems (Addison-Wesley, 2010), ISBN 0321585712.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset