Skip to content
JuliaServicesPublic

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Latest commit

 

History

37 Commits

Folders and files

Repository files navigation

Tempus.jl

Build Status Coverage Documentation Dev Documentation

Overview

Tempus.jl is a lightweight, Quartz-inspired job scheduling library for Julia. It provides an easy-to-use API for defining cron-like schedules and executing jobs at specified times.

Features

  • Define jobs using cron-style scheduling expressions
  • Support for job execution policies (overlap handling, retries, failure strategies)
  • Backend-neutral state through AbstractStores.jl
  • Thread-safe scheduling with concurrency-aware execution
  • Dynamic job control (enable, disable, unschedule jobs)
  • Supports retry policies with exponential backoff

Installation

Tempus.jl is registered in the Julia General registry:

using Pkg
Pkg.add("Tempus")

Quick Start

Defining and Scheduling a Job

using Tempus

# Define a job that prints a message every minute
job = Tempus.Job(
    () -> println("Hello from Tempus!"),
    "example_job",
    "* * * * *",
)

# Create an in-memory scheduler
scheduler = Tempus.Scheduler()

# Add the job to the scheduler
push!(scheduler, job)

# Start the scheduler (runs in a background thread)
Tempus.run!(scheduler)

One-Shot Jobs

# runs once, as soon as the scheduler picks it up, then is disabled;
# failed attempts are re-run until success or max_failed_executions
job = Tempus.OneShotJob(send_report, "send_report_now")
push!(scheduler, job)

Disabling and Enabling Jobs

Tempus.disable!(job)  # Prevents the job from running
Tempus.enable!(job)   # Allows it to run again

These mutate the Job object. To disable a job in a persisting store so it stays disabled across restarts, use Tempus.disableJob!(store, job).

Removing a Job

Tempus.unschedule!(scheduler, job)

Choosing a State Backend

Tempus stores job records and bounded execution history as two namespaced views over one AbstractStores backend. The backend must accept heterogeneous values, i.e. be an AbstractStore{Any}; any codec works, including FileStore(dir; codec=JSONCodec()) (needs using JSON). The default scheduler uses process memory:

using AbstractStores, Tempus

backend = MemoryStore()
store = Tempus.Store(backend)
scheduler = Tempus.Scheduler(store)

Change only the backend to persist jobs and execution history across restarts:

backend = FileStore(joinpath(homedir(), ".tempus", "state"))
scheduler = Tempus.Scheduler(backend)

The same form works with RedisStore (needs using Redis) and SQLStore (needs using DBInterface plus a driver), whose methods live in AbstractStores package extensions. One backend can also hold state for several libraries and the application because each user gets a separate prefix:

backend = SQLStore{Any}(connection; table="service_state")
tempus = Tempus.Store(backend; prefix="tempus/")

Tempus.InMemoryStore(), Tempus.FileStore(directory), and Tempus.SQLiteStore(connection) remain as convenience constructors. The Tempus 2 file store used one JSON file; the new file constructor takes a directory and writes one atomic file per key, so state written by Tempus 2 is not read or migrated — a scheduler started against an old jobs.dat starts empty. Execution history is now persisted too, which Tempus 2's file store did not do.

A store persists each job's definition (name, schedule, options, disabled state) and execution history, but not its action: the action lives in the Tempus.Store object the job was added to with Tempus.addJob! (or push!(scheduler, job)). Add every job at startup; any function works, including closures over application state. A scheduler skips stored jobs that have no action in this process. To run jobs a previous process stored without adding them again, call Tempus.restoreJobs!(store), which resolves each named function by its module path.

Treat a persistent store's contents as trusted input: the default backends use Julia's Serialization, and deserializing attacker-controlled data can execute arbitrary code — whoever can write to the store's directory, database, or Redis keyspace can run code in any process that reopens it. Protect the backing storage with filesystem or database permissions accordingly.

Compiling with juliac --trim

The scheduler and cron parsing compile into trimmed binaries (juliac --trim=safe) with a MemoryStore backend on Julia 1.13 and later, and with a JSONCodec store on Julia 1.14 and later (Julia 1.13's trim verifier rejects JSON.jl's typed decoding of nested structs). Other backends, such as the default SerializedCodec, are not trim-safe. In a trimmed binary:

  • create jobs at run time and add them at every startup; restoreJobs! and resolve_function use reflection and are unavailable;
  • pass job parameters by capturing them in the action instead of job_params, which are converted to keyword arguments from JSON at every call;
  • declare a retry_check's exception argument @nospecialize: retry_check=(state, @nospecialize(e)) -> e isa MyError.

Timezone-aware schedules load TimeZones' database, which is not trim-safe; that code is only compiled into programs that create jobs with a timezone.

Cron Syntax

Tempus.jl uses standard cron syntax, with an optional leading seconds field:

* * * * *      → Every minute
0 12 * * *     → Every day at noon
*/5 * * * *    → Every 5 minutes
30 4 1,15 * *  → 4:30 AM on the 1st and 15th
0 22 * * 1-5   → 10 PM on weekdays
# a 6th trailing field is never needed; a leading seconds field is optional
* * * * * *    → Every second

Fields are minute hour day-of-month month day-of-week (or with seconds first when six fields are given). Supported field forms: *, single values, ranges (1-5), lists (1,3,5), and steps (*/15, 10-30/7 — steps count from the range start, so 10-30/7 fires at 10, 17, and 24). Day-of-week runs Sunday to Saturday as 0–6, with 7 also accepted for Sunday. Three-letter, case-insensitive names work for months and weekdays (0 0 1 JAN *, 0 0 * * MON-FRI), and when both day-of-month and day-of-week are restricted, a day matching either fires (standard cron behavior). The common aliases @yearly/@annually, @monthly, @weekly, @daily/@midnight, and @hourly are also accepted.

All schedules are evaluated in UTC unless the job is given a timezone option (an IANA name like "America/Denver"), in which case the schedule is interpreted in that timezone: spring-forward gaps are skipped and fall-back ambiguities use the first occurrence.

Contributing

Contributions are welcome! To contribute:

  1. Fork the repository.
  2. Create a new branch with your feature or bugfix.
  3. Submit a pull request with your changes.

Make sure to include tests for any new functionality.

License

Tempus.jl is licensed under the MIT License.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

Generated from quinnj/Example.jl