Skip to content

feat: declared background workers + frankenphp_get_worker_handle()#2398

Open
nicolas-grekas wants to merge 1 commit intophp:mainfrom
nicolas-grekas:bgworker-minimal
Open

feat: declared background workers + frankenphp_get_worker_handle()#2398
nicolas-grekas wants to merge 1 commit intophp:mainfrom
nicolas-grekas:bgworker-minimal

Conversation

@nicolas-grekas
Copy link
Copy Markdown
Contributor

@nicolas-grekas nicolas-grekas commented May 7, 2026

Summary

Smallest useful slice of the background-worker work. Adds long-lived
non-HTTP PHP scripts declared via WithWorkerBackground() / background
in the Caddyfile worker block, plus the frankenphp_get_worker_handle()
primitive that gives the script a stream it can park on (stream_select)
to exit gracefully on shutdown / restart.

This carves the minimal surface out of the larger PR #2393 so it is small
enough to review and merge on its own. The richer features built on top
of these primitives are deferred to follow-ups.

What's in

  • PHP API: frankenphp_get_worker_handle(): resource — closes when
    FrankenPHP drains the worker (write end of the stop pipe is closed Go-side).
  • Go API: WithWorkerBackground(), WithWorkerScope(), NextScope,
    SetScopeLabel. num >= 1 is required for bg workers (no lazy-start
    in this build).
  • Lifecycle: boot the script, restart on crash with quadratic backoff,
    halt respawn at max_consecutive_failures. Exit-status 0 = cooperative
    restart, no failure counted.
  • Caddy: background flag inside php_server { workers ... }.
    Per-php_server scope isolation so two server blocks can declare the same
    worker name without colliding. Scope label resolved before Init via the
    cascade in caddy/scopelabel.go (host matcher → first listener address)
    so the very first metric emit on every bg worker carries the right
    m#<scope-label>:<name> prefix.

Deferred (kept out for review surface)

  • frankenphp_ensure_background_worker() and the lazy-start / readiness
    / boot-failure machinery it pulls in.
  • Catch-all (empty-name) workers.
  • Shared-state APIs (frankenphp_set_vars / frankenphp_get_vars).

Test plan

  • TestBackgroundWorkerLifecycle — bg worker boots, touches sentinel,
    parks on stop pipe, Shutdown() returns within 10s.
  • TestBackgroundWorkerCrashRestartsexit(1) on first boot,
    respawned thread runs the success branch.
  • TestBackgroundWorkerWithoutHTTP — bg worker doesn't intercept
    HTTP traffic; a regular request still serves.
  • TestBackgroundWorkerSameNameDifferentScope (internal) — two
    php_server scopes each declare "shared", both Init successfully and
    run independently.
  • TestNextScopeIsDistinct.

Adds a minimal background-worker surface: long-lived non-HTTP PHP scripts
declared via WithWorkerBackground() / `background` in the Caddyfile worker
block. Each worker exposes a stop-pipe stream via
frankenphp_get_worker_handle(); on FrankenPHP shutdown / restart Go closes
the write end so the script's stream_select returns EOF and the loop can
exit cleanly. Crash-restart with quadratic backoff, halted by
max_consecutive_failures. Per-php_server scope isolation so two server
blocks can declare the same worker name without colliding.

Deferred to follow-ups (kept out for review surface):
- frankenphp_ensure_background_worker() / lazy-start machinery
- Catch-all (empty-name) workers
- Shared-state APIs (frankenphp_set_vars / frankenphp_get_vars)

What lands:
- frankenphp_get_worker_handle() zif + stop-fd plumbing in C
- WithWorkerBackground() / WithWorkerScope() Go options; num >= 1 required
- backgroundWorkerThread handler: boot, restart with backoff, cap-on-crash,
  graceful exit on stop-fd close
- Per-scope lookup map keyed by user-facing name
- Caddy `background` worker flag + scope-label cascade (host matcher ->
  first listener address) so the metric prefix m#<scope-label>:<name> is
  set before the very first metric emit.

Tests:
- TestBackgroundWorkerLifecycle: bg worker boots, touches sentinel, parks,
  exits within 10s of Shutdown.
- TestBackgroundWorkerCrashRestarts: exit(1) -> respawn -> restarted
  sentinel.
- TestBackgroundWorkerWithoutHTTP: a regular HTTP request alongside a bg
  worker still serves.
- TestBackgroundWorkerSameNameDifferentScope: two servers each declare
  "shared", both run, distinct *worker per scope.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant