Skip to content
supranimPublic

About

Schedule, manage and execute background tasks. Based on PowPow event loop and threadpool

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Latest commit

 

History

14 Commits

Folders and files

Repository files navigation

Supranim's Task Manager
Queue jobs that may be processed in the background

nimble install supranim_tasks

API reference
Github Actions Github Actions

Key features

  • Built on PowPow event loop & ThreadPool
  • Immediate, delayed (one-shot) and repeating (interval) background tasks
  • Immediate jobs cancel by id: submit returns a JobId, cancelJob drops a still-queued job synchronously (silent, authoritative bool)
  • Wall-clock scheduling on std/times (local time): one-shot at a DateTime, daily and weekly repeats
  • Past one-shot times never fire: the task stays tracked as inactive, observable via taskStatus, with no warning
  • Thread-safe submission from any thread, including from inside callbacks

Note

This task manager is designed for Supranim applications but works in any Nim project that needs background task processing. Requires a threads-enabled build (Nim enables threads by default).

Threading contract

Jobs run on pool worker threads; result callbacks fire serialized on the pool dispatch thread — never on the caller's thread. Lock shared state in callbacks, and never touch thread-unsafe handles (e.g. an HTTP Request) there.

Job closures must not capture ref objects across threads (directly or nested, e.g. seq[SomeRef]). Capture values, strings, seqs of values, locks, atomics and raw pointers instead. This matches powpow's submitWork envelope: closures capturing true refs are equally unsafe there (Nim ORC tracks cycle candidates per thread).

stop, shutdown and close join pool threads, so call them from outside jobs/callbacks. From inside a job/callback, call halt to stop (the recommended way to stop from there).

Example usage

import std/[os, locks]
import pkg/supranim_tasks

var m = newTaskManager(poolSize = 4)

# Immediate: runs on a worker, callback on the dispatch thread
let id = m.submit(
  proc(): string = "hello",
  proc(res: string) = echo "got: ", res
)
if m.cancelJob(id):
  echo "was still queued, will never run"

# Named task: runs once after 2 seconds, cancellable by id or name
discard m.submitDelayed(2000,
  proc(): int = 40 + 2,
  proc(res: int) = echo "named: ", res,
  name = "cleanup"
)
m.removeTask("cleanup") # strict: no further fires, name freed for reuse

# Repeating: runs every 5 minutes until cancelled
let every = m.submitRepeating(5 * 60 * 1000,
  proc(): int = 1,
  proc(res: int) = echo "tick"
)
sleep(1000)
m.cancel(every)

# Scheduled: once at a wall-clock time (local time)
import std/times
discard m.scheduleAt(dateTime(2026, mSep, 15, 9, 0, 0, 0, local()),
  proc(): string = "morning",
  proc(res: string) = echo "scheduled: ", res,
  name = "standup"
)

# Daily and weekly repeats re-arm per occurrence (DST-safe)
discard m.scheduleDaily(9, 30,
  proc(): int = 1,
  proc(res: int) = echo "daily",
  name = "standup-daily"
)
discard m.scheduleWeekly(dMon, 9, 0,
  proc(): int = 1,
  proc(res: int) = echo "weekly",
  name = "weekly-report"
)
echo m.taskStatus("standup") # taskArmed / taskInactive / ...

# Stop accepting work after 30 more seconds, then tear down
discard m.halt(30_000)
m.close()

Runnable versions live in examples/: basics.nim (immediate/delayed/repeating), named_tasks.nim (cancelTask/removeTask by id or name), nonblocking.nim (concurrent overlapping batch, mixed task kinds, scheduler staying responsive while the pool is saturated), cancellable.nim (immediate cancelJob by id) and scheduled.nim (wall-clock scheduleAt/scheduleDaily, past times staying inactive, taskStatus).

Scheduling notes

  • Times are local wall-clock (std/times, local() zone).
  • Daily/weekly tasks chain one-shots: after every fire the next occurrence is recomputed from local now(), so DST shifts land on one 23h/25h day instead of drifting.
  • Chain re-arms demand the next occurrence at least 60s out: a fire landing inside its own target second rolls to the next day/week instead of echoing twice. Explicit same-second schedules still fire ASAP.
  • A past scheduleAt never fires and logs nothing: it stays tracked as taskInactive (name still reserved). cancelTask on it is a no-op; removeTask drops tracking and frees the name.
  • For chains, prefer cancelling by name: the name resolves on the scheduler thread, so a re-arm in flight still stops. A stale id follows supersession to the live occurrence.
  • nextDailyDelayMsFrom / nextWeeklyDelayMsFrom preview in how many milliseconds a daily/weekly task will fire from a given Time.

API

Proc Description
newTaskManager(poolSize = 4) Create the manager; starts the pool and scheduler thread
submit(job, cb, onError = nil) Run job now; returns a JobId (JobId(0) when rejected)
cancelJob(id) Cancel a still-queued immediate job; true iff it will never run
isValid(id) True for a real JobId (anything but JobId(0))
submitDelayed(delayMs, job, cb, onError = nil, name = "") Run job once after delayMs; returns a TimerId
submitRepeating(intervalMs, job, cb, onError = nil, name = "") Run job every intervalMs until cancel; returns a TimerId
scheduleAt(at, job, cb, onError = nil, name = "") Run job once at wall-clock at (DateTime, local); past stays taskInactive
scheduleDaily(hour, minute, second = 0, job, cb, onError = nil, name = "") Run job daily at local time; returns the first TimerId
scheduleWeekly(weekday, hour, minute, second = 0, job, cb, onError = nil, name = "") Run job weekly on weekday at local time
cancelTask(id) / cancelTask(name) Drop a timer; unknown ids/names ignored, name stays reserved
cancel(id) Alias of cancelTask by id
removeTask(id) / removeTask(name) Strict removal: no further fires, name freed for reuse
hasTask(id) / hasTask(name) True while a task is tracked (armed, cancelled or inactive)
taskStatus(id) / taskStatus(name) taskArmed, taskCancelled, taskInactive or taskUnknown
stop() Graceful: reject new work, drain the pool, drop timers
shutdown() Immediate: like stop but discard still-queued jobs
halt(delayMs) stop after delayMs; safe from inside jobs/callbacks
close() stop + join scheduler thread + free resources (idempotent)
isRunning() / poolSize() / rawPool() Status, worker count, underlying pool

Roadmap

  • Task cancellation by id for immediate jobs (pending queue removal)
  • Task prioritization
  • File logging of task execution
  • More detailed error handling and reporting for task failures

❤ Contributions & Support

🎩 License

MIT license. Made by Humans from OpenPeeps.
Copyright © 2026 OpenPeeps & Contributors — All rights reserved.

About

Schedule, manage and execute background tasks. Based on PowPow event loop and threadpool

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

Generated from supranim/starter