{"id":13485883,"url":"https://github.com/Coreoz/Wisp","last_synced_at":"2025-03-27T19:31:48.030Z","repository":{"id":46072886,"uuid":"66705171","full_name":"Coreoz/Wisp","owner":"Coreoz","description":"A simple Java Scheduler library with a minimal footprint and a straightforward API","archived":false,"fork":false,"pushed_at":"2024-11-07T17:40:16.000Z","size":233,"stargazers_count":133,"open_issues_count":4,"forks_count":23,"subscribers_count":11,"default_branch":"master","last_synced_at":"2024-11-07T18:35:29.403Z","etag":null,"topics":["cron","java","java-scheduler","job-scheduler","periodic-jobs","periodic-tasks","schedule","scheduler","wisp"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Coreoz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-08-27T08:48:56.000Z","updated_at":"2024-11-07T17:40:20.000Z","dependencies_parsed_at":"2022-07-22T22:32:15.857Z","dependency_job_id":"632ffc31-ef83-4a86-83ce-dc9960ded68e","html_url":"https://github.com/Coreoz/Wisp","commit_stats":{"total_commits":200,"total_committers":5,"mean_commits":40.0,"dds":0.51,"last_synced_commit":"49e3913139ce82c3a8739d5966b0c06f90d87537"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Coreoz%2FWisp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Coreoz%2FWisp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Coreoz%2FWisp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Coreoz%2FWisp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Coreoz","download_url":"https://codeload.github.com/Coreoz/Wisp/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245910922,"owners_count":20692517,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cron","java","java-scheduler","job-scheduler","periodic-jobs","periodic-tasks","schedule","scheduler","wisp"],"created_at":"2024-07-31T18:00:33.364Z","updated_at":"2025-03-27T19:31:47.598Z","avatar_url":"https://github.com/Coreoz.png","language":"Java","readme":"Wisp Scheduler\n==============\n\n[![Build Status](https://github.com/Coreoz/Wisp/actions/workflows/maven.yml/badge.svg)](./actions)\n[![Coverage Status](https://coveralls.io/repos/github/Coreoz/Wisp/badge.svg?branch=master)](https://coveralls.io/github/Coreoz/Wisp?branch=master)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.coreoz/wisp/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.coreoz/wisp)\n\nWisp is a library for managing the execution of recurring Java jobs.\nIt works like the Java class `ScheduledThreadPoolExecutor`, but it comes with some advanced features:\n- [Jobs can be scheduled to run](#schedules) according to: a fixed hour (e.g. 00:30), a CRON expression, or a custom code-based expression,\n- [Statistics](#statistics) about each job execution can be retrieved,\n- A [too long jobs detection mechanism](#long-running-jobs-detection) can be configured,\n- The [thread pool can be configured to scale down](#scalable-thread-pool) when there is less jobs to execute concurrently.\n\nWisp weighs only 30Kb and has zero dependency except SLF4J for logging.\nIt will try to only create threads that will be used: if one thread is enough to run all the jobs,\nthen only one thread will be created.\nA second thread will generally be created only when 2 jobs have to run at the same time.\n\nThe scheduler precision will depend on the system load.\nThough a job will never be executed early, it will generally run after 1ms of the scheduled time.\n\nWisp is compatible with Java 8 and higher.\n\nGetting started\n---------------\n\nInclude Wisp in your project:\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.coreoz\u003c/groupId\u003e\n    \u003cartifactId\u003ewisp\u003c/artifactId\u003e\n    \u003cversion\u003e2.5.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nSchedule a job:\n```java\nScheduler scheduler = new Scheduler();\n\nscheduler.schedule(\n    () -\u003e System.out.println(\"My first job\"),           // the runnable to be scheduled\n    Schedules.fixedDelaySchedule(Duration.ofMinutes(5)) // the schedule associated to the runnable\n);\n```\nDone!\n\nA project should generally contain only one instance of a `Scheduler`.\nSo either a dependency injection framework handles this instance,\nor either a static instance of `Scheduler` should be created.\n\nIn production, it is generally a good practice to configure the\n[monitor for long running jobs detection](#long-running-jobs-detection).\n\nChangelog and upgrade instructions\n----------------------------------\nAll the changelog and the upgrades instructions are available\nin the [project releases page](https://github.com/Coreoz/Wisp/releases).\n\nSchedules\n---------\n\nWhen a job is created or done executing, the schedule associated to the job\nis called to determine when the job should next be executed.\nThere are multiple implications:\n- the same job will never be executed twice at a time,\n- if a job has to be executed at a fixed frequency,\nthen the job has to finish running before the next execution is scheduled ;\nelse the next execution will likely be skipped (depending of the `Schedule` implementation). \n\n### Basics schedules\nBasics schedules are referenced in the `Schedules` class:\n- `fixedDelaySchedule(Duration)`: execute a job at a fixed delay after each execution. The delay is not guaranteed to be consistent depending on system load\n- `fixedFrequencySchedule(Duration)`: execute a job at a fixed frequency independent of the time the method was called and the system load (like cron)\n- `executeAt(String)`: execute a job at the same time every day, e.g. `executeAt(\"05:30\")`\n\n### Composition\nSchedules are very flexible and can easily be composed, e.g:\n- `Schedules.afterInitialDelay(Schedules.fixedDelaySchedule(Duration.ofMinutes(5)), Duration.ZERO)`:\nthe job will be first executed ASAP and then with a fixed delay of 5 minutes between each execution,\n- `Schedules.executeOnce(Schedules.executeAt(\"05:30\"))`: the job will be executed once at 05:30.\n- `Schedules.executeOnce(Schedules.fixedDelaySchedule(Duration.ofSeconds(10)))`:\nthe job will be executed once 10 seconds after it has been scheduled.\n\n### Cron\nSchedules can be created using [cron expressions](https://en.wikipedia.org/wiki/Cron#CRON_expression).\nThis feature is made possible by the use of [cron library](https://github.com/frode-carlsen/cron). This library is very lightweight: it has no dependency and is made of a single Java class of 650 lines of code.\n\nSo to use cron expression, this library has to be added:\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ech.eitchnet\u003c/groupId\u003e\n    \u003cartifactId\u003ecron\u003c/artifactId\u003e\n    \u003cversion\u003e1.6.2\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nThen to create a job which is executed every hour at the 30th minute,\nyou can create the [schedule](#schedules) using: `CronExpressionSchedule.parse(\"30 * * * *\")`.\n\n`CronExpressionSchedule` exposes two methods to create Cron expressions:\n- `CronExpressionSchedule.parse()` to parse a 5 fields Cron expression (Unix standard), so without a second field\n- `CronExpressionSchedule.parseWithSeconds()` to parse a 6 fields Cron expression, so the first field is the second\n\n\nCron expression should be checked using a tool like:\n- [Cronhub](https://crontab.cronhub.io/)\n- [Freeformater](https://www.freeformatter.com/cron-expression-generator-quartz.html) *but be careful to not include the year field. So for the Cron expression `25 * * * * * *` (to run every minute at the second 25), the correct expression must be `25 * * * * *`*\n\nSometimes 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:\n- `* * 31 2 *` when used with `CronExpressionSchedule.parse()`\n- `* * * 31 2 *` when used with `CronExpressionSchedule.parseWithSeconds()`\n\nCron-utils was the default Cron implementation before Wisp 2.2.2. This has [changed in version 2.3.0](/../../issues/14).\nDocumentation about cron-utils implementation can be found at [Wisp 2.2.2](/../../tree/2.2.2#cron).\nMigration from cron-utils is detailed in the [release note of Wisp 2.3.0](/../../releases/tag/2.3.0).\n\n### Custom schedules\nCustom schedules can be created,\nsee the [Schedule](src/main/java/com/coreoz/wisp/schedule/Schedule.java) interface.\n\n### Past schedule\nSchedules can reference a past time.\nHowever once a past time is returned by a schedule,\nthe associated job will never be executed again.\nAt the first execution, if a past time is referenced a warning will be logged\nbut no exception will be raised.\n\nStatistics\n----------\nTwo methods enable to fetch scheduler statistics:\n- `Scheduler.jobStatus()`: To fetch all the jobs executing on the scheduler. For each job, these data are available:\n  - name,\n  - status (see `JobStatus` for details),\n  - executions count,\n  - last execution start date,\n  - last execution end date,\n  - next execution date.\n- `Scheduler.stats()`: To fetch statistics about the underlying thread pool:\n  - min threads,\n  - max threads,\n  - active threads running jobs,\n  - idle threads,\n  - largest thread pool size.\n\nCleanup old terminated jobs\n---------------------------\nThe 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()`.\n\nFor an application that creates lots of jobs, to enable avoid memory leak, a cleaning job should be scheduled, for example:\n```java\nscheduler.schedule(\n    \"Terminated jobs cleaner\",\n    () -\u003e scheduler\n        .jobStatus()\n        .stream()\n        .filter(job -\u003e job.status() == JobStatus.DONE)\n        // Clean only jobs that have finished executing since at least 10 seconds\n        .filter(job -\u003e job.lastExecutionEndedTimeInMillis() \u003c (System.currentTimeMillis() - 10000))\n        .forEach(job -\u003e scheduler.remove(job.name())),\n    Schedules.fixedDelaySchedule(Duration.ofMinutes(10))\n);\n```\n\nLong running jobs detection\n---------------------------\n\nTo detect jobs that are running for too long, an optional job monitor is provided.\nIt can be setup with:\n```java\nscheduler.schedule(\n    \"Long running job monitor\",\n    new LongRunningJobMonitor(scheduler),\n    Schedules.fixedDelaySchedule(Duration.ofMinutes(1))\n);\n```\nThis way, every minute, the monitor will check for jobs that are running for more than 5 minutes.\nA warning message with the job stack trace will be logged for any job running for more than 5 minutes.\n\nThe detection threshold can also be configured this way: `new LongRunningJobMonitor(scheduler, Duration.ofMinutes(15))`\n\nScalable thread pool\n--------------------\n\nBy default the thread pool size will only grow up, from 0 to 10 threads (and not scale down).\nBut it is also possible to define a maximum keep alive duration after which idle threads will be removed from the pool.\nThis can be configured this way:\n```java\nScheduler scheduler = new Scheduler(\n    SchedulerConfig\n        .builder()\n        .minThreads(2)\n        .maxThreads(15)\n        .threadsKeepAliveTime(Duration.ofHours(1))\n        .build()\n);\n```\nIn this example:\n- There will be always at least 2 threads to run the jobs,\n- The thread pool can grow up to 15 threads to run the jobs,\n- Idle threads for at least an hour will be removed from the pool, until the 2 minimum threads remain.\n\nPlume Framework integration\n---------------------------\n\nIf you are already using [Plume Framework](https://github.com/Coreoz/Plume),\nplease take a look at [Plume Scheduler](https://github.com/Coreoz/Plume/tree/master/plume-scheduler).\n\n","funding_links":[],"categories":["Projects","项目","任务调度"],"sub_categories":["Job Scheduling","作业调度"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCoreoz%2FWisp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FCoreoz%2FWisp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCoreoz%2FWisp/lists"}