Skip to content

server: worker-mode configuration, boot split and dispatch #208

Description

@melihucar

Gate: default
Agent: claude

Depends on: #14, #207

frankenrust-server has no way to run a worker. ServerConfig is
{document_root, num_threads, php_ini} (crates/frankenrust-server/src/server.rs:35-39),
boot is an unconditional convert_to_regular_thread loop (:88-93), dispatch
is an unconditional handle_request_with_regular_php_threads(ctx) (:415-418),
and the worker-script override slot in cgi::split_cgi_path is hard-coded
None (:373, with cgi.rs:135-136 explicitly deferring it: "The worker
override from cgi.go:203-215 is handled by the worker layer"
).

Split out of #14, which owns the core park/resume loop and is scoped to
frankenrust-core. This issue is the only thing that makes that loop reachable
over HTTP.

What to build

  1. ServerConfig gains worker_script: Option<PathBuf> and
    num_worker_threads: usize.
    Add a Default impl and convert every
    existing construction site to ..Default::default() so the next field is
    not another source-breaking change:
    crates/frankenrust-server/src/main.rs:36-40, and the five test binaries
    under crates/frankenrust-server/tests/ (hello_world.rs,
    concurrency.rs, directory_index.rs, directory_index_cgi_vars.rs,
    keepalive_shutdown.rs). This is the one place this issue is allowed to
    touch those files; do not change what they assert.
  2. The boot loop splits (server.rs:88-93): num_worker_threads claims
    become convert_to_worker_thread(claim, worker) (core: worker mode -- the frankenphp_handle_request park/resume loop #14's), the remainder stay
    regular. Reject a config with num_worker_threads > 0 and no
    worker_script, and one where the two counts exceed num_threads, with a
    real error rather than a panic — upstream's own count validation is in
    worker.go.
  3. The worker is registered with FRANKENPHP_WORKER=1 in its prepared env
    (worker.go:138), through core: the prepared environment -- RequestContext.env, registerPreparedEnv, and the $_SERVER merge #207's RequestContext::env. Without this,
    vendor/frankenphp/testdata/_executor.php:4 takes the regular-mode branch
    and the worker script never calls frankenphp_handle_request() — which core: worker mode -- the frankenphp_handle_request park/resume loop #14's
    boot detection then correctly reports as a boot failure, in a quadratic
    backoff loop, while the HTTP request hangs. This is the failure this issue
    exists to prevent; it is not optional.
  4. Dispatch routes (server.rs:415-418): if a worker matches the resolved
    script, hand the RequestContext to core: worker mode -- the frankenphp_handle_request park/resume loop #14's worker dispatch instead of
    handle_request_with_regular_php_threads. Resolution is by script path only
    (worker.go's workersByPath); named-worker routing and m#-prefixed
    module workers are out of scope.
  5. split_cgi_path's worker override (server.rs:373 currently None):
    pass the worker's file name, porting cgi.go:203-215.
  6. main.rs reads the knobs: FRANKENRUST_WORKER_SCRIPT and
    FRANKENRUST_WORKER_THREADS, matching the existing
    FRANKENRUST_DOCUMENT_ROOT/FRANKENRUST_THREADS style.
  7. docker/frankenrust.Dockerfile passes both through, alongside the
    existing FRANKENRUST_LISTEN/FRANKENRUST_THREADS (:100, :120), so the
    conformance and bench legs can drive worker mode later.

Acceptance

  • crates/frankenrust-server/tests/worker_hello.rs (new binary — one
    init_php_threads per process, see hello_world.rs's module doc): boots
    with worker_script set to a fixture using the _executor.php pattern,
    issues one request, asserts status/body match what the same fixture returns
    in regular mode.
  • crates/frankenrust-server/tests/worker_persistence.rs (new binary):
    num_threads: 1, num_worker_threads: 1, N sequential HTTP requests against
    vendor/frankenphp/testdata/worker-with-counter.php, asserting bodies
    requests:1 .. requests:N. This is the test a server that silently
    re-bootstraps per request fails. (core: worker mode -- the frankenphp_handle_request park/resume loop #14 pins the same property below the HTTP
    layer; this pins it through it.)
  • A test that a worker_script config still serves a non-worker script in
    the same document root through the regular path.
  • bash scripts/gate.sh default passes.

Out of scope

  • max_requests and the thread reboot it triggers, autoscaling, the file
    watcher and RestartWorkers, worker-mode sessions, metrics.
  • Named workers, m# module workers, SendMessage/extension workers.
  • Multiple workers in one server — one worker_script is enough; make the
    registry able to hold more than one without exposing a way to configure it.
  • The conformance worker leg: separate issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    fr:followupFiled by an agent mid-taskfr:p1On the critical path to the next milestonefr:readyClaimable by an agentfr:waitingReady, but waiting on an open dependency

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions