Skip to main content

Ruby worker runtime

The zeridion-flare gem ships an opt-in worker runtime alongside the thin client. Instead of hand-writing a poll/ack loop, you define job classes and let the worker register, long-poll for work, dispatch each job to your handler, heartbeat with progress, honor server-side cancellation, and drain gracefully on shutdown.

The runtime lives behind a separate require so thin-client programs stay lean:

require "zeridion_flare/worker"

Requires Ruby 3.2+.

Quick start

require "zeridion_flare/worker"

class SendWelcomeEmail
include Zeridion::Flare::Job
flare_options queue: "email", max_attempts: 5, timeout: 120

def perform(payload, ctx)
return if ctx.cancelled?
return if AlreadySent.exists?(ctx.job_id) # at-least-once → idempotent
Mailer.welcome(payload["email"]).deliver_now
ctx.report_progress(1.0)
end
end

worker = Zeridion::Flare::Worker::Worker.new(concurrency: 5)
worker.run # blocks; SIGTERM/SIGINT → graceful drain

Set FLARE_API_KEY in the environment (or pass api_key: to Worker.new). The worker reads the same FLARE_API_KEY fallback and base-URL default as the thin client.

What the runtime does for you

On start/run, the worker:

  1. Builds a stable worker identity and announces itself — the queues it serves, the job types it can run, and any recurring schedules. Announcement is best-effort: if it fails, the worker logs it and keeps going.
  2. Long-polls for work, asking only for as many jobs as it has free slots.
  3. Dispatches each job to your #perform, on a bounded thread pool.
  4. Heartbeats each in-flight job on a fixed cadence, carrying the latest reported progress, and watches the response for a cancellation signal.
  5. Acknowledges the outcome — succeeded or failed. The server decides whether a failed job retries or is parked; the worker only reports.
  6. On SIGTERM/SIGINT, stops claiming new work and drains in-flight jobs before exiting.

Lifecycle

MethodBehaviour
#startSpawn the poll loop on a background thread and return immediately. Idempotent.
#run#start, install SIGTERM/SIGINT handlers, then block until a signal arrives, then drain. Use this for a standalone worker process.
#stopIdempotent. Stop polling and drain in-flight jobs within the shutdown grace, then return.

Signals are only installed inside #run. If you embed the worker in a larger process, call #start and wire your own shutdown to #stop.

Concurrency & backpressure

concurrency: (default 10) caps how many jobs run at once. The worker only ever asks the server for as many jobs as it has free slots, so it never pulls more than it can run. Because Ruby releases the global lock on I/O, an I/O-bound workload gets real parallelism; for CPU-bound work, prefer a lower concurrency and run more worker processes.

At-least-once delivery — make handlers idempotent

Acknowledgement is best-effort. If a worker finishes a job but crashes before the ack lands, the server re-delivers the same job to another worker. Your handler therefore runs at least once, occasionally more — so it must be idempotent. Dedupe on ctx.job_id (a unique row, a SETNX, an outbox record). This is the one load-bearing contract of the worker model.

See also