Chapter 21. Dynamic Best Practices

Applications are often dynamic. New functionality is added, old functionality is removed, but the system keeps running. Even if your system is not inherently dynamic in its steady state, it is dynamic at startup and shutdown. Without the ability to handle the incremental addition of collaborators, you have to manually ensure that all prerequisite elements—services, extensions, listeners—are available before they are needed. Managing start order is frustrating, cumbersome, and brittle.

We have seen dynamic behavior and mechanisms throughout the Toast example. OSGi services, Service Trackers, and SAT, discussed in Chapter 6, “Dynamic Services,” help considerably. Declarative Services, as discussed in Chapter 15, “Declarative Services,” and the Extension Registry covered in Chapter 16, “Extensions,” simplify dynamic behavior further. In this chapter we generalize the concept of dynamism and look at how it impacts common coding patterns. In particular, we do the following:

• Introduce the notion of dynamic awareness and dynamic enablement

• Outline techniques for handling the arrival and departure of bundles and services

• Detail the OSGi Extender Pattern and BundleTracker

• Look at the dynamics of OSGi startup and shutdown

21.1 Dynamism and You

This chapter discusses the unique challenges presented to developers as they attempt to handle and facilitate comings and goings in the environment around them. We first recap Toast and look at examples of dynamic tolerance. With that as a base, we identify some common dynamic scenarios and outline coding and design best practices.

Dynamism is more than decoupling and using services. Do not be lured into the belief that simply “using services” will make your system behave correctly in a dynamic environment. Even with all the mechanisms in OSGi and Equinox, it is still possible to get dynamism wrong. Concurrency issues, stale listeners, complex programming patterns, and lost changes all collude to cause system failure in the face of dynamic change. In short, being dynamic does not come for free—you must follow certain practices to make the most of these scenarios.

Lesson from the Past

Equinox and OSGi were introduced to Eclipse in Eclipse 3.0 (June 2004). In the course of describing the evolution to OSGi, we explained to the Eclipse community that OSGi supported the dynamic addition and removal of bundles. People were very excited. When the release came, we got quite a number of bug reports complaining that various tools added on top of the Eclipse IDE did not integrate properly until the system was restarted—this dynamic thing was not working . . .

In actual fact, there was no attempt by the Eclipse teams to make the Eclipse tooling platform dynamic. Listening for and handling bundle arrival and departure would be significant work; “Developers would tolerate the restart, and it would be too hard to retrofit dynamism for tools” went the reasoning. Rather, the team adopted the more modest but still significant goal of making the RCP, a subset of the full Eclipse platform, dynamic. This revealed quite a number of assumptions and issues that needed attention. In the end, that goal was largely met, but the effort involved highlights the notion that simply using dynamic mechanisms is not enough. As with concurrency, distribution, transactionality, and persistence, there is no free lunch. Being dynamic takes work.

21.2 Dynamic Aspects of Toast

Toast as an application is dynamic at the domain level; that is, vehicles and users come and go as the system runs. This kind of dynamism is inherent in distributed systems. In addition, in Toast each machine can itself change over time. For example, software can be installed, updated, and uninstalled from vehicles as they are running. We need to ensure that Toast reacts correctly when these changes occur.

The first step is to understand how the elements of the system collaborate—the food chain. Beyond the details of installing functionality, however, are the issues in getting the application to notice the arrival of new functionality and the management of interconnections when functionality is removed from the system—these are the topics addressed in this chapter.

Figure 21-1 gives a notional outline of the Toast Client bundles and shows how they relate to each other. GPS, Airbag, and Display are all at the bottom of the food chain. Tracking and Emergency are in the middle of the food chain, producing and consuming services, and the various UI bundles are at the top as they consume only services.

Figure 21-1 Toast bundle structure

image

Bundles at the bottom of the heap are quite independent of others coming and going. They do, of course, need to clean up after themselves but are otherwise free to be introverted. Bundles in the middle and on top, however, must not only be good citizens and clean up but also carefully watch for other bundles and services coming and going.

