moontask

Work that outlives the request that asked for it.

moontask is a task queue for MoonBit: a worker takes a message off a broker and runs it, a beat sends messages on a rhythm, and a chain, group or chord says what happens after. Brokers and result stores are separate modules, so a queue on one process downloads nothing it does not use.

Fig. 01 The desk it runs itself · press to take over
A tray, a clock and a line. A card waits in the tray, the clock claims it and stamps it, a failure sends it back with a backoff, and a finished card hangs on the line. Three moves, and each one is a word from the table below: publish, claim, settle. Open the figure on its own ↗

How to read it

Three objects, and everything the library does is one of them.

The tray  ·  a queue

Cards wait in the order they were dealt. A message sits in a queue until a worker takes it; the front card is the one that goes first, and the leaf springs keep the pile at the lip so you can see how much is waiting.

The clock  ·  a worker and a beat

The only thing that moves work. It takes the card, stamps the state on it and counts the punch. A beat is the same hand going round by itself: an interval, a cron line, or one instant.

The line  ·  a chain, a group, a chord

Sealed cards hang in the order the desk finished them. A chain is one card carrying the next; a group is several hung together; a chord is the peg that waits until every card of its set is on the line.

A first run

An app owns the broker, the store and the counter. A task is a name, a policy and a body; sending one writes a message and hands back a handle that decodes the stored result back into the body's return type.

let broker = @moontask.MemoryBroker::new(clock=now)
let app = @moontask.App::new(broker, backend=broker, counter=broker, clock=now)

let greet = app.task("greet", fn(name : String, ctx : @moontask.Context) {
  "hello \{name}, attempt \{ctx.attempt}"
})

let worker = app.worker(concurrency=4)
@async.with_task_group(async fn(group) {
  group.spawn_bg(async fn() { worker.run() }, no_wait=true)
  let handle = greet.send("ada")
  println(handle.get(timeout=5000L))
  worker.stop()
})

What it keeps track of

PieceWhat it isWhere it lives
BrokerPublish, claim, confirm or rejectmoontask/spec, implemented by MemoryBroker and moontask-redis
BackendStore and fetch the outcome of a runmoontask/spec
CounterCount the parts of a chordmoontask/spec
InspectorReport what a queue holdsmoontask/spec, what a management page reads
WorkerTake, run, settle, advancemoontask
Beat, ScheduleSend on a rhythmmoontask
Scheduler, JobRun on a rhythm inside one processmoontask/schedule

Where it runs

The queue, the beat and the canvas build for native, WebAssembly and JavaScript, with the timer as the only difference. The Redis module is what a queue across several processes runs on; it keeps each consumer's unconfirmed work in a list of its own and hands it back when the consumer's lease runs out.