Skip to content

Persist pooled PHP writes to saved sites - #4260

Merged
ashfame merged 12 commits into
trunkfrom
agent/forward-proxyfs-request-end
Aug 14, 2026
Merged

ashfame merged 12 commits into
trunkfrom
agent/forward-proxyfs-request-end

Conversation

@ashfame

@ashfame ashfame commented Aug 13, 2026 •

Copy link
Copy Markdown
Member

What

Persist filesystem changes made by pooled PHP instances to autosaved Playground sites after the request completes.

This supersedes #4240 with the architecture Brandon proposed: PROXYFS propagates replica request completion to the filesystem owner, while the OPFS journal remains solely responsible for deciding when to flush.

Root cause

Playground web uses a pool of PHP instances. Secondary instances access shared paths through PROXYFS backed by the primary PHP instance. Their writes are captured by the primary instance's OPFS journal, but request.end is emitted only by the secondary instance. The primary journal therefore has no completion signal for the proxied request, and recent changes can remain queued in memory instead of reaching OPFS.

Deterministic regression proof

The Chromium test exercises the original saved-site symptom instead of asserting any event or flush machinery introduced by this PR:

  • It writes a unique sentinel outside the proxied mounts, so only the primary PHP runtime can see it.
  • The first request creates a file barrier and remains blocked; the second request then creates its own barrier and remains blocked. The test observes each barrier instead of sleeping to guess when either request started, then overwrites the same file to release that request.
  • The test releases and fully awaits the first request before allowing the second request to write the marker, eliminating a later primary request.end that could accidentally flush it.
  • The completed responses prove the requests ran on primary and secondary, respectively.
  • A public readFileAsText() call proves the marker reached the shared live VFS, then an independent read of the autosaved site's real OPFS directory checks the persisted bytes.

At red SHA 4a5116926, the PR contained only this test. The Chromium storage job reached the final OPFS assertion on all four attempts and consistently received undefined instead of the marker contents; all other 60 tests passed. See the expected failed CI job.

The regression test at the fixed SHA is byte-for-byte identical to the red version.

Implementation

  • proxyFileSystem() forwards a replica's request.end as proxyfs.request.end on the filesystem owner.
  • journalFSEventsToOpfs() handles that lifecycle event through its existing single-flight background flush, alongside direct request.end and filesystem.write triggers.
  • Journal teardown removes the new listener with its existing listeners.

This keeps pool-specific behavior out of Playground's worker and keeps OPFS flush ownership inside the journaling abstraction. It does not change export code or introduce an export lock.

Validation

  • Red CI: Chromium storage job — the regression test alone fails at the persisted-bytes assertion on all four attempts.
  • Local green: universal tests 184 passed / 4 skipped; web tests 60 passed; website rerun 377 passed; affected typechecks and lints passed.
  • Green CI: full CI run and Chromium storage job passed on fixed SHA b9013b91e.

@ashfame ashfame changed the title Propagate proxied request completion to filesystem journals Persist pooled PHP writes to autosaved sites Aug 13, 2026
@ashfame
ashfame force-pushed the agent/forward-proxyfs-request-end branch from 631fe16 to f5fc044 Compare August 13, 2026 18:42
@ashfame
ashfame marked this pull request as ready for review August 13, 2026 19:28
Copilot AI lite review requested due to automatic review settings August 13, 2026 19:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Persist writes made by pooled PHP replicas to autosaved OPFS sites by forwarding replica request completion back to the filesystem owner, and verify persistence via a Chromium E2E regression test.

Changes:

  • Forward request.end from replica runtimes as proxyfs.request.end on the source-of-truth runtime.
  • Trigger OPFS journal flushes on proxyfs.request.end in addition to existing flush triggers.
  • Add a Chromium Playwright test that deterministically reproduces (and guards against) the pooled-write persistence regression.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
