Skip to main content

Run Level Service

The HK2 run level service allows you to automatically bring your system up and down in an orderly fashion. Run level services are annotated to come up or down at specific levels. The user then sets the current level of the system and the run level service ensures that all services at that level and below are started.

Creating a RunLevel service

A RunLevel service is simply a service that is in the RunLevel scope. RunLevel services normally have a @PostConstruct or @PreDestroy method to be called when the service is started or stopped (but this is not required).Here is an example of a RunLevel service:

  @Service @RunLevel(value=5)
  public class LoggingRunLevelService {
    @PostConstruct
    private void startMe() {}
    
    @PreDestroy
    private void stopMe() {}
  }

The LoggingRunLevelService will get started when the run level is 5 or higher, and will be shutdown if the run level is dropped to 4 or lower.

The following service is started at level 10:

  @Service @RunLevel(value=10)
  public class SecurityRunLevelService {
    @PostConstruct
    private void startMe() {}
    
    @PreDestroy
    private void stopMe() {}
  }

All the services in one level are started prior to any service in another level being started. If the system is going down rather than up, all services in a level will be stopped before services in a lower level are stopped.

The implementation of Context that goes along with the RunLevel scope is provided by the RunLevel feature.

The RunLevelController

The RunLevelController is a service implemented by the HK2 RunLevel feature. It can tell you the current run level of the system, and it can be used to change the current run level. You can use the asynchronous or synchronous methods to change the level. The only difference is whether or not you want to block the current thread waiting for the services to finish.

The synchronous API to change levels is proceedTo. You can use it like this:

  public class MyClass {
    @Inject
    private RunLevelController controller;
    
    public void changeRunLevel(int level) {
        controller.proceedTo(level);
    }
  }

This code will either bring the system up or down to the specified level while blocking the thread. If you do not want to block the thread, you can use the asynchronous version of the API, which returns a RunLevelFuture that can either be polled or waited on at a later time.

  public class MyClass {
    @Inject
    private RunLevelController controller;
    
    public RunLevelFuture changeRunLevelAsync(int level) {
        return controller.proceedToAsync(level);
    }
  }

There are also methods on the RunLevelFuture which allows the caller to determine what level the system is proceeding to, and whether or not the current job is going up in level or going down.

In general if there is an existing RunLevel job in progress another one cannot be started and existing job cannot be changed. A running job can be cancelled at any time. A RunLevelListener can be used to change the proceedTo value of a RunLevel job.

The RunLevelListener

The RunLevelListener is an interface implemented by users of the RunLevel service if they want to perform actions in error situations or at the end of a level. The implementations must be put into hk2 as services. All implementations of RunLevelListener registered with HK2 will be called.

In the following example the RunLevelListener logs all events that happen.

  @Service
  public class RunLevelListenerLogger implements RunLevelListener {
  
    public void onProgress(ChangeableRunLevelFuture currentJob,
            int levelAchieved) {
        Logger.log("Achieved level: " + levelAchieved);
    }
    
    public void onCancelled(RunLevelFuture currentJob,
            int levelAchieved) {
        Logger.log("Cancelled at level: " + levelAchieved);

    }

    public void onError(RunLevelFuture currentJob, ErrorInformation info) {
        Logger.log("Error while progressing to level: " + currentJob.getProposedLevel(),
            info.getError());
    }
  }

The onProgress callback can be used to change the level the current job is proceeding to using the changeProposedLevel method of the passed in ChangeableRunLevelFuture.

In the onError callback the ErrorInformation parameter can be used to get the error that was thrown by the service. It can also be used to tell the RunLevel service what to do about the error. The choices are to go down to the last fully achieved level and end the job there (ErrorInformation.ErrorAction.GO_TO_NEXT_LOWER_LEVEL_AND_STOP), or to simply ignore the error (ErrorInformation.ErrorAction.IGNORE). When the system is going up in level the default action is to go down to the last fully achieved level and stop. When the system is going down in level the default action is to ignore the error.

If the onCancelled callback has been called then the job has been cancelled. A new job can be put into place, but the current job is considered to be complete.

Multi Threading

The RunLevelController will attempt to run all of the services in a single run-level on parallel threads. All dependencies will be honored as always (if run-level service B depends on run-level service A then run-level service A is guaranteed to be fully up before run-level service B is started). By default the RunLevelController will use as many threads as there are services in the level. This can be controlled with the setMaximumUseableThreads method of RunLevelController.

Multi-threading can be turned off by using the setThreadingPolicy method to set the policy to RunLevelController.ThreadingPolicy.USE_NO_THREADS. If this is done then an IllegalStateException will be thrown from the proceedToAsync method, since in order to do asynchronous processing at least one thread must be used. When the policy is USE_NO_THREADS all services will be started on the thread that calls the proceedTo method. However, even in the USE_NO_THREADS mode a thread will be used when bringing services down in order to ensure that they can be canceled in the case of a hung service.

In some cases it is necessary to use specialized threads on which to run the run-level services. The Executor that is used by the run level feature can be replaced with the setExecutor method of the RunLevelController. The Executor that is currently being used can be discovered with the getExecutor method.

Ordering

In general the run level feature feeds all the services in a certain level to the set of threads based on the natural ordering given by hk2. If only a single thread is being used then that is the order services will run. If multiple threads are used then that is the order those services will be given to the threads. Thus when more than one thread is used the ordering in which services are started may be indeterminate.

A user can affect the ordering with which the services are given to the threads by implementing the Sorter service. All implementations of Sorter will be called with the results from the previous sort method of Sorter. The final result will be the list of services given to the set of threads executing run-level services.

Other Goodies

When you give a service a RunLevel value like 10, that normally means that the service should start at level 10, no sooner and no later. In fact, by default if a service at level 10 is started when the system level is less than 10 an error will be raised. But there are other times when what you want is for the RunLevel value to mean that the service should be started at that level or lower. This is useful in cases where it may be necessary to start the service sooner than it would normally be started based on some runtime characteristic of the system. In that case you can set the mode of the RunLevel to RUNLEVEL_MODE_NON_VALIDATING. Here is an example:

  @RunLevel(value=10, mode=RunLevel.RUNLEVEL_MODE_NON_VALIDATING)
  public class CanRunBeforeTenService {
  }

HK2 is normally a demand driven system, meaning that services are not started until there is an explicit lookup for a service (either via API or via injection). The HK2 Run Level Service allows RunLevel services to be satisfaction driven, where the condition for creating is the current run level of the system. This is useful when building processes that have a specific set of services that need to run on booting or shutting down. The obvious application is for server-style processes, but it is also useful for setting up things like connections or security in clients.

Back to the top