Skip to content

Conversation

@mho22
Copy link
Collaborator

@mho22 mho22 commented Dec 23, 2025

Motivation for the change, related issues

Based on this issue.

Related to this function.

It looks like the ICU data is not found when running the below Blueprint. Even if the file is located in /internal/shared before running the playground.run(...) in run-php.ts.

Implementation details

  • Added a new Intl classes should work when intl is enabled test in playground/website/playwright/e2e/blueprints.spec.ts

Blueprint

{
	"features": {
		"intl": true
	},
	"steps": [
		{
			"step": "runPHP",
			"code": "<?php \n\n$data = array(\n    'F' => 'Foo',\n    'Br' => 'Bar',\n    'Bz' => 'Bz',\n);\n\n$collator = new Collator('en_US');\n$collator->asort($data, Collator::SORT_STRING);\n\nthrow new Exception( json_encode($data, JSON_PRETTY_PRINT ) );"
		}
	]
}

Should throw :

Uncaught Exception: {
    "Br": "Bar",
    "Bz": "Bz",
    "F": "Foo"
} 

But throws :

Uncaught IntlException: Constructor failed

@mho22 mho22 changed the title [ Intl ] Fix Intl feature inside Blueprints in Playground Client [ Intl ] Fix Intl feature inside Blueprints in Playground Dec 23, 2025
@mho22 mho22 force-pushed the fix-intl-feature-issue-in-blueprints branch from f156b6d to 8cb071f Compare December 23, 2025 09:18
@mho22 mho22 force-pushed the fix-intl-feature-issue-in-blueprints branch from 8cb071f to d446c00 Compare December 23, 2025 09:32
@mho22 mho22 mentioned this pull request Dec 23, 2025
3 tasks
@mho22
Copy link
Collaborator Author

mho22 commented Dec 24, 2025

I found the root cause. The above blueprint will run successfully if I comment the proxyFileSystem(...) function in playground-worker-endpoint.ts :

if (!isPrimary) {
	const pathsToShareBetweenPhpInstances = [
		'/tmp',
		requestHandler.documentRoot,
		'/internal/shared',
		'/internal/symlinks',
	];
	const pathsToProxy = pathsToShareBetweenPhpInstances.filter(
		(path) => !isPathToSharedFS(php, path)
	);

	// TODO: Document that this shift is a breaking change.
	// Proxy the filesystem for all secondary PHP instances to
	// the primary one.
	proxyFileSystem(
		await requestHandler.getPrimaryPhp(),
		php,
		pathsToProxy
	);
}

This means something is happening to the /internal/shared/icudt74l.dat file during the proxy.

If I add these lines in the proxyFileSystem(...) function :

+ if( path !== '/internal/shared' ){
	// @ts-ignore
	replica[__private__symbol].FS.mount(
		// @ts-ignore
		replica[__private__symbol].PROXYFS,
		{
			root: path,
			// @ts-ignore
			fs: sourceOfTruth[__private__symbol].FS,
		},
		path
	);
+ }

It runs correctly. I wonder if mounting a file used by PHP internals like the intl extension will corrupt it.

@mho22
Copy link
Collaborator Author

mho22 commented Dec 24, 2025

@adamziel I created a new directory named /internal/private which will be used to store everything related to dynamic extensions. This directory shouldn't be proxied between PHP instances since there seems to be file corruption with the icu.dat file when it is done in /internal/shared.

I tagged you because I wanted to know if /internal/private is a meaningful path to you.

@mho22
Copy link
Collaborator Author

mho22 commented Dec 24, 2025

Maybe /internal/libs ?

@adamziel
Copy link
Collaborator

Hm, not mounting that file worries me. First, what corrupts that file? Could it affect other files and corrupt other data? Second, the system is wired for sharing files between php instances and I worry about the consequences of not sharing them.

The reason we have these mounts is to share a set of files between all the php instances - they're created once in the primary env and then every other php instance sources them from that primary php's filesystem. If we stop mounting, what would happen to icu.dat in non-primary php instances or after the php env is rotated?

@mho22
Copy link
Collaborator Author

mho22 commented Dec 25, 2025

@adamziel Here’s my current understanding of why we were still hitting the Intl issue and how it relates to filesystem sharing.

Each PHP instance is initialized via loadWebRuntime. During that phase, the instance loads and links against its intl.so extension and the corresponding icu.dat file [ which is fetched and memoized at runtime ]. Those links are established during initialization, before any filesystem proxying happens.

Later on, when proxyFileSystem is called, we mount /internal/shared from the primary PHP instance onto the secondary ones using FS.mount. Although the icu.dat file is still visible after the mount, this operation effectively replaces the underlying filesystem for that path. As a result, the ICU runtime inside the PHP instance can no longer use the icu.dat file it was originally linked to during loadWebRuntime, and Intl starts failing [ e.g. Collator constructor errors ].

Even though I can’t say exactly what happens internally inside FS.mount, the observed behavior is consistent : after proxying /internal/shared, PHP’s intl extension can no longer access ICU data reliably. This strongly suggests that remounting that directory breaks the early filesystem bindings created during PHP initialization.

To fix this, my proposal is to isolate native extensions .so and their required runtime assets [ like icu.dat ] into a dedicated, instance-local directory, such as : /internal/private, /internal/libraries or /internal/libs

Each PHP instance would load its extensions from this directory during startup, and that directory would never be proxied or remounted. This keeps ICU and other native extensions stable and instance-safe.

In contrast, /internal/shared should only be used for files that are not tightly coupled to a specific PHP instance and are safe to share across instances.

What do you think ?

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