Home

Awesome

Wisp Scheduler

Build Status Coverage Status Maven Central

Wisp is a library for managing the execution of recurring Java jobs. It works like the Java class ScheduledThreadPoolExecutor, but it comes with some advanced features:

Wisp weighs only 30Kb and has zero dependency except SLF4J for logging. It will try to only create threads that will be used: if one thread is enough to run all the jobs, then only one thread will be created. A second thread will generally be created only when 2 jobs have to run at the same time.

The scheduler precision will depend on the system load. Though a job will never be executed early, it will generally run after 1ms of the scheduled time.

Wisp is compatible with Java 8 and higher.

Getting started

Include Wisp in your project:

<dependency>
    <groupId>com.coreoz</groupId>
    <artifactId>wisp</artifactId>
    <version>2.5.0</version>
</dependency>

Schedule a job:

Scheduler scheduler = new Scheduler();

scheduler.schedule(
    () -> System.out.println("My first job"),           // the runnable to be scheduled
    Schedules.fixedDelaySchedule(Duration.ofMinutes(5)) // the schedule associated to the runnable
);

Done!

A project should generally contain only one instance of a Scheduler. So either a dependency injection framework handles this instance, or either a static instance of Scheduler should be created.

In production, it is generally a good practice to configure the monitor for long running jobs detection.

Changelog and upgrade instructions

All the changelog and the upgrades instructions are available in the project releases page.

Schedules

When a job is created or done executing, the schedule associated to the job is called to determine when the job should next be executed. There are multiple implications:

Basics schedules

Basics schedules are referenced in the Schedules class:

Composition

Schedules are very flexible and can easily be composed, e.g:

Cron

Schedules can be created using cron expressions. This feature is made possible by the use of cron library. This library is very lightweight: it has no dependency and is made of a single Java class of 650 lines of code.

So to use cron expression, this library has to be added:

<dependency>
    <groupId>ch.eitchnet</groupId>
    <artifactId>cron</artifactId>
    <version>1.6.2</version>
</dependency>

Then to create a job which is executed every hour at the 30th minute, you can create the schedule using: CronExpressionSchedule.parse("30 * * * *").

CronExpressionSchedule exposes two methods to create Cron expressions:

Cron expression should be checked using a tool like:

Sometimes a use case is to disable a job through configuration. This use case can be addressed by setting a Cron expression that looks up the 31st of February:

Cron-utils was the default Cron implementation before Wisp 2.2.2. This has changed in version 2.3.0. Documentation about cron-utils implementation can be found at Wisp 2.2.2. Migration from cron-utils is detailed in the release note of Wisp 2.3.0.

Custom schedules

Custom schedules can be created, see the Schedule interface.

Past schedule

Schedules can reference a past time. However once a past time is returned by a schedule, the associated job will never be executed again. At the first execution, if a past time is referenced a warning will be logged but no exception will be raised.

Statistics

Two methods enable to fetch scheduler statistics:

Cleanup old terminated jobs

The method Scheduler.remove(String jobName) enables to remove a jobs that is terminated, so in the JobStatus.DONE status. Once removed, the job is not returned anymore by Scheduler.jobStatus().

For an application that creates lots of jobs, to enable avoid memory leak, a cleaning job should be scheduled, for example:

scheduler.schedule(
    "Terminated jobs cleaner",
    () -> scheduler
        .jobStatus()
        .stream()
        .filter(job -> job.status() == JobStatus.DONE)
        // Clean only jobs that have finished executing since at least 10 seconds
        .filter(job -> job.lastExecutionEndedTimeInMillis() < (System.currentTimeMillis() - 10000))
        .forEach(job -> scheduler.remove(job.name())),
    Schedules.fixedDelaySchedule(Duration.ofMinutes(10))
);

Long running jobs detection

To detect jobs that are running for too long, an optional job monitor is provided. It can be setup with:

scheduler.schedule(
    "Long running job monitor",
    new LongRunningJobMonitor(scheduler),
    Schedules.fixedDelaySchedule(Duration.ofMinutes(1))
);

This way, every minute, the monitor will check for jobs that are running for more than 5 minutes. A warning message with the job stack trace will be logged for any job running for more than 5 minutes.

The detection threshold can also be configured this way: new LongRunningJobMonitor(scheduler, Duration.ofMinutes(15))

Scalable thread pool

By default the thread pool size will only grow up, from 0 to 10 threads (and not scale down). But it is also possible to define a maximum keep alive duration after which idle threads will be removed from the pool. This can be configured this way:

Scheduler scheduler = new Scheduler(
    SchedulerConfig
        .builder()
        .minThreads(2)
        .maxThreads(15)
        .threadsKeepAliveTime(Duration.ofHours(1))
        .build()
);

In this example:

Plume Framework integration

If you are already using Plume Framework, please take a look at Plume Scheduler.