Synsema docsENES

Cron

A built-in background scheduler. Each job runs on its own thread, non-blocking, and truly executes its task — the counters in cron_list() reflect real executions.

cron.syn
-- Doc example: cron scheduler. Jobs run on background threads and EXECUTE their
-- task for real — the doctest asserts the observable effect, not just registration.
intent: "doc example: cron"
require time
require file("_doctest_cron.txt")

task write_marker()
    write_file("_doctest_cron.txt", "cron ran")

print("scheduled jobs: " + text(length(cron_list())))

test "a scheduled job executes its task and the counters tell the truth"
    cron_after(0.2, write_marker)
    sleep(0.8)
    assert_eq(read_file("_doctest_cron.txt"), "cron ran")
    assert_eq(length(cron_list()), 1)
    each j in cron_list()
        assert_eq(j["name"], "write_marker")
        assert_eq(j["run_count"], 1)
        assert_eq(j["errors"], 0)

Scheduling

task sync_inventory()
    let data be http_get("https://api.warehouse.com/stock")
    share data as "inventory"

cron_every(300, sync_inventory)     -- every 5 minutes
cron_after(3600, send_reminder)     -- once, after 1 hour

The task must take 0 parameters and be defined at the top level (the job runs it by name). A task with required parameters fails at registration with a clear error — wrap it in a zero-argument task. cron_every requires a positive interval; cron_after accepts a delay of 0 (runs right away). The task argument is the reference (sync_inventory) or its name as text ("sync_inventory"); both builtins return the job name (text), which is what cron_cancel(name) takes.

Managing

cron_cancel("sync_inventory")       -- stop a job
let jobs be cron_list()             -- list all jobs
print(cron_status())                -- formatted status

Each cron_list() entry carries name, interval, repeating, active, run_count (completed executions) and errors (ticks that ended in an error). Registering a job with the same name replaces the previous one (counters restart from zero).

Semantics

Under serve: shared state

Under synsema serve, jobs run with the same shared state and the same capabilities as your routes: db, state_*, memory, blackboard. Top-level jobs start only once the server is already serving, and a cron_every registered from a route works and is globally visible.

task tick()
    state_incr("heartbeats", 1)

cron_every(60, tick)

serve on 8080
    route "GET /health"
        give state_get("heartbeats")

Keeping jobs alive

Under run, jobs execute while the program lives and stop when it ends. Jobs share state with each other (one job can read what another wrote via state_* or remember). To exchange data with the rest of the program, use external effects: a file (write_file/read_file), an on-disk database, or the blackboard (share/observe). Under serve none of this is needed: jobs and routes already see the same shared state.

Use synsema serve to keep the process — and the schedule — running, even with no routes:

synsema serve scheduler.syn         -- "Serving N cron job(s). Press Ctrl+C to stop."

Job threads wait parked (zero CPU between ticks); each job's interpreter is built once and reused.