Skip to content

Repository files navigation

eo-scheduler (eos)

A lightweight job scheduling library for Java 21+. It provides a simple, modular, and dependency-free core domain for job scheduling. It can run in-memory for small applications or use a durable backend (like PostgreSQL) for distributed environments.

System Architecture & Constraints (Crucial for AI Agents & Developers)

If you (or an AI agent) are extending, using, or contributing to this library, you must adhere to the following strict rules:

1. Semantic Guarantees

  • At-Least-Once Delivery: The system guarantees that a job will run at least once. Due to lease expirations or worker crashes, a job might run more than once. All Job.act() implementations MUST be idempotent.
  • No Time Drift: When calculating the next execution time, the system passes the original scheduled time (Ticket.dueTime()) to Routine.next(after), never the actual completion time (Instant.now()).
  • Distributed Locking: The JDBC implementation relies on SELECT ... FOR UPDATE SKIP LOCKED. This ensures atomic claims across multiple pods without requiring a separate consensus system (like ZooKeeper).
  • Timezone Transparency: Routines must not rely on system default timezones. For instance, CronRoutine requires an explicit ZoneId in its constructor.

2. Concurrency Model

  • Virtual Threads: The scheduler loop (eos-ticker) uses Java 21+ Thread.ofVirtual(). It spawns a new virtual thread for every claimed ticket and a separate background virtual thread to continuously renew the lease while the job is running. Do not use traditional thread pools.

Modular Structure

The project is divided into distinct Maven modules. You only import what you need.

  • eos-core: The pure Java domain model (7 interfaces). Zero external dependencies.
  • eos-ram: In-memory implementations (RamAgenda, RamQueue, IntervalRoutine, etc.).
  • eos-decorators: Standard decorators (LoggedJob, SafeJob, RetryJob, DeadLetterJob).
  • eos-ticker: The scheduler loop utilizing Java 21 Virtual Threads.
  • eos-jdbc: Durable persistence using JDBC.

Installation (via JitPack)

This project is deployed via JitPack.

First, add the JitPack repository to your pom.xml:

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

In-Memory (RAM) Setup

For lightweight, single-instance applications:

<dependencies>
    <dependency>
        <groupId>com.github.maniac4j</groupId>
        <artifactId>eos-core</artifactId>
        <version>Tag</version>
    </dependency>
    <dependency>
        <groupId>com.github.maniac4j</groupId>
        <artifactId>eos-ram</artifactId>
        <version>Tag</version>
    </dependency>
    <dependency>
        <groupId>com.github.maniac4j</groupId>
        <artifactId>eos-ticker</artifactId>
        <version>Tag</version>
    </dependency>
</dependencies>

Distributed (JDBC) Setup

For clustered environments requiring persistence:

<dependencies>
    <dependency>
        <groupId>com.github.maniac4j</groupId>
        <artifactId>eos-core</artifactId>
        <version>Tag</version>
    </dependency>
    <dependency>
        <groupId>com.github.maniac4j</groupId>
        <artifactId>eos-jdbc</artifactId>
        <version>Tag</version>
    </dependency>
    <dependency>
        <groupId>com.github.maniac4j</groupId>
        <artifactId>eos-ticker</artifactId>
        <version>Tag</version>
    </dependency>
</dependencies>

(Remember to include your specific JDBC driver).

Quick Start Example

import uz.maniac4j.eos.core.*;
import uz.maniac4j.eos.ram.*;
import uz.maniac4j.eos.ticker.VirtualThreadTicker;
import java.time.Duration;
import java.util.concurrent.ConcurrentHashMap;

public class Main {
    public static void main(String[] args) {
        // 1. Define an idempotent Job
        final Job myJob = input -> System.out.println("Processing: " + input);
        
        // 2. Register it in a Catalog
        final MapCatalog catalog = new MapCatalog(
            java.util.Map.of("print-job", myJob)
        );

        // 3. Setup Memory Structures (or use JDBC alternatives)
        final ConcurrentHashMap<String, Ticket> tickets = new ConcurrentHashMap<>();
        final ConcurrentHashMap<String, Routine> routines = new ConcurrentHashMap<>();
        final ConcurrentHashMap<String, java.time.Instant> leases = new ConcurrentHashMap<>();

        final Agenda agenda = new RamAgenda(tickets, routines);
        final Queue queue = new RamQueue(tickets, routines, leases);

        // 4. Schedule the Task
        agenda.append(
            "report-series", 
            "print-job", 
            "Data", 
            new IntervalRoutine(Duration.ofSeconds(60))
        );

        // 5. Start the Scheduler Loop
        final Sink sink = new Sink() {
            public void info(String msg) { System.out.println(msg); }
            public void error(String msg, Throwable cause) { cause.printStackTrace(); }
        };

        final VirtualThreadTicker ticker = new VirtualThreadTicker(
            queue, catalog, sink, 
            Duration.ofSeconds(5), // Polling interval
            Duration.ofMinutes(5)  // Lease timeout
        );

        Thread.ofVirtual().start(ticker);
    }
}

Decorators

Use the eos-decorators module to add behavior without modifying the job itself.

// Example: A job that retries 3 times, catches fatal errors, and logs activity.
final Job robustJob = new LoggedJob(
    new SafeJob(
        new RetryJob(originJob, 3),
        systemSink
    ),
    systemSink,
    "MyImportantJob"
);

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages