A simple Java Scheduler library with a minimal footprint and a straightforward API

Overview

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.2.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:

  • the same job will never be executed twice at a time,
  • if a job has to be executed at a fixed frequency, then the job has to finish running before the next execution is scheduled ; else the next execution will likely be skipped (depending of the Schedule implementation).

Basics schedules

Basics schedules are referenced in the Schedules class:

  • fixedDelaySchedule(Duration): execute a job at a fixed delay after each execution
  • executeAt(String): execute a job at the same time every day, e.g. executeAt("05:30")

Composition

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

  • Schedules.afterInitialDelay(Schedules.fixedDelaySchedule(Duration.ofMinutes(5)), Duration.ZERO): the job will be first executed ASAP and then with a fixed delay of 5 minutes between each execution,
  • Schedules.executeOnce(Schedules.executeAt("05:30")): the job will be executed once at 05:30.
  • Schedules.executeOnce(Schedules.fixedDelaySchedule(Duration.ofSeconds(10))): the job will be executed once 10 seconds after it has been scheduled.

Cron

Schedules can be created using cron expressions. This feature is made possible by the use of cron-utils. So to use cron expression, cron-utils should be added in the project:

<dependency>
    <groupId>com.cronutils</groupId>
    <artifactId>cron-utils</artifactId>
    <version>8.0.0</version>
</dependency>

Then to create a job which is executed every hour at the 30th minute, you can create the schedule: CronSchedule.parseQuartzCron("0 30 * * * ? *").

Cron expression should be created and checked using a tool like Cron Maker.

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:

  • Scheduler.jobStatus(): To fetch all the jobs executing on the scheduler. For each job, these data are available:
    • name,
    • status (see JobStatus for details),
    • executions count,
    • last execution start date,
    • last execution end date,
    • next execution date.
  • Scheduler.stats(): To fetch statistics about the underlying thread pool:
    • min threads,
    • max threads,
    • active threads running jobs,
    • idle threads,
    • largest thread pool size.

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:

  • There will be always at least 2 threads to run the jobs,
  • The thread pool can grow up to 15 threads to run the jobs,
  • Idle threads for at least an hour will be removed from the pool, until the 2 minimum threads remain.

Plume Framework integration

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

Comments
  • Removal of schedules

    Removal of schedules

    I'm looking at using this framework for scheduling. Everything will be configuration based, but it can change at runtime. Is there a better way of removing a schedule than shutting everything down and rescheduling everything? Ideally it would be something like

    scheduler.remove("job-name");
    

    or

    scheduler.findJob("job-name").get().close();
    

    If a job is already running it should be allowed to complete, and whether it is running or not it should not be scheduled again.

    enhancement 
    opened by tylerhawkes 21
  • Stops scheduling after a few minutes of executions

    Stops scheduling after a few minutes of executions

    Heyo! This scheduler is absolutely awesome first off! I am having an issue with the following code, the error is also below.

    00:24:20.968 [Wisp Scheduler Worker #0] ERROR com.coreoz.wisp.Scheduler - Error during job 'xxx.xxx.xxx.Scheduled.ServerStatus@385afffe' execution
    java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached
            at java.base/java.lang.Thread.start0(Native Method)
            at java.base/java.lang.Thread.start(Thread.java:800)
            at java.base/java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:939)
            at java.base/java.util.concurrent.ThreadPoolExecutor.ensurePrestart(ThreadPoolExecutor.java:1585)
            at java.base/java.util.concurrent.ScheduledThreadPoolExecutor.delayedExecute(ScheduledThreadPoolExecutor.java:346)
            at java.base/java.util.concurrent.ScheduledThreadPoolExecutor.schedule(ScheduledThreadPoolExecutor.java:562)
            at com.mattmalec.pterodactyl4j.requests.RateLimiter.lambda$runQueue$2(RateLimiter.java:106)
            at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708)
            at com.mattmalec.pterodactyl4j.requests.RateLimiter.lambda$runQueue$3(RateLimiter.java:105)
            at com.mattmalec.pterodactyl4j.utils.LockUtils.locked(LockUtils.java:29)
            at com.mattmalec.pterodactyl4j.requests.RateLimiter.runQueue(RateLimiter.java:105)
            at com.mattmalec.pterodactyl4j.requests.RateLimiter.lambda$queueRequest$0(RateLimiter.java:61)
            at com.mattmalec.pterodactyl4j.utils.LockUtils.locked(LockUtils.java:29)
            at com.mattmalec.pterodactyl4j.requests.RateLimiter.queueRequest(RateLimiter.java:59)
            at com.mattmalec.pterodactyl4j.requests.Requester.request(Requester.java:57)
            at com.mattmalec.pterodactyl4j.requests.RequestFuture.<init>(RequestFuture.java:29)
            at com.mattmalec.pterodactyl4j.requests.PteroActionImpl.execute(PteroActionImpl.java:92)
            at com.mattmalec.pterodactyl4j.PteroAction.execute(PteroAction.java:97)
            at dev.elliotfrost.anarchybot.Scheduled.ServerStatus.BungeeStatus(ServerStatus.java:26)
            at dev.elliotfrost.anarchybot.Scheduled.ServerStatus.run(ServerStatus.java:51)
            at com.coreoz.wisp.Scheduler.runJob(Scheduler.java:481)
            at com.coreoz.wisp.Scheduler.lambda$launcher$0(Scheduler.java:450)
            at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
            at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
            at java.base/java.lang.Thread.run(Thread.java:831)
    00:24:20.968 [Wisp Scheduler Worker #0] DEBUG com.coreoz.wisp.Scheduler - Job 'xxx.xxx.xxx.Scheduled.ServerStatus@385afffe' executed in 257ms
    

    The code for the scheduler (whole file not included):

        public void onReady(ReadyEvent event) {
            LOGGER.info("{} is ready", event.getJDA().getSelfUser().getAsTag());
            new ServerStatus().newStatuses();
            new Scheduler(
                    SchedulerConfig
                            .builder()
                            .minThreads(1)
                            .maxThreads(2)
                            .threadsKeepAliveTime(Duration.ofSeconds(5))
                            .build()
            ).schedule(new ServerStatus(), Schedules.fixedDelaySchedule(Duration.ofSeconds(10)));
        }
    

    The scheduled code:

    public class ServerStatus implements Runnable {
    
        public void BungeeStatus() {
            ArrayList<String> messages = new ArrayList<>();
            PteroClient api = PteroBuilder.createClient("https://panel.skynode.pro", Config.get("PTERO_TOKEN"));
            UtilizationState Powerstate = api.retrieveServerByIdentifier(Config.get("BUNGEE-SERVER-ID")).execute().retrieveUtilization().execute().getState();
            // Objects.requireNonNull(Bot.getJDA().getGuildById(Config.get("GUILD-ID")).getTextChannelById(Config.get("STATUS-CHANNEL"))).editMessageById().queue();
            try {
                File myObj = new File("status-messages.txt");
                Scanner myReader = new Scanner(myObj);
                while (myReader.hasNextLine()) {
                    messages.add(myReader.nextLine());
                }
                myReader.close();
            } catch (FileNotFoundException e) {
                System.out.println("An error occurred.");
                e.printStackTrace();
            }
            MessageEmbed embed = new EmbedBuilder()
                    .setTitle("Status: " + Powerstate.toString())
                    .setDescription("The server that you actually connect to!")
                    .setFooter("anarchy.ciputin.cf","https://i.imgur.com/i4ht6nZ.png")
                    .setColor(Determinestatecolor(Powerstate))
                    .build();
            Bot.getJDA().getGuildById(Config.get("GUILD-ID")).getTextChannelById(Config.get("STATUS-CHANNEL")).editMessageEmbedsById(messages.get(3), embed).queue();
        }
    
        @Override
        public void run() {
            if (Config.get("DEV").equals("true")) {return;}
            BungeeStatus();
            AnarchyStatus();
            LobbyStatus();
            SMPStatus();
        }
        public void newStatuses() {
            if (Config.get("DEV").equals("true")) {return;}
            /* Delete old Messages */
                List<Message> old = Bot.getJDA().getGuildById(Config.get("GUILD-ID")).getTextChannelById(Config.get("STATUS-CHANNEL"))
                        .getHistory()
                        .retrievePast(100)
                        .complete();
            /* Send new messages */
            Bot.getJDA().getGuildById(Config.get("GUILD-ID")).getTextChannelById(Config.get("STATUS-CHANNEL")).sendMessage("BungeeCord:").complete();
            Bot.getJDA().getGuildById(Config.get("GUILD-ID")).getTextChannelById(Config.get("STATUS-CHANNEL")).sendMessage("Anarchy:").complete();
            Bot.getJDA().getGuildById(Config.get("GUILD-ID")).getTextChannelById(Config.get("STATUS-CHANNEL")).sendMessage("Lobby:").complete();
            Bot.getJDA().getGuildById(Config.get("GUILD-ID")).getTextChannelById(Config.get("STATUS-CHANNEL")).sendMessage("SMP:").complete();
    
            /* Get history of said messages */
            List<Message> messages = Bot.getJDA().getGuildById(Config.get("GUILD-ID")).getTextChannelById(Config.get("STATUS-CHANNEL")).getIterableHistory().limit(4).complete();
    
            /* save history */
            try {
                FileWriter myWriter = new FileWriter("status-messages.txt");
                for (int i = 0, messagesSize = messages.size(); i < messagesSize; i++) {
                    Message message = messages.get(i);
                    myWriter.append(message.getId() + "\n");
                }
                myWriter.close();
            } catch (IOException e) {
                System.out.println("An error occurred.");
                e.printStackTrace();
            }
            for (Message message : old) {
                Bot.getJDA().getGuildById(Config.get("GUILD-ID")).getTextChannelById(Config.get("STATUS-CHANNEL")).deleteMessageById(message.getId()).complete();
            }
        }
    
        public void LobbyStatus() {
            ArrayList<String> messages = new ArrayList<>();
            PteroClient api = PteroBuilder.createClient("https://panel.skynode.pro", Config.get("PTERO_TOKEN"));
            UtilizationState Powerstate = api.retrieveServerByIdentifier(Config.get("LOBBY-SERVER-ID")).execute().retrieveUtilization().execute().getState();
            // Objects.requireNonNull(Bot.getJDA().getGuildById(Config.get("GUILD-ID")).getTextChannelById(Config.get("STATUS-CHANNEL"))).editMessageById().queue();
            try {
                File myObj = new File("status-messages.txt");
                Scanner myReader = new Scanner(myObj);
                while (myReader.hasNextLine()) {
                    messages.add(myReader.nextLine());
                }
                myReader.close();
            } catch (FileNotFoundException e) {
                System.out.println("An error occurred.");
                e.printStackTrace();
            }
            MessageEmbed embed = new EmbedBuilder()
                    .setTitle("Status: " + Powerstate.toString())
                    .setDescription("The server that you connect to on join!")
                    .setFooter("anarchy.ciputin.cf", "https://i.imgur.com/i4ht6nZ.png")
                    .setColor(Determinestatecolor(Powerstate))
                    .build();
            Bot.getJDA().getGuildById(Config.get("GUILD-ID")).getTextChannelById(Config.get("STATUS-CHANNEL")).editMessageEmbedsById(messages.get(1), embed).queue();
        }
    
        public void AnarchyStatus() {
            ArrayList<String> messages = new ArrayList<>();
            PteroClient api = PteroBuilder.createClient("https://panel.skynode.pro", Config.get("PTERO_TOKEN"));
            UtilizationState Powerstate = api.retrieveServerByIdentifier(Config.get("ANARCHY-SERVER-ID")).execute().retrieveUtilization().execute().getState();
            // Objects.requireNonNull(Bot.getJDA().getGuildById(Config.get("GUILD-ID")).getTextChannelById(Config.get("STATUS-CHANNEL"))).editMessageById().queue();
            try {
                File myObj = new File("status-messages.txt");
                Scanner myReader = new Scanner(myObj);
                while (myReader.hasNextLine()) {
                    messages.add(myReader.nextLine());
                }
                myReader.close();
            } catch (FileNotFoundException e) {
                System.out.println("An error occurred.");
                e.printStackTrace();
            }
            MessageEmbed embed = new EmbedBuilder()
                    .setTitle("Status: " + Powerstate.toString())
                    .setDescription("The main event :smiling_imp:")
                    .setFooter("anarchy.ciputin.cf","https://i.imgur.com/i4ht6nZ.png")
                    .setColor(Determinestatecolor(Powerstate))
                    .build();
            Bot.getJDA().getGuildById(Config.get("GUILD-ID")).getTextChannelById(Config.get("STATUS-CHANNEL")).editMessageEmbedsById(messages.get(2), embed).queue();
        }
    
        public void SMPStatus() {
            ArrayList<String> messages = new ArrayList<>();
            PteroClient api = PteroBuilder.createClient("https://panel.skynode.pro", Config.get("PTERO_TOKEN"));
            UtilizationState Powerstate = api.retrieveServerByIdentifier(Config.get("SMP-SERVER-ID")).execute().retrieveUtilization().execute().getState();
            // Objects.requireNonNull(Bot.getJDA().getGuildById(Config.get("GUILD-ID")).getTextChannelById(Config.get("STATUS-CHANNEL"))).editMessageById().queue();
            try {
                File myObj = new File("status-messages.txt");
                Scanner myReader = new Scanner(myObj);
                while (myReader.hasNextLine()) {
                    messages.add(myReader.nextLine());
                }
                myReader.close();
            } catch (FileNotFoundException e) {
                System.out.println("An error occurred.");
                e.printStackTrace();
            }
            MessageEmbed embed = new EmbedBuilder()
                    .setTitle("Status: " + Powerstate.toString())
                    .setDescription("The SMP Server!")
                    .setFooter("anarchy.ciputin.cf","https://i.imgur.com/i4ht6nZ.png")
                    .setColor(Determinestatecolor(Powerstate))
                    .build();
            Bot.getJDA().getGuildById(Config.get("GUILD-ID")).getTextChannelById(Config.get("STATUS-CHANNEL")).editMessageEmbedsById(messages.get(0), embed).queue();
        }
        private Color Determinestatecolor(UtilizationState Powerstate) {
            if (String.valueOf(Powerstate) != "RUNNING") { return Color.RED; } else { return Color.blue; }
        }
    }
    

    The container (this runs in a docker container for java) is not out of memory, but is actually only using 200/1024mb. Another issue is that with each execution, the memory usage gets higher, with no relief. But it levels out at 200mb, coincidentally when it stops working...

    opened by cixastra 4
  • Kotlin Cron Support

    Kotlin Cron Support

    First of all, I love your library!

    I am developing a Kotlin app on JVM and I'm running into the above issues trying to create a basic CronSchedule as follows:

    image

    I know this is a Java library but you wouldn't happen to know how to get this working?

    image

    The Fixed schedule works beautifully but Cron in Kotlin would really put it over the top.

    opened by marklapasa 4
  • Forcing executing a job

    Forcing executing a job

    I'm using Wisp to schedule a bunch of processes, using Cron expressions. I would like to be able to force run a job before its due date. I see this is possible running directly the Runnable from a Job instance, but in this way the run count and latest run timestamp are not updated. From what I see from the source code I can't update externally those values; is there any workaround? It is something that you would like to add to the project? I could provide a pull request if so.

    opened by mbigatti 3
  • Wait for job to complete

    Wait for job to complete

    I have a job to run every 5 seconds but I want to wait for it finish before going next run

    public class CustomJob implements Runnable {

    private JobVO jobVO;
    
    public CustomJob(JobVO jobVO) {
        this.jobVO = jobVO;
    }
    
    public void run() {
    
        System.out.println("Job is running..." + jobVO.getName());
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    

    }

    It ignore thread.sleep and run again.

    opened by muhdkhokhar 3
  • Bump cron-utils from 8.0.0 to 9.1.3

    Bump cron-utils from 8.0.0 to 9.1.3

    Bumps cron-utils from 8.0.0 to 9.1.3.

    Release notes

    Sourced from cron-utils's releases.

    9.1.3

    Update cron definition to match Spring cron documentation. Remove dependency on Hibernate validation, to avoid security vulnerability. Many thanks to @pwntester!

    9.1.2

    Release 9.1.2 solves the following issues:

    We also enhanced handling of DST for next/past executions (#213), removed multiple slf4j bindings (#434), and avoided declaring Lombok as a transitive dependency (#437).

    Thanks to @jaguililla @natros @francisdb @benoitbb @albuhuba @lpbak @pangyikhei @lowell80 @LennyKarpel @Naxos84 @Blemicek @sbandler @IndeedSi @HongZhaoHua @littleYanzi @albertotn @edmeme @zhanxingcheng @zhanxingcheng @charchithere @barunhalderkolkata for reporting, discussing and providing solutions to the issues above!

    9.1.1

    In case of a cron urgency, call the 9.1.1 release 😄

    9.1.0

    For details regarding this release, please check milestone 9.1.0 Many thanks to @pangyikhei @rymsha @Eyal-Shalev @alsereda @melonhead901 @fi3te @skyline75489 @tudobmb @albertotn @albertotn @francisdb @marcoslarsen @Fantazjatyk @flamezealot @yangjiajun2014 @NikitaNovozhilovWork @alexshtin @mfateev @SJX516 @dimitripunch @pkoenig10 for reporting issues, contributing tests, documentation and fixes!

    9.0.2

    No release notes provided.

    9.0.1

    No release notes provided.

    9.0.0

    No release notes provided.

    8.1.1

    No release notes provided.

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 2
  • Bump junit from 4.12 to 4.13.1

    Bump junit from 4.12 to 4.13.1

    Bumps junit from 4.12 to 4.13.1.

    Release notes

    Sourced from junit's releases.

    JUnit 4.13.1

    Please refer to the release notes for details.

    JUnit 4.13

    Please refer to the release notes for details.

    JUnit 4.13 RC 2

    Please refer to the release notes for details.

    JUnit 4.13 RC 1

    Please refer to the release notes for details.

    JUnit 4.13 Beta 3

    Please refer to the release notes for details.

    JUnit 4.13 Beta 2

    Please refer to the release notes for details.

    JUnit 4.13 Beta 1

    Please refer to the release notes for details.

    Commits
    • 1b683f4 [maven-release-plugin] prepare release r4.13.1
    • ce6ce3a Draft 4.13.1 release notes
    • c29dd82 Change version to 4.13.1-SNAPSHOT
    • 1d17486 Add a link to assertThrows in exception testing
    • 543905d Use separate line for annotation in Javadoc
    • 510e906 Add sub headlines to class Javadoc
    • 610155b Merge pull request from GHSA-269g-pwp5-87pp
    • b6cfd1e Explicitly wrap float parameter for consistency (#1671)
    • a5d205c Fix GitHub link in FAQ (#1672)
    • 3a5c6b4 Deprecated since jdk9 replacing constructor instance of Double and Float (#1660)
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 2
  • Why is JobThread marked as daemon?

    Why is JobThread marked as daemon?

    Hi, I was looking for simple scheduler library supporting cron expressions and I came across this one. I have a question though:

    How is scheduler intended to work when JobThread is marked as daemon? In my very simple scenario I just create Scheduler and schedule some jobs that I want to run regularly every day, but after main method finishes, JVM terminates because there are no non-daemon threads. What is the reasoning behind marking these threads as daemon? I think JobThread should not be marked as daemon by default or am I missing something?

    opened by bukefalos 2
  • Move away from cron-utils

    Move away from cron-utils

    Cron-utils does not follow the spirit of Wisp: keep it small and efficient. Moreover, cron-utils has recurring security issues due to poor design choices and especially relying to javax.el.

    https://github.com/frode-carlsen/cron/tree/master/java8 seem a good candidate: everything is contained is one class of 650 lines, without any dependencies. Though it does not seem to be available on Maven central.

    opened by amanteaux 0
  • Bump cron-utils from 9.1.5 to 9.1.6

    Bump cron-utils from 9.1.5 to 9.1.6

    Bumps cron-utils from 9.1.5 to 9.1.6.

    Commits
    • d78d40b pom.xml -> 9.1.6
    • 9c73298 Update pom.xml towards JDK8, for a compatible release.
    • 528fcae Issue #493: update code towards one of the proposed solutions.
    • cfd2880 Merge pull request #494 from NielsDoucet/RCE-fix
    • d670750 Merge pull request #493 from pwntester/patch-1
    • 6f91560 Merge branch 'hibnico-fix-interval-mapping'
    • d95759b Fix mapping of interval for the day of week
    • 9c93c17 Resolve RCE vulnerability.
    • d7c6e3c Update CronValidator.java
    • 2cf9697 Merge pull request #492 from albertotn/description-italian
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 0
  • Use virtual threads instead of a thread pool

    Use virtual threads instead of a thread pool

    Virtual threads are becoming a reality since Java 19, though currently they are only available through preview usage of the JVM: --enable-preview.

    When virtual threads are fully part of the JVM, Wisp core should be rewritten to use virtual threads:

    • It should actually consume less hardware resources
    • The core code will be smaller and easier to maintain
    • There will be no more need to configure the thread pool since virtual thread pooling is not recommended
    enhancement 
    opened by amanteaux 0
  • Log next schedule after completion

    Log next schedule after completion

    Thank you, this is a great library and very useful. One thing that would be handy is to know the next time a job is going to run after it has completed.

    We've a lot of jobs scheduled, so I'd rather not iterate through and log out all their stats on a separate fixed schedule.

    Perhaps you could add a post execution hook, or simply debug log the result in Scheduler.scheduleNextExecution?

    opened by andywilson 3
  • NOT AN ISSUE _ Compliment.

    NOT AN ISSUE _ Compliment.

    I'd like to say big thanks. I used this library in my big project. I created a customer scheduler that allows to run between certain time and in that timezone.

    Even having 100x job it works smooth

    Thanks.

    opened by muhdkhokhar 3
Releases(2.3.0)
  • 2.3.0(Sep 13, 2022)

    Changelog

    This release proposes a new Cron implementation dependency. Much lighter than the previous one, the new Cron library used:

    • Has no dependency
    • Does not use any dynamic code nor interpret any type of execution code (no Java reflection etc.)
    • Proposes a computation algorithm similar to the old one used
    • Support standard Cron expression style as well as the extended Quartz format (without the 7th year field)

    It brings to Wisp:

    • A much safer code dependency: this library will never contain any critical security issue. The worst case security issue the library may face is denial of service through Cron expression interpretation (that is still a lot better than remote code execution...!)
    • A lighter dependency: cron-utils jar file is 170ko whereas the new dependency is only 20ko. Moreover cron-utils is relying on jakarta.el which jar file is 230ko
    • Less maintenance: cron-utils proposes many features useless for Wisp and for many projects using Wisp. As a consequence, to bring all these features and corresponding fixes, there are many releases of cron-utils. The new Cron library used by Wisp contains only Cron expression parsing and next dates calculation. That's exactly what Wisp requires. And now that this code works, it's not likely to evolve much in the future

    Breaking change and upgrade instructions

    The usage of cron-utils is now deprecated. The related code will be removed in Wisp 3. However, it's still advised to do the migration when it's possible: it will bring more safety to projects using Wisp with Cron.

    The changes to consider are:

    • Update the pom.xml file to replace cron-utils by the new Cron library. So this dependency:
    <dependency>
      <groupId>com.cronutils</groupId>
      <artifactId>cron-utils</artifactId>
      <version>9.1.6</version>
    </dependency>
    

    Must be replaced by:

    <dependency>
      <groupId>ch.eitchnet</groupId>
      <artifactId>cron</artifactId>
      <version>1.6.2</version>
    </dependency>
    
    • Upgrade of Cron expressions used: cron-utils proposes 7 fields for Cron expression (the last one is the year), whereas the new cron library proposes two options: 5 fields (minute precision) and 6 fields (second precision). It means that if a Cron expression with 7 fields is used, the last field (the year) must be removed in order to be compatible with the new Cron library. For example, the Quartz expression 0 0 12 * * ? * must be translated to 0 0 12 * * ?. Most of the time, the year field is set to put an unreachable date, this can be accomplished by setting the expected date to a 31st of February: * * * 31 2 *
    • To parse Cron expression using the new library:
      • CronExpressionSchedule.parse() must be used to parse a 5 fields Cron expression (Unix standard), so without a second field
      • CronExpressionSchedule.parseWithSeconds() must be used to parse a 6 fields Cron expression, so the first field is the second

    Maven

    <dependency>
      <groupId>com.coreoz</groupId>
      <artifactId>wisp</artifactId>
      <version>2.3.0</version>
    </dependency>
    
    Source code(tar.gz)
    Source code(zip)
  • 2.2.2(Nov 16, 2021)

    Changelog

    • Upgrade cron-utils from version 9.1.5 to version 9.1.6 (critical security issue)

    Maven

    <dependency>
        <groupId>com.coreoz</groupId>
        <artifactId>wisp</artifactId>
        <version>2.2.2</version>
    </dependency>
    
    Source code(tar.gz)
    Source code(zip)
  • 2.2.1(Jun 22, 2021)

    Changelog

    • Upgrade cron-utils from version 8.0.0 to version 9.1.5 (security issue)
    • Upgrade junit (testing) from version 4.12 to version 4.13.2 (security issue)
    • Upgrade assertj-core (testing) and lombok (dev only)

    Maven

    <dependency>
        <groupId>com.coreoz</groupId>
        <artifactId>wisp</artifactId>
        <version>2.2.1</version>
    </dependency>
    
    Source code(tar.gz)
    Source code(zip)
  • 2.2.0(Feb 13, 2020)

    Changelog

    • #8 Removed cached thread pool creation
    • #4 Enable to customize the time zone for CronSchedule

    Maven

    <dependency>
        <groupId>com.coreoz</groupId>
        <artifactId>wisp</artifactId>
        <version>2.2.0</version>
    </dependency>
    
    Source code(tar.gz)
    Source code(zip)
  • 2.1.0(May 17, 2019)

    Changelog

    • Do not execute the launcher thread in the job thread pool to avoid confusion
    • Add some thread pool statistics
    • Fix composition schedules to enable scheduling again a cancelled job
    • Add job last started time information (so the last job duration can be retrieved)
    • Job.timeInMillisSinceJobRunning() and Job.lastExecutionTimeInMillis() are deprecated in favor of Job.lastExecutionStartedTimeInMillis() and Job.lastExecutionEndedTimeInMillis()

    Maven

    <dependency>
        <groupId>com.coreoz</groupId>
        <artifactId>wisp</artifactId>
        <version>2.1.0</version>
    </dependency>
    
    Source code(tar.gz)
    Source code(zip)
  • 2.0.1(Mar 3, 2019)

    Changelog

    • Scheduler minimum thread value takes in consideration the launcher thread

    Maven

    <dependency>
        <groupId>com.coreoz</groupId>
        <artifactId>wisp</artifactId>
        <version>2.0.1</version>
    </dependency>
    
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0(Mar 3, 2019)

    Changelog

    Upgrade instructions from 1.x.x version to 2.x.x version

    • If a cron schedule is used, then cron-utils must be upgraded to version 8.0.0
    • Constructors Scheduler(int maxThreads, long minimumDelayInMillisToReplaceJob) and Scheduler(int maxThreads, long minimumDelayInMillisToReplaceJob, TimeProvider timeProvider) are deprecated in favor of Scheduler(SchedulerConfig config)
    • The monitor for long running jobs detection might be configured

    Maven

    <dependency>
        <groupId>com.coreoz</groupId>
        <artifactId>wisp</artifactId>
        <version>2.0.0</version>
    </dependency>
    
    Source code(tar.gz)
    Source code(zip)
Owner
Coreoz
Coreoz
Code for Quartz Scheduler

Quartz Scheduler Quartz is a richly featured, open source job scheduling library that can be integrated within virtually any Java application - from t

Quartz Job Scheduler 5.4k Jan 5, 2023
The simple, stupid batch framework for Java

Easy Batch The simple, stupid batch framework for Java™ Project status As of November 18, 2020, Easy Batch is in maintenance mode. This means only bug

Jeasy 571 Dec 29, 2022
An extremely easy way to perform background processing in Java. Backed by persistent storage. Open and free for commercial use.

The ultimate library to perform background processing on the JVM. Dead simple API. Extensible. Reliable. Distributed and backed by persistent storage.

JobRunr 1.3k Jan 6, 2023
Cron utils for parsing, validations and human readable descriptions as well as date/time interoperability.

cron-utils We define crons. And support them. cron-utils is a Java library to define, parse, validate, migrate crons as well as get human readable des

jmrozanec 965 Dec 30, 2022
Elide is a Java library that lets you stand up a GraphQL/JSON-API web service with minimal effort.

Elide Opinionated APIs for web & mobile applications. Read this in other languages: 中文. Table of Contents Background Documentation Install Usage Secur

Yahoo 921 Jan 3, 2023
Eclipse Foundation 3k Dec 31, 2022
Eclipse OpenJ9: A Java Virtual Machine for OpenJDK that's optimized for small footprint, fast start-up, and high throughput.

Eclipse OpenJ9: A Java Virtual Machine for OpenJDK that's optimized for small footprint, fast start-up, and high throughput. Builds on Eclipse OMR (https://github.com/eclipse/omr) and combines with the Extensions for OpenJDK for OpenJ9 repo.

null 3k Jan 3, 2023
AllPairs4J - an open source Java library for generation of minimal set of test combinations

AllPairs4J AllPairs4J is an open source Java library for generation of minimal set of test combinations. AllPairs4J is a Java port of allpairspy proje

Pavel Nazimok 5 Dec 11, 2022
Daily mail subscription implementation using Java Spring-boot and Quartz Scheduler

Daily Mail Subscription Service POC Implemented using Java Spring-boot and Quartz Scheduler Working Application Exposing 3 endpoints /subscription/cre

null 16 Jun 3, 2022
A minimal WHIP implementation for the Raspberry Pi. It sends Mic and Camera to a WHIP endpoint

whipi A minimal WHIP implementation for the Raspberry Pi. It sends Camera Mic to a WHIP endpoint. Requires a Raspberry Pi with a PiCam and Java 11. It

|pipe| 12 Oct 27, 2022
Persistent cluster-friendly scheduler for Java

db-scheduler Task-scheduler for Java that was inspired by the need for a clustered java.util.concurrent.ScheduledExecutorService simpler than Quartz.

Gustav Karlsson 714 Dec 31, 2022
Code for Quartz Scheduler

Quartz Scheduler Quartz is a richly featured, open source job scheduling library that can be integrated within virtually any Java application - from t

Quartz Job Scheduler 5.4k Jan 5, 2023
A minimal sit mod for Fabric & Quilt using Polymer.

Polysit A polymer-based server-side sit mod for Fabric & Quilt! How to Use? Requirements: Minecraft 1.18.2 Polymer Polysit One of either mod loader: Q

The Glitch 5 Jun 30, 2022
This bare project template includes a minimal setup for using unimodules with React Native tvOS.

What This is a clone of expo's bare minimal template which includes a minimal setup for using unimodules with React Native. Additionally, this templat

Abdullah Musab Ceylan 4 Dec 25, 2022
Telegram API Client and Telegram BOT API Library and Framework in Pure java.

Javagram Telegram API Client and Telegram Bot API library and framework in pure Java. Hello Telegram You can use Javagram for both Telegram API Client

Java For Everything 3 Oct 17, 2021
Jakarta money is a helpful library for a better developer experience when combining Money-API with Jakarta and MicroProfile API.

jakarta-money Jakarta money is a helpful library for a better developer experience when combining Money-API with Jakarta and MicroProfile API. The dep

Money and Currency API | JavaMoney 19 Aug 12, 2022
A simple java wrapper library for alquran-cloud api 🤍☕

A simple java wrapper library for alquran-cloud api ?? It is still under development. ?? How to add this library into your project Maven Step 1. Add t

Anas Elgarhy 14 Oct 9, 2022