Service Coordination Classes in 6.0

Introduction

The pentaho-service-coordinator OSGI Bundle contains two main features, IServiceBarrier and IPhasedLifecycleManager. The bundle is actually usable outside of OSGI and is seeing first usage within the PDI-OSGI-Bridge. Both are designed to help coordinate the system in the more dynamic environment we find ourselves moving in. OSGI is a part of that, as is the more sophisticated nature of our product in general where we support highly pluggable architectures across the board.

IServiceBarrier

A basic barrier class. Barriers allow for extensible coordination between units of code unaware of the other. The coupling between systems is to the barrier instead of each other. Example Scenario: "Component A" don’t know about "Component B", but A shouldn’t execute until B is done processing something. B places a hold on A’s Execution Service Barrier.

Multiple Threads can await the availability of the barrier, and multiple outside components can place holds on the barrier. The awaitAvailability() call right now will block forever. Feel free to add a timeout if you find yourself needing that feature. I didn’t right away.

You can reverse the pattern and use the barrier to indicate when a service is available ( Scenario 2 below ). This will allow multiple consumers to check availability and optionally block execution until the service becomes available.

Note: Barriers are not a substitute for synchronization or Locks. A hold may be added at any time and will only affect the next call to isAvailable() or awaitAvailability().

IServiceBarrierManager

A simple factory/registry for barriers by ID. Held in a WeakHashMap. You don’t need to use the manager. It’s just there to facilitate finding a barrier. Publishing the barrier to the OSGI service registry would be better. However the manager works well when you’re using the jar outside of OSGI and there’s no ServiceLocator to be found.

Example Barrier consumer ( Component A from the example )

IServiceBarrier barrier = serviceBarrierManager.getBarrier( "myService" );

// Allow others to block execution by obtaining a lock. awaitAvailability() will block until all holds are released
barrier.awaitAvailability();

// continue executing code

Example Barrier Hold ( Component B from the example )

IServiceBarrier barrier = serviceBarrierManager.getBarrier( "myService" );
barrier.hold();
// Do something
barrier.release();

Scenario 2: Service

// In Constructor
this.barrier = serviceBarrierManager.getBarrier( "myService" );
barrier.hold();
// Perform initialization 
barrier.release();

public IServiceBarrier getBarrier(){
  return barrier
}

Scenario 2: Service Consumer

IServiceBarrier barrier = someService.getBarrier();

// Conditionally call if available
if( barrier.isAvailable() ){
  someService.doSomethingOptional();
}

// Block until service is available
barrier.awaitAvailability();
someService.doSomethingRequired();

IPhasedLifecycleManager

Most of our existing lifecycle managers notify listeners synchronously. All work done by that listener for the particular lifecycle event has to be completed before returning. This is causing problems as we move into OSGI where the system starts up Asynchronously employing many different systems running concurrently in background threads. It’s also limiting startup performance where unrelated listeners could very well process the lifecycle events in parallel.

IPhasedLifecycleManager is based around the concept of a Phase (integer). When the phase of the manager is changed (setPhase, advance, retreat), the Listeners will be notified by way of a configurable Executor. By default the Executor will simply call them synchronously, but an ExecutorService could be configured to notify them in parallel.

Listeners supplied an Event class which holds the phase as well as an optional object related to the phase event. When done processing the phase change Listeners call accept() on the event. The advantage is that the listener can return immediately from the Phase notification, freeing up a Thread to be used by another listener, and it can then asynchronously accept the event whenever it’s internally ready.

How to handle dependent listeners

You can create a hierarchy of listeners, have one manually call the dependent ones in order. You could also nest PhasedLifecycleManagers for very sophisticated work. And of course you could employ the IServiceBarrier to coordinate between them.

Why not just use an Event Bus (Guava, etc)

An AsyncEventBus supports much of the requirements. The largest difference here is that listeners must all accept the phase event before the manager can advance again. You can think of it as a built-in barrier associated with the phase.