Notice that it is sometimes interesting to talk about the grouping of logical functionality. For example, the figure shows that the Navigation application constituent parts interact closely and the elements of the Emergency application interact together, but the applications do not directly interact. Looking at the system more abstractly allows you to talk about the level of interaction between the various applications. This feeds into our food-chain notion but at a coarse grain.

The Toast bundles register services and listeners and contribute extensions to be instantiated by the system. In particular, Toast has the following requirements:

• Various back end bundles must register servlets with the HttpService when it is available.

• All bundles must clean up listeners on removal.

• The back end portal must discover and integrate new actions as they arrive and clean up when they are removed.

• The client UI must react to the coming and going of client applications.

• All applications must correctly react to the presence or absence of their required services.

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

21.3 Dynamic Challenges

Being dynamic is all about managing the links between types, their instances, and bundles—collaborators. There are two main challenges when trying to operate in a dynamic world: being dynamic-aware and being dynamic-enabled.

Dynamic awareness is an outward involvement, ensuring that the links you have to others are updated as collaborators are added to and removed from the system. Awareness is tricky to get right. As such, most of this chapter outlines techniques and helpers for making your bundles dynamic-aware.

Being dynamic-enabled relates to cleaning up after yourself. This is relatively straightforward to achieve because it’s a self-centered concern—you just need to do it.

21.4 Dynamic Awareness

Dynamic awareness has to do with updating your bundle’s data structures and behavior in response to changes in the set of collaborators; that is, a dynamic-aware bundle is one that can handle other elements coming and going or starting and stopping. Dynamic awareness needs to be considered wherever you have a data structure that is based on type, object, or data contributions from other bundles. In the Toast case, everything is dynamic. Vehicle devices such as GPS and airbags, software such as emergency management and audio control, can all come and go at any time. On the back end, portal actions and servlets are also dynamic. In short, all of Toast must be dynamic-aware.

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

Addition is generally easier to deal with as there is less cleaning up to be done—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 a cache, or it may be as complicated as tracking contributed, registered, or constructed objects, deleting them as required, and cleaning up afterward.

This cleanup is important because an uninstalled bundle is not garbage-collected until all instances of its types are collected and all references to its types are dropped; only then can the bundle’s classes be unloaded and the bundle be considered truly uninstalled. Technically, you can continue using existing types and objects even after the defining bundle is uninstalled, but the state of the bundle, and thus the integrity of its functionality and services, is not guaranteed—yet another reason why dynamic awareness is important. The OSGi specification calls these zombie bundles.

Using OSGi services, in and of itself, does not make your systems dynamic-aware—it is easy to write bad code that ignores the service lifecycle signals given by the framework, or neglects to unget services or unregister listeners. Similarly, the Equinox Extension Registry can be abused to create unstable dynamic systems. The key to being dynamic is using the supplied dynamic management mechanisms effectively.

Chapter 6, “Dynamic Services,” highlights several approaches for managing dynamic services. The dynamic service and extension usage patterns outlined in Sections 6.4, “Using Declarative Services,” and 16.6, “Dynamic Extension Scenarios,” are very powerful approaches. Subsequent sections in this chapter highlight some additional pitfalls and techniques for managing collaboration, such as contributed objects (e.g., listeners) and tracking bundle events.

21.4.1 Object Handling

Many systems include listeners or other objects, observers, that form couplings by being registered with a notifier service, the subject. The HttpService is one example—servlets in effect are registered as clients of the HttpService and are notified when there is a request to process. Every time a listener is added, a link between the listener and the notifier is created. If the listener’s contributor disappears or is deactivated, this link needs to be cleaned up.

In a perfect world, all clients would be dynamic-enabled and would clean up after themselves. Failure to clean up listeners prevents the uninstalled contributor from being garbage-collected and may cause runtime errors when a decommissioned listener is invoked. 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 bundle 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. It also creates a standard test-and-set race condition on the listener’s validity flag.

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 contributor—Rather than just registering the listener, have clients register both themselves and the listener. Event sources then listen for bundle events and proactively remove listeners contributed by bundles being removed.

