cluster: make cluster/fork work under WASIX (fork IPC + reuseport scheduling) - #115
Open
Arshia001 wants to merge 8 commits into
Open
cluster: make cluster/fork work under WASIX (fork IPC + reuseport scheduling)#115Arshia001 wants to merge 8 commits into
Arshia001 wants to merge 8 commits into
Conversation
The IPC read path was stubbed to ENOSYS under __wasi__, killing every child_process.fork / cluster.fork message channel on first readiness (stream error -> fd closed -> sends fail EPIPE). With the fix, fork IPC message channels work end-to-end under WASIX: worker online handshake, process.send in both directions, and cluster's listen negotiation all function. Cluster serving still needs connection handle-passing (or a reuseport-style strategy) and js-firekylin stays skipped on WASIX. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
uv__sock_reuseport failed UV_ENOTSUP under __wasi__ before the runtime was consulted, even though the whole path below works (wasix-libc maps SO_REUSEPORT to sock_set_opt_flag; wasmer applies it to the host socket before bind). Verified under WASIX: same-port listeners in one process and across forked processes, EADDRINUSE still enforced without the flag, 40 connections balanced 18/22 across two worker processes, and Node's test-dgram-reuseport.js passes. This provides the primitive for the reuseport-based cluster scheduling strategy that would let cluster-served apps (js-firekylin) run on WASIX. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WASIX cannot pass listen handles between processes (no SCM_RIGHTS), which made both of Node cluster's scheduling strategies unusable there: round robin passes every accepted connection to a worker, and the shared-handle mode passes the listen handle itself. SO_REUSEPORT, however, works end to end under WASIX (wasix-libc -> sock_set_opt_flag -> wasmer applies it to the host socket), with genuine kernel-level connection balancing across forked guest processes. Add a third scheduling strategy for WASIX. Project policy keeps the Node lib/ tree byte-identical to upstream, so it lives in native code (src/edge_cluster_wasix.cc) and is installed from EdgeRuntime before the main builtin executes: in WASIX cluster workers (NODE_UNIQUE_ID still present at that point), an embedded script replaces the worker-side cluster._getServer — an exported, replaceable property — so TCP port listens bind their own UV_TCP_REUSEPORT handle instead of asking the primary for one, and report the 'listening' act for the primary's bookkeeping. No primary-side changes are needed at all: the primary never learns a handle key, so its registry and cleanup paths are untouched. UDP, fd, and pipe listens keep the upstream path, as does everything on native targets (compile-time gate). Known limitation inherited from the child-only shape: _getServerData/ _setServerData is not round-tripped through the primary (e.g. TLS session ticket keys stay per-worker). Verified under WASIX: cluster workers balance raw TCP connections 13/11, cluster 'listening' events fire in the primary, and js-firekylin (ThinkJS, cluster-served) passes the framework test. Regressions green: js-firekylin native (round robin unchanged), js-hedgedoc/js-rssmonster WASIX, js-svelte native. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With fork IPC (libuv-wasix plain read) and the cluster reuseport strategy in place, 11 of the 17 tests in WASIX_SKIP_CLUSTER_FORK_TESTS pass and are removed from the skip list, including TCP cluster serving (test-http-server-drop-connections-in-cluster, test-tls-ticket-cluster) and the child_process fork/messaging tests (test-diagnostics-channel-process, the domain and http fork harnesses). Two entries were misfiled and move to their real groups: test-http-client-with-create-connection fails on a unix-socket listen (unix-socket group) and test-crypto-secure-heap fails on OpenSSL secure heap (crypto group). What remains cluster-specific is UDP cluster listens, which still go through shared-handle passing, plus the known_issues negative test whose error-swallowing path (exit 0 on non-success worker messages) engages now that fork IPC delivers messages — the upstream known issue is not observable under WASIX. Full wasix quickjs suite locally: 1681 passed, 0 failed (baseline before: 1671 with the old skip list). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
UDP cluster listens went through shared-handle passing and were the last cluster capability broken under WASIX. The worker-side override now also covers udp4/udp6 port listens via dgram._createSocketHandle with UV_UDP_REUSEPORT; the kernel distributes datagrams between the workers by source hash (flows pin to a worker) instead of shared-socket delivery. Two contract details surfaced by the upstream tests: - _getServer callbacks must stay asynchronous (an IPC round trip upstream); the override now defers cb via process.nextTick, which test-dgram-cluster-close-during-bind's close-during-bind window depends on. - dgram passes the raw bind() arguments through: options.port can be null, undefined, or the bind callback function (socket.bind(cb)); all of those mean an ephemeral-port listen per the bind([port][, address][, callback]) signature. WASIX_SKIP_CLUSTER_FORK_TESTS is now empty: every cluster/fork test in the wasix lanes passes, including the known_issues negative test (back to failing-as-expected: with reuseport the port-0 rebind scenario behaves deterministically again). Verified: cluster UDP echo distributes 11/5 across two workers; full wasix quickjs suite 1686 passed / 0 failed; js-firekylin green on WASIX and native. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The test spawns an external cksum binary in the guest; the first exec cold-downloads and LLVM-compiles wasmer/coreutils, which exceeds the per-test timeout on CI runners with an empty wasmer cache. It passes locally with a warm cache, so this is an environment cost, not a cluster/fork or subprocess capability gap. Filed under the subprocess-shell group. (CI wasix suite was otherwise green: 1685 passed / 1 failed.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same class as test-http-chunk-problem: the test execs ab through a shell,
and on CI runners with an empty wasmer cache the first external exec
cold-downloads and compiles wasmer/bash + wasmer/coreutils, exceeding the
per-test timeout. Locally the test self-skips gracefully ('problem
spawning ab') because the warm-cached shell starts fast enough. These two
are the only external-binary tests among the recent cluster/fork unskips;
the remaining nine are node-child-only and passed CI twice.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ipping test-http-chunk-problem (spawns cat) and test-http-full-response (execs ab through a shell) rely on guest binaries that ARE available (wasmer/bash, wasmer/coreutils); their CI timeouts came from the first exec cold- downloading and compiling those packages, not from a capability gap. Give them the scaled timeout (WASIX_SLOW_TESTS, 12x) and keep the coverage rather than skipping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Split out of #111.
Makes Node's
cluster(andchild_process.forkIPC) functional under WASIX:read()for IPC streams — the IPC read path was stubbed to ENOSYS under__wasi__, killing every fork/cluster message channel. Fork IPC now works end-to-end (online handshake, bidirectionalprocess.send, listen negotiation).uv__sock_reuseportfailed UV_ENOTSUP before consulting the runtime, though the whole path below works (wasix-libc →sock_set_opt_flag→ wasmer applies it to the host socket before bind). (libuv-wasix changes: wasix: enable SO_REUSEPORT in uv__sock_reuseport wasix-org/libuv#9)UV_TCP_REUSEPORT/UV_UDP_REUSEPORThandles and the host kernel balances connections. Implemented insrc/edge_cluster_wasix.cc(project policy keepslib/byte-identical to upstream) as an embedded script that replaces the worker-sidecluster._getServer; no primary-side changes. Compile-time gated to WASIX.WASIX_SKIP_CLUSTER_FORK_TESTSis now empty: every cluster/fork test passes in the wasix lanes. The two tests that exec external guest binaries (test-http-chunk-problem,test-http-full-response) move to the scaled-timeout slow bucket (cold wasmer cache compiles on CI runners), not the skip list.Known limitation:
_getServerData/_setServerDatais not round-tripped through the primary (TLS session ticket keys stay per-worker).Verified under WASIX: raw TCP connections balance 13/11 across two workers, UDP echo distributes 11/5, full wasix quickjs suite 1686 passed / 0 failed.
🤖 Generated with Claude Code