README

TaskManager

A lightweight, elegant task scheduler for Kotlin and Java with support for cron expressions, fixed-rate scheduling, and manual execution.

Features

Quick Start

Kotlin

val tasks = TaskManager {
    concurrency = 4
    context["db"] = database
}

// Heartbeat every 5 seconds
tasks.task("heartbeat", every = 5.seconds) {
    println("heartbeat")
}

// Cron task - every hour
tasks.task("cleanup", cron = "0 0 * * * ?") {
    val db = get<Database>("db")
    db.cleanup()
}

// Manual task
tasks.task("rebuild-index") {
    rebuildIndex()
}

tasks.start()

// Manually execute
tasks.run("rebuild-index")

tasks.shutdown()

Java

TaskManager tasks = TaskManager.builder()
    .concurrency(4)
    .putContext("db", database)
    .build();

// Heartbeat every 5 seconds
tasks.task("heartbeat", null, Duration.ofSeconds(5), false, ctx -> {
    System.out.println("heartbeat");
    return null;
});

// Cron task
tasks.task("cleanup", "0 0 * * * ?", null, false, ctx -> {
    Database db = ctx.get("db");
    db.cleanup();
    return null;
});

tasks.start();
tasks.shutdown(true);

Installation

Add to your build.gradle.kts:

dependencies {
    implementation("com.taskmanager:taskmanager:1.0.0")
}

Core Concepts

1. Task Registration

Register tasks with simple or advanced scheduling:

// Simple - Fixed rate
tasks.task("task1", every = 10.seconds) { }

// Simple - Cron
tasks.task("task2", cron = "0 * * * * ?") { }

// Simple - Manual only
tasks.task("task3") { }

// Advanced - Schedule object
tasks.task("task4", Schedule.WithInitialDelay(
    delay = 1.minutes,
    schedule = Schedule.FixedRate(5.seconds)
)) { }

2. Context Management

Inject and use context values:

val tasks = TaskManager {
    context["db"] = database
    context["config"] = config
}

tasks.task("process") {
    val db = get<Database>("db")              // Throws if not found
    val cache = getOrNull<Cache>("cache")     // Returns null if not found
    val timeout = getOrDefault("timeout", 30)  // Returns default if not found
    
    // Set task-local values
    set("processed", 100)
}

3. Lifecycle Management

val tasks = TaskManager {
    autoStart = false  // Don't start immediately
    
    onTaskStart = { event ->
        println("Starting ${event.taskName}")
        // Dynamically inject context
        event.context["timestamp"] = System.currentTimeMillis()
    }
    
    onTaskComplete = { exec ->
        if (exec.isSuccess) {
            println("${exec.taskName} took ${exec.duration}ms")
        } else {
            println("${exec.taskName} failed: ${exec.error}")
        }
    }
}

tasks.start()  // Start scheduling
tasks.shutdown(awaitTermination = true)  // Graceful shutdown

4. Task Control

tasks.enable("task1")   // Enable scheduling
tasks.disable("task1")  // Disable scheduling (doesn't affect running tasks)
tasks.remove("task1")   // Remove task completely

// Query
tasks.exists("task1")                  // Check existence
tasks.getTaskDefinition("task1")        // Get task metadata
tasks.listTaskNames()                   // List all tasks

5. Manual Execution

// Async execution
val future = tasks.run("task1")
val result = future.get()

// Sync execution
val result = tasks.runBlocking("task1")

// With additional context
tasks.run("task1", mapOf("param" to "value"))

6. Concurrency Control

// Don't allow concurrent execution (default)
tasks.task("exclusive", every = 5.seconds, allowConcurrent = false) {
    Thread.sleep(10000)  // Even if this takes longer, next execution waits
}

// Allow concurrent execution
tasks.task("parallel", every = 5.seconds, allowConcurrent = true) {
    Thread.sleep(10000)  // Multiple instances can run simultaneously
}

Advanced Examples

Custom Monitoring

class TaskMonitor {
    private val stats = ConcurrentHashMap<String, TaskStats>()
    
    data class TaskStats(
        var totalExecutions: Long = 0,
        var successCount: Long = 0,
        var failureCount: Long = 0,
        var averageDuration: Long = 0
    )
    
    fun record(execution: TaskExecution) {
        val stat = stats.getOrPut(execution.taskName) { TaskStats() }
        stat.totalExecutions++
        if (execution.isSuccess) stat.successCount++
        // ... update stats
    }
}

val monitor = TaskMonitor()

val tasks = TaskManager {
    onTaskComplete = { exec -> monitor.record(exec) }
}

Dynamic Context Injection

val tasks = TaskManager {
    onTaskStart = { event ->
        // Inject context based on task type
        when {
            event.taskName.startsWith("db-") -> {
                event.context["connection"] = connectionPool.acquire()
            }
            event.taskName.startsWith("api-") -> {
                event.context["token"] = refreshToken()
            }
        }
    }
}

Retry Logic

tasks.task("resilient-task") {
    retry(times = 3, delay = 5.seconds) {
        riskyOperation()
    }
}

// Helper function
fun <T> retry(times: Int, delay: Duration, block: () -> T): T {
    repeat(times - 1) {
        try {
            return block()
        } catch (e: Exception) {
            Thread.sleep(delay.toMillis())
        }
    }
    return block()  // Last attempt
}

Schedule Types

// Cron expression (Quartz format)
Schedule.Cron("0 0 * * * ?")  // Every hour

// Fixed rate (from start time)
Schedule.FixedRate(Duration.ofSeconds(10))

// Fixed delay (from completion time)
Schedule.FixedDelay(Duration.ofSeconds(10))

// One-time execution
Schedule.Once(Instant.now().plusSeconds(60))

// With initial delay
Schedule.WithInitialDelay(
    delay = Duration.ofMinutes(1),
    schedule = Schedule.FixedRate(Duration.ofSeconds(5))
)

Cron Expression Format

Uses Quartz cron format:

┌───────────── second (0-59)
 │ ┌───────────── minute (0-59)
 │ │ ┌───────────── hour (0-23)
 │ │ │ ┌───────────── day of month (1-31)
 │ │ │ │ ┌───────────── month (1-12 or JAN-DEC)
 │ │ │ │ │ ┌───────────── day of week (0-6 or SUN-SAT)
 │ │ │ │ │ │
 * * * * * *

Examples:

Configuration Options

TaskManagerConfig {
    // Number of threads in pool
    var concurrency: Int = Runtime.getRuntime().availableProcessors()
    
    // Thread name prefix
    var threadNamePrefix: String = "task-manager"
    
    // Auto-start scheduling on creation
    var autoStart: Boolean = false
    
    // Global context
    val context: MutableMap<String, Any>
    
    // Lifecycle hooks
    var onTaskStart: ((TaskStartEvent) -> Unit)? = null
    var onTaskComplete: ((TaskExecution) -> Unit)? = null
}

Best Practices

  1. Use context for shared resources: Database connections, configurations, etc.

  2. Implement monitoring via hooks: Don't let the framework dictate your monitoring strategy

  3. Handle errors in tasks: Wrap risky operations in try-catch

  4. Use allowConcurrent wisely: Most tasks should not allow concurrent execution

  5. Graceful shutdown: Always call shutdown(awaitTermination = true)

  6. Test cron expressions: Use online tools to validate before deployment

Thread Safety

License

MIT License

Credits

Built with: