Skip to content

Signals and timers ​

ProSignals and timers needs a JoobQ Pro key. Without one every Core feature keeps working: the Pro routes of the SDK gateway answer 402 pro_required and the console's Pro routes are not served. Get a licence · Core and Pro · Pro features index

A worker never holds a lease while a run waits: the engine keeps the wait, and the step task comes back only when it can go on.

Signals ​

wait_for(name, signal:, timeout_ms:) waits durably for a signal (timeout_ms defaults to 600,000, ten minutes); its output is the signal's payload, so later steps read it with output("approval", "by").

crystal
workflow "conf-signal-#{LANG}" do
  step "before", Ok
  wait_for "approval", signal: "approve", timeout: 60.seconds
  step "after", Approved, input: {by: ref("steps.approval.output.by")}
end,
python
wf("signal").step("before", before).wait_for("approval", signal="approve", timeout_ms=60000)
            .step("after", approved, input={"by": output("approval", "by")}),
ts
wf('signal')
  .step('before', async () => ({ ok: true }))
  .waitFor('approval', { signal: 'approve', timeoutMs: 60000 })
  .step('after', async (i: any) => ({ approved_by: i.by }), { input: { by: output('approval', 'by') } }),
ruby
wf("signal")
  .step("before", ->(_i, _ctx) { { ok: true } })
  .wait_for("approval", signal: "approve", timeout_ms: 60_000)
  .step("after", ->(i, _ctx) { { approved_by: i["by"] } }, input: { by: output("approval", "by") }),
go
wf("signal").
	Step("before", func(map[string]any, *EC) (any, error) { return map[string]any{"ok": true}, nil }).
	WaitFor("approval", "approve", 60000).
	Step("after", func(in map[string]any, ec *EC) (any, error) {
		return map[string]any{"approved_by": in["by"]}, nil
	}, joobq.WithInput[*App](map[string]any{"by": joobq.Output("approval", "by")})),
rust
wf("signal")
    .step("before", |_i: Value, _c: &Ctx| Ok(json!({"ok": true})))
    .wait_for("approval", "approve", 60_000)
    .step_with("after", |i: Value, _c: &Ctx| Ok(json!({"approved_by": i["by"]})), StepOptions { input: json!({"by": output("approval", Some("by"))}), ..Default::default() }),
java
wf("signal")
    .step("before", (i, ctx) -> map("ok", true))
    .waitFor("approval", "approve", 60_000)
    .step("after", (i, ctx) -> map("approved_by", m(i).get("by")), StepOptions.input(map("by", DSL.output("approval", "by")))),
php
wf('signal')
    ->step('before', fn () => ['ok' => true])
    ->waitFor('approval', 'approve', 60000)
    ->step('after', fn ($i, $ctx) => ['approved_by' => $i['by']], input: ['by' => DSL::output('approval', 'by')]),
csharp
Wf("signal")
    .Step("before", (i, c) => Task.FromResult<object?>(new { ok = true }))
    .WaitFor("approval", "approve", 60000)
    .Step("after", (i, c) => Task.FromResult<object?>(new { approved_by = i?["by"]?.GetValue<string>() }), input: new { by = Dsl.Output("approval", "by") }),

Tested against a real server in every language shown.

Send a signal from code with signal_workflow(id, name, payload) (POST /api/v1/sdk/workflows/:id/signal, answers {id, accepted}), or from the console (POST /api/v1/workflows/:id/signal, operator). A signal sent before the run reaches the wait is kept by the engine and consumed when the wait starts.

When timeout_ms passes with no signal, the engine times the step out (the history records StepTimedOut with kind signal_wait).

Timers ​

timer(name, for_ms:) waits durably for a time: no worker is busy while it waits.

crystal
workflow "conf-timer-#{LANG}" do
  timer "pause", for: 3.seconds
  step "after", Now
end,
python
wf("timer").timer("pause", for_ms=3000).step("after", after_timer),
ts
wf('timer').timer('pause', { forMs: 3000 }).step('after', async () => ({ now_ms: Date.now() })),
ruby
wf("timer").timer("pause", for_ms: 3000).step("after", ->(_i, _ctx) { { now_ms: now_ms } }),
go
wf("timer").Timer("pause", 3000).Step("after", func(map[string]any, *EC) (any, error) {
	return map[string]any{"now_ms": nowMs()}, nil
}),
rust
wf("timer").timer("pause", 3000).step("after", |_i: Value, _c: &Ctx| Ok(json!({"now_ms": now_ms()}))),
java
wf("timer").timer("pause", 3000).step("after", (i, ctx) -> map("now_ms", System.currentTimeMillis())),
php
wf('timer')->timer('pause', 3000)->step('after', fn () => ['now_ms' => now_ms()]),
csharp
Wf("timer").Timer("pause", 3000).Step("after", (i, c) => Task.FromResult<object?>(new { now_ms = NowMs() })),

Tested against a real server in every language shown.

JoobQ Core is MIT licensed. JoobQ Pro needs a licence key.