Deferred:   a simple library for doing things later
1 Adding a task to a taskqueue
defer
after
today-at
tomorrow-at
2 Interacting with task queues
enqueue
dequeue
inspect-queue
apply-queue
apply-queue/  promise
3 Shutting down a queue
wait-queue
purge-queue
shutdown-queue
4 Making your own task queues
queue-manager
queue-manager-loop
5 Utility functions
eta-from-offset
6.3.90.900

Deferred: a simple library for doing things later

 (require deferred) package: deferred

Deferred allows you to run things at a specified time or after a specified elapsed time. It’s sort of like a less complete cron, usable from within racket.

The central notion within deferred is that of the task queue: a central place where tasks go to wait. (This is in fact a misnomer: it’s actually just a set, and tasks handle their own scheduling independently of each other, but I found it helpful to think about it in this way.) There’s a default queue that requires no setup, but if you want a bunch of separate taskqueues, you can achieve this with parameterization.

1 Adding a task to a taskqueue

In deferred, a "task" is just a zero-argument function to be run (which is internally wrapped into its own thread. There are several ways to add a function to a taskqueue, with various levels of convenience.

For running a function at a specified date, use defer:

procedure

(defer fn [#:eta date])  void?

  fn : (-> any)
  date : date? = (current-date)
Runs the provided function at a later date (defaulting to now). Providing a date in the past will run the function immediately. Blocks until the task has been added to the current taskqueue (which is the value of the queue-manager parameter). If the taskqueue is no longer accepting tasks, returns immediately.

For running a function after a specified interval, use after:

syntax

(after num kw body ...)

 
  num : number?
  kw : (one-of/c #:seconds #:minutes #:hours #:days)
Run the specified body expressions after a given delay. Only one of the timing keywords may be provided (this may change in the future). Uses defer internally, so its specific behaviors apply here as well.

For convenience, there are shortcuts for running something at a specific hour and minute today or tomorrow:

syntax

(today-at hour minute body ...)

 
  hour : (</c 24)
  minute : (</c 60)

syntax

(tomorrow-at hour minute body ...)

 
  hour : (</c 24)
  minute : (</c 60)
Run the specified body expressions at the given time today (or tomorrow). If the time has already passed, runs the body expressions immediately. Uses defer internally, so its specific behaviors apply here as well.

2 Interacting with task queues

Most of the time, it shouldn’t really be necessary to interact with the task queues directly. Nonetheless, if you want to inspect the contents of a queue, or override the default queueing behavior in defer, there are functions to do so.

procedure

(enqueue t)  bool?

  t : thread?
Add the specified thread to the tasks tracked by the taskqueue. (The thread should already be running.)

Return #t if the enqueue message was sent successfully, #f otherwise.

procedure

(dequeue t)  (or/c void? #f)

  t : thread?
Remove the specified thread from the tasks tracked by the taskqueue.

Return nothing if the dequeue message was sent successfully, #f if the queue is not running. Fails silently if the thread is not in the queue.

procedure

(inspect-queue)  (or/c void? #f)

displayln each of the items in the current queue. (Each item is a thread.)

procedure

(apply-queue fn)  (or/c void? #f)

  fn : (-> (set/c thread? #:kind 'immutable) any)
Apply the provided function to the set of threads in the current queue for side-effects.

procedure

(apply-queue/promise fn)  promise?

  fn : (-> (set/c thread? #:kind 'immutable) any)
Apply the provided function to the set of threads in the current queue. Immediately return a promise that when forced gives the return value of the function.

3 Shutting down a queue

When you’re done with a queue, you can shut it down to remove any pending tasks, and prevent enqueueing any additional ones.

procedure

(wait-queue)  void?

Waits for all tasks in the current queue at the time of the call to finish, then returns. Does not prevent additional tasks from being added while waiting.

procedure

(purge-queue)  (or/c void? #f)

Purges the current queue, immediately killing all tasks that haven’t yet marked themselves as running. (There’s no guard against race conditions. Don’t assume that all tasks that were killed did not run.)

Returns nothing if the purge message was successfully sent or #f if the queue is not running.

procedure

(shutdown-queue)  (or/c void? #f)

Shuts down the current queue, immediately killing all tasks and preventing the queue from accepting any new ones. As for purge-queue, there’s no guarantee about what has and has not yet run.

Returns nothing if the shutdown message was successfully sent or #f if the queue is not running.

4 Making your own task queues

If you need multiple separate queues, or your own custom queues, you can accomplish this by parameterizing queue-manager:

parameter

(queue-manager)  thread?

(queue-manager qm)  void?
  qm : thread?
 = (thread queue-manager-loop)
The current task queue used for all other operations. Basic add/remove operations are accomplished by sending message '(add item) and '(remove item). For inspecting the queue and applying a function the message '(inspect callback-fn) is used. For purging and shutting down, the messages 'purge and 'shutdown are used. Custom queue implementations should somehow handle (or ignore) these messages.

procedure

(queue-manager-loop [queue-items])  void?

  queue-items : (set/c any/c #:kind 'immutable) = (set)
A basic queue manager loop, repsonding to 'add, 'remove, 'inspect, 'purge, and 'shutdown. Tracks enqueued items with an immutable set. This particular function is agnostic about what the enqueued items are, though the purge function used for purging/shutting down will assume they are threads.

5 Utility functions

procedure

(eta-from-offset [#:seconds sec    
  #:minutes minutes    
  #:hours hours    
  #:days days])  date?
  sec : number? = 0
  minutes : number? = 0
  hours : number? = 0
  days : number? = 0
Get a date corresponding to the suppled offset in seconds, minutes, hours, and days from now.