packages/playground/website/playwright/e2e/opfs.spec.ts Adds an E2E regression test and OPFS helper to assert pooled replica writes persist to OPFS.
packages/php-wasm/web/src/lib/directory-handle-mount.ts Flush OPFS journal when the new proxyfs.request.end lifecycle event fires.
packages/php-wasm/universal/src/lib/universal-php.ts Adds a new PHP event type for proxied request completion.
packages/php-wasm/universal/src/lib/proxy-file-system.ts Dispatches proxyfs.request.end to the filesystem owner when a replica request ends.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/php-wasm/universal/src/lib/proxy-file-system.ts
Comment thread packages/playground/website/playwright/e2e/opfs.spec.ts Outdated
@ashfame
ashfame marked this pull request as draft August 13, 2026 19:53
@ashfame
ashfame marked this pull request as ready for review August 13, 2026 20:39
@ashfame
ashfame requested a balanced review from Copilot August 13, 2026 20:49
@ashfame ashfame changed the title Persist pooled PHP writes to autosaved sites Persist pooled PHP writes to saved sites Aug 13, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (4)

packages/playground/website/playwright/e2e/opfs.spec.ts:515

  • If the repo enables useUnknownInCatchVariables, error is unknown and error?.name is a type error. Consider typing the catch variable (e.g., catch (error: any)) or narrowing it safely (e.g., error instanceof DOMException && error.name === 'NotFoundError') before checking .name.
			} catch (error) {
				if (error?.name === 'NotFoundError') {
					return undefined;
				}
				throw error;
			}

packages/php-wasm/universal/src/lib/proxy-file-system.ts:229

  • This adds a request.end listener on replica without any corresponding removal/disposal path. If proxyFileSystem() can be called multiple times for the same replica (or replicas can be torn down/replaced), this can leak listeners and cause duplicate proxyfs.request.end dispatches. Consider registering the handler with an AbortSignal/AbortController tied to the proxyFS lifecycle, or returning a dispose() function that removes the listener (by keeping a named handler reference).
	// Filesystem listeners belong to the source of truth. Notify them when a
	// request using that filesystem through PROXYFS finishes on the replica.
	replica.addEventListener('request.end', () => {
		sourceOfTruth.dispatchEvent({ type: 'proxyfs.request.end' });
	});

packages/playground/website/playwright/e2e/opfs.spec.ts:821

  • The 3s poll timeout is fairly tight for OPFS flush/persistence on contended CI machines and may introduce test flakiness even if the behavior is correct. Consider increasing the timeout (or reusing an existing OPFS-sync/flush signal if available) to make this regression test reliably reflect correctness rather than timing.
		await expect
			.poll(
				() =>
					readOpfsWpContentFile(website.page, site.slug, markerName),
				{ timeout: 3000 }
			)
			.toBe(markerContents);

packages/playground/website/playwright/e2e/opfs.spec.ts:768

  • This loop polls every 1ms, which can cause unnecessary CPU usage in the WASM runtime during the test (especially under CI) with little benefit. Bumping the sleep interval (e.g., 10–50ms) reduces busy-waiting while still keeping the test deterministic.
					code: `<?php
file_put_contents(${JSON.stringify(firstRequestBarrierPath)}, 'ready');
while (file_get_contents(${JSON.stringify(firstRequestBarrierPath)}) !== 'release') {
	usleep(1000);
}

@brandonpayton brandonpayton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ashfame I think these changes are a lot cleaner than those in the other PR (#4240, I think). What do you think?

I left one question in the test because it doesn't make sense to me.

Comment thread packages/playground/website/playwright/e2e/opfs.spec.ts Outdated

@brandonpayton brandonpayton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ashfame I'm marking this as Approved but left a couple of notes that would be good to follow up on before merge. Once you've followed up and CI is passing, please feel free to merge this yourself. Thank you!

Comment thread packages/php-wasm/web/src/lib/directory-handle-mount.ts
Comment thread packages/playground/website/playwright/e2e/opfs.spec.ts Outdated
@ashfame
ashfame merged commit fd1b3f2 into trunk Aug 14, 2026
57 checks passed
@ashfame
ashfame deleted the agent/forward-proxyfs-request-end branch August 14, 2026 00:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants