Skip to content

Function tasks

Function tasks add named handler dispatch and stored results on top of Honker queues. The convenience API is not identical in every binding:

BindingFunction-task API
PythonDecorators, in-process workers, worker CLI, and periodic-task decorator
JavaExplicit TaskRegistry, TaskHandle, runTasks, and result handles
KotlinKotlin registry helpers over the Java task runtime
Node, Rust, Go, Ruby, Bun, Elixir, .NET, C++Queue and result primitives; no function-task registry/dispatcher

All task-capable bindings store the same queue envelope:

{"__honker_task__":{"task":"send-email","args":["alice@example.com","Hi"],"kwargs":{}}}

That makes the storage format portable. Run each queued task with a worker whose registry contains the named handler. Cross-language handler dispatch is not yet covered by Honker’s compatibility tests, so keep producers and task workers on the same task API unless you test that combination yourself.

import time
import honker
db = honker.open("app.db")
q = db.queue("default")
@q.task(name="send-email", retries=3, timeout_s=30)
def send_email(to: str, subject: str) -> dict:
return {"sent_at": time.time()}
result = send_email("alice@example.com", "Hi")
# Run `python -m honker worker myapp.tasks:db --queue=default`
# in another process before waiting for the result.
value = result.get(timeout=10) # Or: await result.aget(timeout=10)

Calling send_email(...) enqueues the call and returns a TaskResult; it does not run the function in the caller.

Python’s decorator registry is process-global. Import the module containing the decorated functions before starting a worker.

Terminal window
python -m honker worker myapp.tasks:db --queue=default --concurrency=4

The positional argument is an import path followed by :variable_name. Useful flags are --queue NAME (repeatable), --concurrency N, and --list.

import asyncio
# Import the modules containing @q.task functions first.
asyncio.run(db.run_workers(concurrency=4))

db.run_workers runs until its stop_event is set or the task is cancelled. Java and Kotlin instead keep the TaskWorkerHandle returned by db.runTasks(...) open for the worker lifetime.

Python defaults to f"{fn.__module__}.{fn.__qualname__}"; Java and Kotlin require an explicit name. A queued job continues to reference the old name after a code rename, so prefer a stable explicit name from day one:

@q.task(name="send-email")
def send_email(...):
...

Otherwise, keep a handler registered under the old name until old jobs have drained.

Python @task and JVM registered tasks default to stored results with a one-hour TTL, three attempts, and a 60-second retry delay. Override them with Python decorator arguments or JVM TaskOptions. Python additionally supports timeout_s; JVM task handlers do not have an equivalent task-level timeout option. Python @periodic_task is the exception: result storage defaults off.

Arguments and return values must be JSON-compatible. Raw payloads on a task-worker queue and unknown task names go to the dead-letter table. Malformed JSON/envelopes are not a portable task-layer contract; validate custom producers before relying on their failure behavior.

from honker import crontab, every_s
@q.periodic_task(crontab("0 3 * * *"), name="nightly-backup")
def nightly_backup():
...
@q.periodic_task(every_s(1), name="heartbeat")
def heartbeat():
...

@periodic_task registers both the function handler and a scheduler entry with an empty argument list. Java and Kotlin can combine their explicit task registry with the scheduler primitives, but do not provide periodic-task sugar.

  • _honker_live — pending and in-flight task envelopes.
  • _honker_dead — exhausted retries, raw task-queue payloads, unknown tasks, and explicit failures.
  • _honker_results — return values keyed by job id.
  • _honker_scheduler_tasks — scheduler registrations, including Python periodic tasks.

All tables share the same .db file as your business data.