Tuesday, October 13, 2015

An OSGi Scheduler

The OSGi enRoute initiative (just released!) is already acting as an incubator for future OSGi specifications. It already generated ten RFPs that are available on github. Each of these RFPs has a (often simple) implementation in enRoute. In this blog I want to discuss one of my favorites: the Scheduler.
There are many services that I've desired for a long time but a scheduler that could use Unix cron like expressions is definitely one of the oldest.
Quite often you need to run a task every hour, every third Wednesday, or on January one on odd years.  From an OSGi perspective the design is quite straightforward, just register a service, specify the cron expression as a property and wait to be called back. The service diagram is as follows:
An extensive description can be found at the OSGi enRoute service catalog. An example using this service would look something like:
@Component(
    property = CronJob.CRON + "=1-30/2 * * * * ?"
  )
  public class CronComponent implements CronJob
    @Override
    public void run(Object data) throws Exception {
      System.out.println("Cron Component");
    }
  }
So what does that CRON expression mean? Well, there are 7 fields:
  • Second in minute
  • Minute in hour
  • Hour in day
  • Day of month
  • Month in year
  • Day of week
  • Year (optional)
The expression is matched against the actual time. Every time the time matches, the corresponding action is executed. For matching, each field can have quite a complex syntax. The simplest is if it is a wildcard, the asterisk ('*'). A wildcard, well, always matches. The second form is just a number, this just matches at that position. For example, 0 12 * * * * * will match noon every day. You can also specify a range like 0-30. To repeat you can add a slash and the step value like 0/5 which repeats every fifth second (if used as the second field). And last but not least you can add additional syntax after a comma (,) like 1,5,15,45. The scheduler uses the syntax made popular in Java by Quartz. There are cron simulators to test these expressions since they can become quite complex.
The primary use case is nice, but there are other scheduling problems. What about having a delay in your code? Especially in distributed middleware and IoT applications scheduling on time is a crucial aspect.
We've made this service a real OSGi service in a Java 8 world. We took advantage of the OSGi R6 Promises and the amazing Java 8 Date & Time support. We also could make the service API friendlier to lambdas by allowing exceptions to be thrown. The scheduler supports fire once and repeated schedules with various combinations of milliseconds and Java 8 temporals.
A few samplers. First a one shot simple delay:
scheduler.after( () →
    System.out.println("fire!"), 100 ); // ms
  scheduler.after( () →
    System.out.println("fire!"), 
    Duration.ofMillis(100) );
We can also schedule an event at a given time:
  LocalDateTime localDateTime =
    LocalDateTime.parse("2017-01-13T09:54:42.820Z", ISODATE);
  ZonedDateTime zonedDateTime =
    localDateTime.atZone(ZoneId.of("UTC"));
  Instant instant = zonedDateTime.toInstant();
  scheduler.at( () → System.out.println("fire!"), instant );
And using promises:
     scheduler.after( 100 ).
       then( this::start ).
       then( this::secondStage ).
       then( this::thirdStage, this::failure );
The scheduler also provides a wrapper for allowing Promises to time out. If the provided promise does not resolve before the timeout then the resulting promise fails with a Time Out Exception.
  void foo( Promise p ) {
     CancellablePromise cp = scheduler.before( p, 
       Duration.ofMinutes(5) );
     cp.then( this::start ).
        then( this::secondStage ).
        then( this::thirdStage, this::failure );
  }
Instead of firing once we also need to handle repeating schedules. Schedules should not always run until the end of the component's life cycle, sometimes you want to stop them long before. Therefore all schedules return a Closeable. If this object is closed, the schedule will stop. (If the component is stopped, all schedules are stopped automatically of course.)
The simplest example is again a simple milliseconds repeat. You can specify any number of initial delays, the last delay is repeated until the schedule is stopped:

  Closeable rampUp = scheduler.schedule( 
    this::tick, 10, 20, 40, 80, 100 );
The last, but definitely not least is the cron schedule. The following example ticks every second second in the first half of each minute.
  Closeable cron = scheduler.schedule( 
    this::cronTick, "0-30/2 * * * * *" );
OSGi enRoute also contains an example scheduler application. This application has a web based GUI that allows you to exercise the API. just check out the workspace and run or debug it in bndtools.
The experiences in OSGi enRoute with this service resulted in RFP 166 Scheduling. These RFPS are discussed in the Expert Groups and this already created some new ideas and nibbled on the including ones. Liferay indicated that it would be really useful if the service could support persistent schedules. There was also a discussion about the guarantees one should get for repeating schedules. Currently the schedules are repeated by actual time and not interval as long as they do not overlap. However, we likely need to consider cases where cron events could be missed. Lots of work!
If you're interested in this area do not hesitate to read the RFP and provide comments on our public Bugzilla, or better, join the Expert Groups.
An interesting IoT example of the Scheduler service is in the OSGi IoT Contest for the upcoming OSGi Community Event 2015 in Ludwigsburg. The contest is about Lego® trains and their tracks. You can either write a Train Manager or a Track Manager.


During the Community Event we will run these bundles on an actual track in different combinations, best performers win a prize.
In the SDK you find an emulator, a sample Train, and an example Track Manager. This Track Manager makes extensive use of the Scheduler to control the color of the signals. For example:

  // set the signal to green for 10 seconds
  private void greenSignal(
  Optional<Signalhandler<Object>> signal){
    if(signal.isPresent()){
      setSignal(signal.get().segment.id, Color.GREEN);
      scheduler.after(() → 
        setSignal(signal.get().segment.id,Color.YELLOW),10000);
      scheduler.after(() →
        setSignal(signal.get().segment.id,Color.RED),15000);
    }
  }

Ok, from a safety point of view using you might have noticed that timings for track segment signals might not be, well, safe. Alas, we do not have presence sensors in each track segment and the changing signals look pretty cool in the emulator!
But if you think you can do better? Then why not participate in the contest? There are plenty of opportunities to use this service in this context and it is sure to be a lot of fun. You are not required to show up in Ludwigsburg, you can see your train by video if you are unfortunately cannot be there. The lucky ones that are present will be able to 'practice' during the conference hours. I do admit, I am awfully curious how those trains will interact with each other during the finale. I do expect a few spectacular crashes.

No comments:

Post a Comment