The Tick

How the once-a-minute tick schedules and runs jobs

The tick is the heart of the module: once a minute it looks at every enabled job, decides what is due, and runs it. Neither the webtrees core nor the operating system does any scheduling itself — they only start tick.php .

flowchart TD
    subgraph Triggers["`⏱ Triggers *(once per minute)*`"]
        CRON["`🕑 OS cron / systemd<br/>*every minute*`"]
        WATCH["`👁 watch daemon<br/>*(in-module)*`"]
    end

    subgraph Guard["🛡 Guard"]
        TP["`📄 cli/tick.php · **cron:tick**<br/>*guard(): 403 over HTTP*`"]
        OFF{"`⚠ site offline?<br/>*data/offline.txt*`"}
        BOOT["`🚀 boot()<br/>*webtrees + DB*`"]
    end

    subgraph Selection["🔎 Select work"]
        TIME["`📅 time-based jobs<br/>**next_run_at due** *(UTC)*`"]
        CE[("`📦 cj_event<br/>*event queue*`")]
        DRAIN["`🧮 drain queue<br/>*coalesced · 60 s budget*`"]
    end

    subgraph Execution["`⚙ Execution *(isolated child)*`"]
        RUN["`🧪 JobRunner<br/>**proc_open**`"]
        LIM["`⏲ timeout 30–3600 s<br/>last **64 KB** · exit code`"]
    end

    subgraph Storage["💾 Storage"]
        CJRUN[("`cj_run<br/>*run history*`")]
        MARK["`🏷 tick.last<br/>*stale > 300 s → tick_stale*`"]
    end

    CRON --> Guard
    WATCH --> Guard
    TP --> OFF
    OFF -->|"offline"| SKIP["⏭ skip tick"]
    OFF -->|"online"| BOOT
    BOOT --> TIME
    BOOT --> DRAIN
    CE --> DRAIN
    TIME --> Execution
    DRAIN --> Execution
    RUN --> LIM
    LIM --> CJRUN
    BOOT --> MARK

    classDef sourceStyle fill:#e3f2fd,stroke:#1565C0,stroke-width:2px
    classDef queueStyle fill:#fff3e0,stroke:#E65100,stroke-width:2px
    classDef tickStyle fill:#e8f5e9,stroke:#2E7D32,stroke-width:2px
    classDef execStyle fill:#fce4ec,stroke:#C2185B,stroke-width:2px
    classDef storageStyle fill:#f5f5f5,stroke:#616161,stroke-width:1px

    class CRON,WATCH sourceStyle
    class OFF,CE queueStyle
    class TP,TIME,DRAIN tickStyle
    class RUN,LIM execStyle
    class CJRUN,MARK,SKIP storageStyle

What starts a tick

Pick one trigger (once):

  • OS cron — * * * * * from the webtrees root (see Installation).
  • systemd timer — the generated unit from the module’s admin page.
  • watch daemon — the in-module resident process that re-launches tick.php every 60 s and survives FPM worker recycling (Installation, which also carries the daemon’s lifecycle diagram).

All three run the same command: php modules_v4/cronjob/cli/tick.php cron:tick.

What one tick does

  1. Guard — CliBootstrap::guard() answers 403 if the script is ever reached over HTTP; then CliBootstrap::boot() loads webtrees + the database.
  2. Offline check — if data/offline.txt exists the tick is skipped entirely, before any database access — e.g. during a webtrees update, when schema and code are in flux.
  3. Select the work — two sources, combined in one pass:
    • time-based jobs whose next_run_at has passed (cron schedules, UTC); the due triggers are computed against the last run (or the job’s creation, for never-run jobs).
    • event jobs — the cj_event queue is drained oldest-first, coalesced to one run per event name, within a 60-second wall-clock budget. Event jobs receive the event name and payload via the CRONJOB_EVENT / CRONJOB_EVENT_PAYLOAD environment. See the event pipeline.
  4. Run each job in isolation — JobRunner validates the command (a modules_v4/<module>/cli/<script>.php path, or one of the four allowlisted core commands; at most 10 argument tokens) and launches it as a child process with a hard timeout (30–3600 s). Only the last 64 KB of output are captured; a job that hits its timeout is killed (proc_terminate) and reported as exit -9.
  5. Record — every run is written to cj_run (status, exit code, duration, the trigger that fired it, captured output) — this is the run history. Event-triggered runs are additionally linked via cj_event_run.
  6. Marker — the tick writes data/cronjob/tick.last. If that marker is older than 300 s, the module reports itself as tick_stale in the admin status.

Manual runs

You can also run the tick by hand for debugging — --dry-run, --job=<name>, --strict and --full-output are described on the CLI scripts page. Note that --full-output may expose personal data, so only use it where the output destination is trusted.

Last modified November 8, 2026: php doc references (f831d42)