Skip to content

Say that any $_ENV write in worker mode outlives its request - #366

Merged
diolektor merged 1 commit into
mainfrom
fix/worker-mode-docs-omit-that-env-writes-are-worker-global
Sep 19, 2026
Merged

diolektor merged 1 commit into
mainfrom
fix/worker-mode-docs-omit-that-env-writes-are-worker-global

Conversation

@diolektor

Copy link
Copy Markdown
Contributor

Problem

docs/php/superglobals.md described worker-mode $_ENV pinning from one side only — "writes your bootstrap makes there stay visible to every request that worker serves", and, a paragraph later, "$_ENV holds the process values plus whatever your bootstrap added". Both sentences are true, and both read as though the bootstrap is what makes a write survive.

Nothing distinguishes a bootstrap write from any other. Pinning the array is the whole of the mechanism, so $_ENV['tenant_id'] = $request->tenant(); inside a request handler stays there for the rest of that worker's life: every later request that worker serves reads it, and so does a request the worker picks up while the writer is parked at a suspension point, because $_ENV is deliberately left out of the state a suspended request carries with it. None of it is reported — no warning, no log line, only a request reading a value some other request set.

The behaviour is intentional and is not changed here; what was missing was the second half of its description. The fiber page meanwhile asserted the opposite — "Each fiber's PHP state — superglobals, response headers, output buffers, VM stack — is saved on suspension and restored on resumption. Fibers are fully isolated from each other" — on the very page the $_ENV note sends readers to for the list of suspension points.

What changed

  • docs/php/superglobals.md gains a note saying that $_ENV in worker mode is worker state rather than request state, covering both the later-request and the multiplexed-neighbour case, and then says where a per-request value belongs instead: $_SERVER, which is rebuilt per request and travels with its own request across a suspension; whatever your framework scopes to the request, with the condition that disqualifies one — a container binding or a request stack set up once during bootstrap is worker state, with the same reach as $_ENV; and the request object's attributes container, for as long as that object is passed along, because oxphp_http_request() builds a fresh Request, and a fresh empty container with it, on every call.
  • docs/features/fiber-multiplexing.md now lists the six superglobals the scheduler actually parks per fiber ($_GET, $_POST, $_COOKIE, $_SERVER, $_FILES, $_REQUEST) instead of naming the class, says that what is not on that list is shared while two requests overlap on a thread, and states the $_ENV carve-out. Enumerating rather than quantifying is deliberate: $_ENV is not the only PHP state outside the parked set, so any "the one exception" phrasing would have been false in a different direction.
  • docs/features/worker-mode.md gains a $_ENV bullet in What Persists — the list an operator reads as the inventory of surviving state, and the one place $_ENV was absent from — and its "Between requests" reset list now names the setting the carve-out has always depended on, PHP's default auto_globals_jit=1.

No behaviour changes — documentation only.

Verification

  • scripts/gen-llms-txt.sh --check — regenerated and up to date (58 pages).
  • scripts/check-links.sh — 641 in-repo links, none broken, the new cross-page anchors included.
  • cargo fmt -- --check, cargo clippy --no-default-features -- -D warnings, cargo test --no-default-features — clean. No Rust or C source is touched on this branch.
  • The sequential half of what the note describes is already held by existing tests: a worker test writes $_ENV from inside a request handler and requires the value back on a later request, and the filter_input test asserts the same from the other side.
  • The upstream fact the neighbouring filter_input(INPUT_ENV, …) note rests on was re-read at both supported PHP versions: php_auto_globals_create_env() leaves the $_ENV symbol-table entry and the engine's own copy sharing one array, so copy-on-write separates them at the first userland write — whoever makes it. That is why the filter extension keeps returning environment values after a handler has written to $_ENV, and the note now says so without attributing the write to the bootstrap.

Docs:
  - The `$_ENV` section described the pinning only from the bootstrap side, so a reader takes a handler write for request state. Any write survives: every later request that worker serves reads it, and so does a request multiplexed alongside the writer while it is parked. Name the per-request homes that do work, and what disqualifies a framework's.
  - The fiber page promised full isolation and listed superglobals as a class, which was never true of `$_ENV`. List the six names the scheduler actually parks and say that what is left is shared while requests overlap.
  - Scope the worker page's reset list to `auto_globals_jit=1`, the setting the carve-out has always depended on and never stated.
@diolektor
diolektor merged commit 763c895 into main Sep 19, 2026
7 checks passed
@diolektor
diolektor deleted the fix/worker-mode-docs-omit-that-env-writes-are-worker-global branch September 19, 2026 19:49
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