taskman

Search:
  Source   Edit

This package can be used for simple tasks that need to run on an interval or at a certain time.

It has both an async and non-async api (They are similar, only difference being in creation of scheduler)

Making tasks

The main procs for creating tasks are

  • every When you want a task to run on an interval (First run is when start() is called)
  • at When you want the task to run once at a certain date (Runs once)
  • wait When you want to wait a certain amount of time and then run the task (Runs once)

Example: cmd: -r:off

import taskman
let tasks = newAsyncScheduler() # or newScheduler if you don't want to use async

tasks.every(5.minutes) do () {.async.}:
  echo "This will run every 5 minutes"

tasks.at("2077-01-1".parse("yyyy-mm-dd")) do () {.async.}:
  echo "This will run on the 1st of janurary in 2077"

tasks.wait(10.hours) do () {.async.}:
  echo "This will run after 10 hours"

# Block the thread and run tasks when needed
# asyncCheck could be used instead e.g. so you can have a web server running also
# You could also put it onto another thread
waitFor tasks.start()
By default the scheduler will end when all tasks are finished (i.e. every at and wait task has completed). It also will run tasks late if they are added while the scheduler is sleeping while waiting for a tasks start time. To change this behaviour you can add a periodicCheck parameter to the start function which will make the scheduler check every periodicCheck seconds for which task it should be waiting on

Example: cmd: -r:off

import taskman
let tasks = newAsyncScheduler()

proc main() {.async.} =
  tasks.every(10.minutes) do () {.async.}:
    echo "10 minutes has passed"
  asyncCheck tasks.start(100) # The scheduler will check every 100 milliseconds for new tasks

  # If we didn't have the periodicCheck passed before then this task would only run after
  # the 10 minutes task before has ran
  tasks.wait(5.seconds) do () {.async.}:
    echo "5 seconds has passed"

waitFor main()

Deleting Tasks

Tasks can be directly removed using del

Example:

import taskman
let 
  tasks = newScheduler()
  taskA = newTask[TaskHandler](5.seconds, proc () = echo "hello")

tasks &= taskA
tasks.every(1.days, (proc () = echo "A new dawn appears"), name = "newDay")
assert tasks.len == 2

# Tasks can be deleted either with their name or using the original task obj
tasks.del taskA
tasks.del "newDay"
assert tasks.len == 0
If inside a task, then removeTask can be used to remove the current task (This is just a helper to raise RemoveTaskException). onlyRun is another helper which calls removeTask after a task has run a certain number of times

Example:

import taskman
let tasks = newScheduler()

tasks.every(1.milliseconds) do ():
  if true:
    echo "I don't want to run anymore"
    removeTask() #

tasks.every(1.milliseconds) do ():
  if not false:
    raise (ref RemoveTaskException)()

var i = 1
tasks.every(1.nanoseconds) do ():
  onlyRun(3)
  # This task will only run three times (Terminates at the end of the 3 run)
  echo i
  inc i

assert tasks.len == 3
tasks.start() # Tasks will be deleted when ran in this scenario
assert tasks.len == 0

Cron

For more advanced intervals you can instead use cron timers

Example:

import taskman
let tasks = newScheduler()

# Just like * * * * *
tasks.every(cron(x, x, x, x, x)) do ():
  echo "Minute has passed"

tasks.every(cron(5, 10, x, x, x)) do ():
  echo "It is the 5th minute of the 10th hour"
See the cron module for more info on the syntax

Types

AsyncTaskHandler = proc (): Future[void] {..}
Proc that runs in an async scheduler
Note: When using --threads:on the proc must be gcsafe
  Source   Edit
ErrorHandler[T] = proc (s: SchedulerBase[T]; task: TaskBase[T];
                        exception: ref Exception) {..}
The error handler will be called when a task raises an exception. Can be used to do things like reschedule a proc to be called earlier or to just ignore errors   Source   Edit
RemoveTaskException = object of CatchableError
  
Throw this within a task to remove it from the scheduler   Source   Edit
SchedulerBase[T] = ref object
  tasks*: HeapQueue[TaskBase[T]]
  running: bool
  errorHandler*: ErrorHandler[T]
  when T is AsyncTaskHandler:
    timer
  
  Source   Edit
TaskBase[T] = ref object
  handler*: T
  startTime*: Time
  name*: string
  case kind*: TimerKind
  of Interval:
      interval*: TimeInterval

  of Cron:
      cronFormat*: Cron

  of OneShot:
    nil
  
  • handler: The proc that handles the task being called
  • startTime: The time that the task should be called
  • name: The name of the task, useful for debugging with error messages (default is defaultTaskName)
  • oneShot: Whether the task only runs once (true) or runs continuiously (false)
  Source   Edit
TaskHandler = proc () {..}
Proc that runs in a normal scheduler
Note: When using --threads:on the proc must be gcsafe
  Source   Edit

Consts

defaultTaskName = "task"
Default name for a task. Will be shown in error messages   Source   Edit

Procs

proc add[T: HandlerTypes](scheduler: SchedulerBase[T]; task: TaskBase[T]) {.
    inline.}
Adds a task to the scheduler.   Source   Edit
proc at[T: HandlerTypes](scheduler: SchedulerBase[T]; interval: TimeInterval;
                         name: string; handler: T)
Sugar that allows you to have name and lambda   Source   Edit
proc at[T: HandlerTypes](scheduler: SchedulerBase[T]; time: DateTime;
                         handler: T; name = defaultTaskName)
Runs a task at a certain date/time (only runs once).

Example:

let tasks = newAsyncScheduler()

tasks.at("2077-03-06".parse("yyyy-MM-dd")) do () {.async.}:
  echo "The date is now 2077-03-06"
  Source   Edit
proc del(scheduler: SchedulerBase[HandlerTypes]; task: string)
Removes a task from the scheduler (via its name). If there are multiple tasks with the same name then the first task is deleted

Example:

let tasks = newScheduler()
proc handler() =
  echo "Time has passed"
tasks.every(5.minutes, handler, name = "time pass")
tasks.del "time pass"
doAssert tasks.len == 0
  Source   Edit
proc del[T: HandlerTypes](scheduler: SchedulerBase[T]; task: TaskBase[T])
Removes a task from the scheduler

Example:

import std/sugar
# Create task that echos "hello" every 5 seconds
let tasks = newScheduler()
proc sayHello() =
  echo "Hello"
let task = newTask[TaskHandler](5.seconds, sayHello, name = "Hello world")
tasks &= task
# Run scheduler in background, do other stuff
# Then the task can be deleted anytime later
tasks.del task
doAssert tasks.len == 0
  Source   Edit
proc every[T: HandlerTypes](scheduler: SchedulerBase[T]; cron: Cron; handler: T;
                            name = defaultTaskName)
Runs a task every time a cron timer is valid

Example:

let tasks = newAsyncScheduler()

tasks.every(cron(x, x, x, x, x)) do () {.async.}:
  echo "A minute has passed"
  Source   Edit
proc every[T: HandlerTypes](scheduler: SchedulerBase[T]; interval: TimeInterval;
                            handler: T; name = defaultTaskName)
Runs a task every time the interval occurs.

Example:

let tasks = newAsyncScheduler()

tasks.every(5.seconds) do () {.async.}:
  echo "5 seconds has passed, see you again in 5 seconds"

tasks.every(2.hours) do () {.async.}:
  echo "2 hours has passed, see you again in 2 hours"
  Source   Edit
proc every[T: HandlerTypes](scheduler: SchedulerBase[T];
                            often: TimeInterval | Cron; name: string; handler: T)
Sugar that allows you to have name and lambda   Source   Edit
proc len(scheduler: SchedulerBase): int {.inline.}
Returns number of tasks in the scheduler   Source   Edit
proc newAsyncScheduler(errorHandler: ErrorHandler[AsyncTaskHandler] = defaultErrorHandler[
    AsyncTaskHandler]): AsyncScheduler {....raises: [], tags: [].}
Creates an async version of Scheduler. See newScheduler for more details   Source   Edit
proc newScheduler(errorHandler: ErrorHandler[TaskHandler] = defaultErrorHandler[
    TaskHandler]): Scheduler {....raises: [], tags: [].}
Creates a sync version of Scheduler

Example:

# By default exceptions are raised
let tasks = newScheduler()
# But it can be overridden
let tasksError = newScheduler() do (s: Scheduler, task: Task, exception: ref Exception):
  ## Echo exception and then reschedule the task to run in 5 seconds
  # Scheduler is passed so that the error handler can be its own proc
  # and used in multiple schedulers and also be gcsafe
  echo exception.msg
  task.startTime = getTime() + 5.seconds
  Source   Edit
proc newTask[T: HandlerTypes](cron: Cron; handler: T; name = defaultTaskName): TaskBase[
    T]
Creates a new task which can be added to a scheduler.   Source   Edit
proc newTask[T: HandlerTypes](interval: TimeInterval; handler: T;
                              name = defaultTaskName): TaskBase[T]
Creates a new task which can be added to a scheduler. This task will run every interval   Source   Edit
proc newTask[T: HandlerTypes](time: DateTime; handler: T; name = defaultTaskName): TaskBase[
    T]
Creates a new task which can be added to a scheduler. This task will only run once (will run at time)   Source   Edit
proc next(task: TaskBase): Time {....raises: [TooFarAheadCron].}
Returns the next date that a task will run at (If it ran now). If the task is a oneShot then it just returns its start time   Source   Edit
proc removeTask() {....raises: [RemoveTaskException], tags: [].}
Is used in a running task to make it not be called again. (Two tasks are considered equal if they have the same handler proc even if the times are different so this might cause issues)

Example:

let tasks = newScheduler()
var i = 0
tasks.every(5.seconds) do ():
  if i == 5:
    # Stop the task after the fifth time it has ran and never run it again
    removeTask()
  else:
    # Do important stuff
    inc i
  Source   Edit
func running(tasks: SchedulerBase): bool {.inline.}
Returns true if the scheduler is running   Source   Edit
proc start(scheduler: AsyncScheduler; periodicCheck = 0): owned(Future[void]) {.
    ...raises: [Exception], tags: [RootEffect, TimeEffect].}
Starts running the tasks. Call with asyncCheck to make it run in the background
  • periodicCheck: This prevents the scheduler from fulling stopping and specifies how many milliseconds to poll for new tasks. Also use this with non async scheduler to allow you to add new tasks in that might be shorter than current running one
  Source   Edit
proc start(scheduler: Scheduler; periodicCheck = 0) {.
    ...raises: [TooFarAheadCron, Exception], tags: [TimeEffect, RootEffect].}
  Source   Edit
proc wait[T: HandlerTypes](scheduler: SchedulerBase[T]; interval: TimeInterval;
                           handler: T; name = defaultTaskName)
Waits interval amount of time and then runs task (only runs once).

Example: cmd: --threads:off

import std/httpclient
let tasks = newAsyncScheduler()
let client = newAsyncHttpClient()
# I need to send message reminder in a few minutes
tasks.wait(5.minutes) do () {.async.}:
  asyncCheck client.post("http://notificationurl.com", "Your reminder message")
  Source   Edit
proc wait[T: HandlerTypes](scheduler: SchedulerBase[T]; interval: TimeInterval;
                           name: string; handler: T)
Sugar that allows you to have name and lambda   Source   Edit

Templates

template onlyRun(times: int)
Make task only run a certain number of times

Example:

let tasks = newScheduler()
tasks.every(5.seconds) do ():
  onlyRun(5) # This is like the example in removeTask
  Source   Edit