Introspection—Every object has a class. The bundle for a class can be found using FrameworkUtil.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 bundle adds a listener that is an instance of a class from a different bundle.

Discovery—The OSGi Whiteboard Pattern discussed in Chapter 15, “Declarative Services,” flips the relationship between observer and subject such that observers do not register with the subject but rather the subject discovers the observers when needed. This centralizes the list management and simplifies the cleanup but does not eliminate the issues altogether.

In the end, there is no one right answer. The different strategies have different characteristics. The point is that you must be aware of the inter-bundle 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.

21.4.2 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 the following snippet:

image

In this case, the listener is registered as soon as the bundle is started. Your code collects and caches references related to bundles as needed. The BundleListener watches for UNINSTALLED and UNRESOLVED bundle events and removes the data related to 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.

21.5 The Extender Pattern and BundleTracker

Services are not the only things that come and go in OSGi. As we have seen, bundles also change state. Reacting to these state changes can be quite powerful. The BundleListener described in the preceding section is a simple example of this. Unfortunately, programming raw BundleListeners is fraught with the same sort of complexity as using ServiceListeners, as we touched on in Chapter 6, “Dynamic Services.” Enter BundleTrackers.

The BundleTracker class is a direct spin-off of ServiceTracker, complete with customizers. In essence it allows you to describe the kinds of bundles in which you are interested and then presents you with a collection of the bundles that currently fit that model and a set of events relaying changes in the collection. Very convenient.

One of the key uses of BundleTracker is to implement the Extender Pattern. The Extender Pattern is reminiscent of the Whiteboard Pattern. It allows an extender bundle to adapt discovered bundles and configure them into different contexts. OSGi and Equinox include several examples of this:

• The Declarative Services extender watches for active and starting bundles that have the Service-Component header in their manifest. When it finds one, it parses the listed files and creates the described components.

• The Equinox Extension Registry is an extender that watches for resolved bundles that contain a plugin.xml file in their root folder. The content of this file is loaded and woven into the Extension Registry.

The Extender Pattern itself is very useful, and BundleTrackers make extender implementation reasonably straightforward.

21.6 Dynamic Enablement

Dynamic enablement means being a good bundle citizen—a dynamic-enabled bundle 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 Toast, this means that all bundles must correctly unregister their listeners, unget acquired services, and unregister all their services.

Some of this can be done automatically for you with mechanisms such as Declarative Services. Being dynamic-enabled may also mean disposing of 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 streams

• Undisposed graphical objects such as images, colors, and fonts

• Unstarted and running threads

• Open sockets

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

21.6.1 Cleaning Up after Yourself

Developers often assume that when their bundle’s stop method is called, the system is shutting down. As such, they do only mild cleanup, if any at all. These bundles are not dynamic-enabled. Bundles can be stopped at any time and actual system shutdown may not occur for quite some time.

A dynamic-enabled bundle is one that

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

• Ensures that all allocated resources are freed

• Implements a rigorous component deactivate method where Declarative Services are used

• Implements a rigorous stop method where a BundleActivator is used

Bundles 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, as well as for opening and closing streams.

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

More generally, you can track the objects needing disposal. The following code is a sketch of how this works. The activator maintains a weak set of objects that need disposal. Throughout the life of the bundle, various disposable objects are added to and removed from the set. When the bundle 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

This is just an example of an implementation approach. At the end of the 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 bundle stops. When the bundle 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, it is either removed transparently as garbage or it is unregistered from the source when the bundle is stopped. Unregistering a listener that is not registered is a no-op.

21.7 The Dynamics of Startup and Shutdown

Much of the focus on dynamism tends to be in terms of things being added or removed while the application is running. Some people assume that since their scenario is fixed, dynamic issues do not apply to them. In fact, every system is dynamic since bundles are installed and started in a sequential and more or less random order.

21.7.1 Start Levels

