Jukebox: Services
Background
Early experiments with the threads in OS/2 showed that the concept of a thread is weak where it gets to providing reliability and consistency. Threads offered by host operating systems were useful, but handicapped: they didn't offer any kind of fault tolerance. All the problems that happen just cause the thread to die, without any kind of notification to possible consumers of the information that the threads might produce, thus causing difficult to debug deadlocks.
Service was introduced to address this specific problem, as well as to provide uniform lifecycle and platform independent synchronization primitives between producers and consumers in a parallel system.
It turned out to be so useful, all the projects that have been created since are heavily using them.
Messenger
This is the simplest service possible, however, it was created the last.
The concept is simple: you create a messenger instance and send it on its way. When the task is completed, the messenger notifies you using the Asynchronous Completion Token. No problem ever gets unreported.
Use Case: Write an audit record to the disk and report back.
Passive Service
The passive service lifecycle is as follows:
- Service is instantiated;
- Service is started, startup() sequence is executed. Note that the startup may be unsuccessful;
- Service waits idly, allowing others to call its business logic specific methods;
- Service is stopped, shutdown() sequence is executed.
A typical example of a passive service is inetd, a.k.a. superserver - when starting up, it reads the configuration, may fail if it is incorrect, then waits for incoming connections, serves them, then, on shutdown, it may kill all active connections, then it terminates.
The service is not a silver bullet. While a lot of consistency is added by enforcing the protection of startup() and shutdown() methods so they don't blow up and leave the service in an undefined state, there's a condition that must be watched carefully: the service that has just stopped must have a state identical to a service that has just been instantiated. In the PassiveService proper this condition is met, however, subclasses must watch the implementation closely. Usually, the best solution is to discard the instance of the service that has stopped and create a new one, however, it may not be feasible under some circumstances (poolable resources, high performance environment).
Active Service
Active service, unlike passive, doesn't sit idly after it has started. Instead, execute() is called at step #3. Also, the service may terminate by itself (calling shutdown()) if it so happens that execute() call is finished before some external entity called stop().
A typical example of an active service is... a batch program. It starts, reads the configuration, does some predefined job and terminates either when it is done, or when the user kills it, closing the file handles as it is shutting down.
Using Semaphores with Services
In order to make the services useful, asynchronous notifications about the service state were introduced. There are four notification checkpoints (for both passive and active services):
- When the service was started.
- When the service completed the startup sequence, either successfully or unsuccessfully.
- When the service was stopped.
- When the service completed the shutdown sequence.
Here's an example illustrating the usefulness of the notification checkpoints:
protected void startup() throws Throwable { // Create a semaphore group to keep track on subservice startups SemaphoreGroup subStarted = new SemaphoreGroup(); for ( ; needToStartSomeMoreSubServices(); ) { Service s = createNextSubService(); // add the semaphore returned by start() to the group subStarted.add(s.start()); // and keep going } // And now wait until all the subservices have started successfully if (!subStarted.waitForAll(true)) { throw new ServiceUnavailableException("Oops"); } }
In case there's a single service startup, the code is even simpler:
Service s = new Service(); if ( !s.start().waitFor() ) { throw new ServiceUnavailableException("Service startup failed"); }