scheduler

nim-schedules

A Nim scheduler library that lets you kick off jobs at regular intervals.

Example usage:

schedules:
  every(seconds=1, id="tick", throttle=1, async=true):
    echo("async tick ", now())
    await sleepAsync(2000)
  every(seconds=1, id="tick", throttle=1):
    echo("sync tick ", now())

Types

BeaterAsyncProc = proc (): Future[void] {...}{.gcsafe, closure.}
BeaterThreadProc = proc (): void {...}{.gcsafe, thread.}
Throttler = ref object
  num: int
  beats: seq[Future[void]]
Throttle the total number of beats.
BeaterKind {...}{.pure.} = enum
  bkInterval, bkCron
Beater = ref object of RootObj
  id: string
  startTime: DateTime
  endTime: Option[DateTime]
  beaterProc: BeaterAsyncProc
  throttler: Throttler
  case kind*: BeaterKind
  of bkInterval:
      interval*: TimeInterval

  of bkCron:
      cron*: Cron

  
Beater generates beats for the next runs.
Settings = ref object
  appName*: string
  errorHandler*: proc (fut: Future[void]) {...}{.closure, gcsafe.}
Scheduler = ref object
  settings: Settings
  beaters: seq[Beater]
  futures: seq[Future[void]]

Vars

logger = newConsoleLogger(lvlAll, defaultFmtStr, false)
By default, the logger is attached to no handlers. If you want to show logs, please call addHandler(logger).

Procs

proc initThrottler(num: int = 1): Throttler {...}{.raises: [], tags: [].}
Initialize the total number of beats allowed to be scheduled. By default, it's 1. If it's greater than 1, then more than one beats can be scheduled simultaneously.
proc throttled(self: Throttler): bool {...}{.raises: [], tags: [].}
Whether the throttler is allowed to schedule more beats.
proc submit(self: Throttler; fut: Future[void]) {...}{.raises: [], tags: [].}
Submit a new future to the throttler. WARNING: this function does not perform throttling check.
proc `$`(beater: Beater): string {...}{.raises: [], tags: [].}
proc initBeater(interval: TimeInterval; asyncProc: BeaterAsyncProc;
               startTime: Option[DateTime] = none(DateTime);
               endTime: Option[DateTime] = none(DateTime); id: string = "";
               throttleNum: int = 1): Beater {...}{.raises: [UnpackError],
    tags: [TimeEffect].}

Initialize a Beater, which kind is bkInterval.

startTime and endTime are optional.

proc initBeater(interval: TimeInterval; threadProc: BeaterThreadProc;
               startTime: Option[DateTime] = none(DateTime);
               endTime: Option[DateTime] = none(DateTime); id: string = "";
               throttleNum: int = 1): Beater {...}{.raises: [UnpackError],
    tags: [TimeEffect].}

Initialize a Beater, which kind is bkInterval.

startTime and endTime are optional.

proc initBeater(cron: Cron; threadProc: BeaterThreadProc;
               startTime: Option[DateTime] = none(DateTime);
               endTime: Option[DateTime] = none(DateTime); id: string = "";
               throttleNum: int = 1): Beater {...}{.raises: [UnpackError],
    tags: [TimeEffect].}

Initialize a Beater, which kind is bkInterval.

startTime and endTime are optional.

proc initBeater(cron: Cron; asyncProc: BeaterAsyncProc;
               startTime: Option[DateTime] = none(DateTime);
               endTime: Option[DateTime] = none(DateTime); id: string = "";
               throttleNum: int = 1): Beater {...}{.raises: [UnpackError],
    tags: [TimeEffect].}

Initialize a Beater, which kind is bkInterval.

startTime and endTime are optional.

proc fireTime(self: Beater; prev: Option[DateTime]; now: DateTime): Option[DateTime] {...}{.
    raises: [UnpackError, Exception, KeyError], tags: [RootEffect].}

Returns the next fire time of a task execution.

For bkInterval, it has below rules:

  • For the 1st run,
    • Choose startTime if it hasn't come.
    • Choose the next startTime + N * interval that hasn't come.
  • For the rest of runs,
    • Choose prev + interval.

If self.endTime is set and greater than the fire time, a none(DateTime) is returned.

proc fire(self: Beater): owned(Future[void]) {...}{.raises: [Exception, FutureError],
    tags: [RootEffect, TimeEffect].}
Fire beats as async loop until no beats can be scheduled.
proc newSettings(appName = "";
                errorHandler: proc (fut: Future[void]) {...}{.closure, gcsafe.} = nil): Settings {...}{.
    raises: [], tags: [].}
proc initScheduler(settings: Settings): Scheduler {...}{.raises: [], tags: [].}
Initialize a scheduler.
proc register(self: Scheduler; beater: Beater) {...}{.raises: [], tags: [].}
Register a beater.
proc idle(self: Scheduler): owned(Future[void]) {...}{.raises: [Exception, FutureError],
    tags: [RootEffect, TimeEffect].}
Idle the scheduler. It prevents the scheduler from shutdown when no beats is running.
proc start(self: Scheduler): owned(Future[void]) {...}{.raises: [Exception, FutureError],
    tags: [RootEffect, TimeEffect].}
Start the scheduler.
proc serve(self: Scheduler) {...}{.raises: [Exception, ValueError, FutureError, UnpackError,
                                   OSError, IndexError],
                           tags: [RootEffect, TimeEffect].}
Serve the scheduler. It's a blocking function.
proc waitFor(self: Scheduler) {...}{.raises: [Exception, ValueError, FutureError,
                                     UnpackError, OSError, IndexError],
                             tags: [RootEffect, TimeEffect].}
Run all beats til they're completed.

Macros

macro scheduler(sched: untyped; body: untyped): typed

Initialize a scheduler and register code blocks as beats.

You'll use it when you want to mix using nim-schedules with some other libraries, such as jester, etc.

macro schedules(body: untyped): untyped

Initialize a scheduler, register code blocks as beats, and run it as a blocking application.

You'll use it when the scheduled jobs are the only thing your programm will need to handle.