OSGi includes the notion of start level to help manage the startup and shutdown order. The start level of a bundle is set using the StartLevel service. OSGi start levels are much like UNIX start levels. As the system starts, the start level is increased, and all bundles marked with the current start level are started before moving on to the next level. So, for example, by the time bundles at level 4 are started, all those needing to be started at level 3 have been started. If a level is not specified, the framework uses the default start level. In Equinox the default start level is 4. You can change this by manipulating the value of the osgi.bundles.defaultStartLevel system property.

You can inject your bundle into the startup sequence by controlling its start level. This allows you to, for example, add login prompters before the application is run, control the bundles that are installed, or do last-minute cleanup as the system shuts down.

A Word on DS and Simple Configurator

In some of the Toast configurations outlined in this book we use start levels to ensure that two certain bundles are started early, in particular, the Declarative Services implementation, org.eclipse.equinox.ds, and the Simple Configurator, org.eclipse.equinox.simpleconfigurator. These bundles are unique in that they are basic system infrastructure.

The DS bundle must be started before its extender is able to process any contributed DS components. As such, the DS bundle must be started at or before the start level of any bundles expecting to use DS—if DS is not started, their components will not be registered. If you use DS and start levels, we recommend simply ensuring that the DS bundle is started at level 1.

Simple Configurator is part of the p2 provisioning infrastructure and is responsible for installing and managing other bundles. While it is technically possible to start this bundle at any point in the start sequence, earlier is better, as its operation may result in bundles being installed, updated, or uninstalled. It’s better to do this before they have started doing work.

21.7.2 Proper Use of Services

By far the best approach to startup dynamism is to simply use the available dynamic mechanisms as intended. If your bundle is dynamic-aware for addition, it does not need any special treatment at startup. When the needed services appear, the bundle or component will start operation accordingly. The use of Declarative Services and the Extension Registry with lazy activation greatly ease achieving this goal. Even so, with DS you may need to use immediate components, as discussed in Section 15.2.5, “Immediate Components.”

It takes some time for traditional Java programmers to get their heads around the OSGi pattern of interacting bundles. There is generally no main thread or entry point from which you can hang the startup of the various components. Even in situations where there is, that usage pattern is brittle and hard to maintain.

21.7.3 Shutting Down Is Not Always Easy

Many bundles start up and allocate resources that need to be cleaned up on shutdown. HTTP servers, worker pools, network connections, and the like all need to be explicitly shut down. An example from the writing of this book is telling. When putting together the client UI for the radio and CD player applications, we used the Eclipse Jobs mechanism to create workers to manage the operation of each virtual device. These workers wait until an event is discovered, wake and process the event, and then schedule themselves to run and wait again. When the system is shut down, the radio and CD player DS components are deactivated and need to clean up their Jobs. The following snippet outlines this process:

image

Logically, canceling a Job causes it to stop what it is doing and return. This in turn would allow the join to succeed, and we would be sure that the component’s resources were indeed freed. In practice, however, the Job simply uses a wait call and does not check for cancellation, as shown here:

image

Since the Job does not look for cancellation, shutdown’s call to cancel has no effect, the Job will never exit, and the join call will never complete—deadlock. Instead, you must be careful to ensure that the component or bundle’s deactivation code is robust and complete—resources must be freed and the methods must not fail, do a partial job, or hang. In this case the problem can be addressed by having a volatile boolean field called running that is set and checked in the updated Job code, as shown here:

image

The key here is that the Job itself must be aware that it can be canceled and must clean itself up. The component can then cancel the Job and be assured it is stopped. The same model can be applied to threads, loops, and many other resources allocated and managed by components or bundles.

21.8 Summary

Dynamic update and addition of functionality to running applications is an important part of the total user experience. Toast would be significantly diminished without it, and dynamic server-side applications would be impossible.

OSGi and Equinox include many mechanisms, from Declarative Services to BundleTracker and ExtensionTracker, in support of dynamic behavior. Even with these, being dynamic is not free. It is somewhat akin to concurrent programming. Attention to detail and revisiting and isolating your assumptions are good tactics—and likely good things to do anyway! In many cases, the outcome is a better, more flexible system that is also more dynamic.

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

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