Skip to content

Commit 4bc130a

Browse files
LostBeardclaude
andcommitted
SpawnDev.ILGPU + ILGPU.P2P 4.9.4-rc.1: TwoTab phantom-alive close + Wasm linear-memory option
ILGPU.P2P (closes P2PSwarm.TwoTab_PeerDiscovery regression): P2PWebRtcBridge.wire.OnClose now filters phantom-alive wires (where `Destroyed=false` but underlying transport is dead) using the new `Wire.SimplePeer.IsTransportDead` accessor in SpawnDev.WebTorrent 3.2.3-rc.2. Both filter sites updated: the wireSet `RemoveWhere` and the `torrent.Wires` cross-check walk. Catches the Chromium-under-Playwright bug where `connectionstatechange` doesn't propagate to "failed" on remote tab close, leaving the wire's Destroyed flag false and inflating the canonical wireSet count. Verified: TwoTab passes 1m 37s standalone (was failing at 90s timeout in 4.9.2-rc.34); LargeBuffer_100MB passes 3m 37s standalone (no regression). SpawnDev.WebTorrent dep bumped 3.2.2 -> 3.2.3-rc.2. ILGPU (Wasm backend - configurable linear memory ceiling): Adds `WasmBackendOptions.MaxLinearMemoryPages` (default 16384 / 1 GiB, configurable up to 65536 / 4 GiB) so consumers whose working set exceeds 1 GiB can opt into a larger SharedArrayBuffer-backed WebAssembly.Memory `maximum`. Threaded through `WasmAccelerator.Create` and the cached-memory `eval` strings; replaces the previous hardcoded `maximum: 16384` at two call sites. Default behavior unchanged - additive option. Required by SpawnDev.ILGPU.ML's DA3-Small graph executor where total live allocations exceed 1 GiB at op 93. Note: cached memory's maximum is fixed at instantiation; raising the option after the accelerator already reused the cached memory has no effect. Co-authored-by: TJ <lostit1278@gmail.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4430b67 commit 4bc130a

5 files changed

Lines changed: 53 additions & 9 deletions

File tree

SpawnDev.ILGPU.P2P/P2PWebRtcBridge.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,16 @@ string CanonicalPeerId() =>
124124
wireSet.Remove(wire);
125125
beforeFilter = wireSet.Count;
126126
wireSetDump = string.Join(",",
127-
wireSet.Select(w => $"d={w.Destroyed}/p={w.PeerId?[..Math.Min(8, w.PeerId.Length)] ?? "null"}"));
128-
wireSet.RemoveWhere(w => w.Destroyed);
127+
wireSet.Select(w => $"d={w.Destroyed}/td={w.SimplePeer?.IsTransportDead ?? false}/p={w.PeerId?[..Math.Min(8, w.PeerId.Length)] ?? "null"}"));
128+
// Filter phantom wires whose Destroyed flag has not yet been set
129+
// (Chromium-under-Playwright: connectionstatechange event chain
130+
// doesn't propagate on remote tab close, so wire.OnClose never
131+
// fires -> Destroyed stays false -> wireSet count stays inflated
132+
// -> isLastWireForCanonical wrongly false -> peer never
133+
// unregisters). IsTransportDead consults the peer's underlying
134+
// transport directly: PC connectionState in {failed,closed} OR
135+
// data channel was once open and is no longer.
136+
wireSet.RemoveWhere(w => w.Destroyed || (w.SimplePeer?.IsTransportDead ?? false));
129137
afterFilter = wireSet.Count;
130138
if (wireSet.Count == 0)
131139
_wiresByBtPeerId.TryRemove(canonical!, out _);
@@ -207,6 +215,12 @@ string CanonicalPeerId() =>
207215
{
208216
if (otherWire == wire) continue;
209217
if (otherWire.Destroyed) continue;
218+
// Skip phantom-alive wires whose underlying transport is gone but whose
219+
// Destroyed flag has not yet been set. Same Chromium-under-Playwright
220+
// bug as the wireSet filter above; without this check the foreach would
221+
// see the phantom and wrongly conclude a live replacement exists,
222+
// skipping UnregisterPeer indefinitely.
223+
if (otherWire.SimplePeer?.IsTransportDead == true) continue;
210224
if (string.Equals(otherWire.PeerId, canonical, StringComparison.Ordinal))
211225
return;
212226
}

SpawnDev.ILGPU.P2P/SpawnDev.ILGPU.P2P.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
<TargetFramework>net10.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
7-
<Version>4.9.3</Version>
8-
<PackageReleaseNotes>v4.9.3 stable: bundle bump in lockstep with SpawnDev.ILGPU 4.9.3. P2P source unchanged from 4.9.2-rc.35 (same phantom-DESTROYED-wire fix in `P2PWebRtcBridge.wire.OnClose`, same 240s/480s `LargeBuffer_100MB` test budgets, same `SpawnDev.WebTorrent 3.2.2` stable transitive dependency). Bumped to track the SpawnDev.ILGPU 4.9.3 stable release (new `ArrayView&lt;T&gt;.CopyToHostAsync()` partial-readback extension + WebGPU `Half` NaN/Inf codegen fix). Same known issue: `P2PSwarm.TwoTab_PeerDiscovery` still fails (Chromium-under-Playwright phantom-ALIVE wire issue; needs separate active-liveness probe).</PackageReleaseNotes>
7+
<Version>4.9.4-rc.1</Version>
8+
<PackageReleaseNotes>v4.9.4-rc.1: closes `P2PSwarm.TwoTab_PeerDiscovery` regression. `P2PWebRtcBridge.wire.OnClose` now filters phantom-alive wires (where `Destroyed=false` but underlying transport is dead) using the new `Wire.SimplePeer.IsTransportDead` accessor in SpawnDev.WebTorrent 3.2.3-rc.2. Catches the Chromium-under-Playwright bug where `connectionstatechange` does not propagate to `"failed"` on remote tab close, leaving wire's `Destroyed` flag false and inflating the canonical wireSet count. Both bridge filter sites updated: the wireSet `RemoveWhere` and the `torrent.Wires` cross-check walk. SpawnDev.WebTorrent dep bumped 3.2.2 -&gt; 3.2.3-rc.2 (rc.2 ships synthesis-aware IsTransportDead — reads RtcPeer's `_lastObservedPcState` captured from the OnConnectionStateChange event instead of the JS-native `connectionState` which Chromium-under-Playwright leaves stuck at `"connected"` indefinitely).</PackageReleaseNotes>
99
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
1010
<GenerateDocumentationFile>true</GenerateDocumentationFile>
1111
<EmbedAllSources>true</EmbedAllSources>
@@ -28,7 +28,7 @@
2828

2929
<ItemGroup>
3030
<ProjectReference Include="..\SpawnDev.ILGPU\SpawnDev.ILGPU.csproj" />
31-
<PackageReference Include="SpawnDev.WebTorrent" Version="3.2.2" />
31+
<PackageReference Include="SpawnDev.WebTorrent" Version="3.2.3-rc.2" />
3232
</ItemGroup>
3333

3434
</Project>

SpawnDev.ILGPU/SpawnDev.ILGPU.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
<TargetFramework>net10.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
7-
<Version>4.9.3</Version>
8-
<PackageReleaseNotes>v4.9.3 stable: bug-fix follow-up to v4.9.2. (1) New `ArrayView&lt;T&gt;.CopyToHostAsync()` extension: real per-backend partial readback for sub-views. WebGPU uses `queue.CopyBufferToBuffer` + `mapAsync` of just the slice; WebGL uses the GL-worker's partial-range readback; Wasm slices the SharedArrayBuffer slot directly; CUDA / OpenCL / CPU use ILGPU's native `view.CopyToCPU(target)` which encodes the partial range through the view's offset and length. Closes the `Buffer.BlockCopy` cardinal-rule violation in SpawnDev.Codecs decoder integration - one device buffer can now be split into per-channel / per-plane host arrays without iterating over codec data on the host. (2) WebGPU `Half` NaN/Inf bit-pattern codegen fix: WGSL multi-compare paths emit IEEE 754 bit-pattern checks; the 4.9.2 codegen used the f32 mask constants and `bitcast&lt;u32&gt;(operand)` directly, but WGSL rejects `bitcast&lt;u32&gt;(f16)`. Codegen now routes f16 through `bitcast&lt;u32&gt;(vec2&lt;f16&gt;(x, 0.0h))` with f16 mask constants (`0x7C00` exponent, `0x03FF` mantissa). Closes `HalfNaNComparisonTest` failure on WebGPU. (3) `P2P_Dispatcher_Create` test expectation aligned with the intentional 60s `DispatchTimeoutMs` default (was historically asserting the now-too-tight 30s). See [CHANGELOG.md](CHANGELOG.md) for the full per-version history.</PackageReleaseNotes>
7+
<Version>4.9.4-rc.1</Version>
8+
<PackageReleaseNotes>v4.9.4-rc.1: configurable Wasm linear-memory ceiling. Adds `WasmBackendOptions.MaxLinearMemoryPages` (default 16384 / 1 GiB) so consumers whose working set exceeds the default can opt into a larger SharedArrayBuffer-backed `WebAssembly.Memory` `maximum`. Browsers support up to 65536 pages (4 GiB) for shared memory. Default unchanged - existing consumers see identical behavior. Required by `SpawnDev.ILGPU.ML`'s DA3-Small graph executor where total live allocations exceed 1 GiB at op 93. Note: the cached memory's `maximum` is fixed at instantiation; raising the option after the accelerator already reused the cached memory has no effect. See [CHANGELOG.md](CHANGELOG.md) for the full per-version history.</PackageReleaseNotes>
99
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
1010
<GenerateDocumentationFile>true</GenerateDocumentationFile>
1111
<EmbedAllSources>true</EmbedAllSources>

SpawnDev.ILGPU/Wasm/Backend/WasmBackend.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,6 +1179,19 @@ public class WasmBackendOptions
11791179
/// keeps the descheduling window short.
11801180
/// </summary>
11811181
public int WorkerCount { get; set; } = Math.Max(2, WasmILGPUDevice.GetHardwareConcurrency() - 2);
1182+
1183+
/// <summary>
1184+
/// Maximum SharedArrayBuffer-backed <c>WebAssembly.Memory</c> size in 64 KiB pages.
1185+
/// Default <c>16384</c> (1 GiB), the conservative ceiling that fits within Chrome's
1186+
/// per-renderer SharedArrayBuffer reservation budget without contending with other
1187+
/// tabs. Browsers support up to <c>65536</c> pages (4 GiB) for shared memory; raise
1188+
/// only when the consumer's working set genuinely exceeds 1 GiB and the host has
1189+
/// the headroom (large-model ML inference is the canonical case — see
1190+
/// <c>SpawnDev.ILGPU.ML</c>'s DA3-Small graph executor). Note: the cached memory's
1191+
/// <c>maximum</c> is fixed at instantiation; raising this option after the
1192+
/// accelerator has already allocated and reused the cached memory has no effect.
1193+
/// </summary>
1194+
public int MaxLinearMemoryPages { get; set; } = 16384;
11821195
}
11831196

11841197
/// <summary>

SpawnDev.ILGPU/Wasm/WasmAccelerator.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ public class WasmAccelerator : KernelAccelerator<WasmCompiledKernel, WasmKernel>
3838
// Worker count for parallel dispatch
3939
private int _workerCount = 4;
4040

41+
/// <summary>
42+
/// Maximum SharedArrayBuffer-backed WebAssembly.Memory size in 64 KiB pages.
43+
/// Set at construction from <see cref="WasmBackendOptions.MaxLinearMemoryPages"/>
44+
/// (default 16384 / 1 GiB). Used as the <c>maximum</c> argument when the accelerator
45+
/// allocates the cached <c>WebAssembly.Memory</c> on first dispatch — once the cached
46+
/// memory exists, <c>memory.grow</c> can extend its <c>initial</c> up to this value
47+
/// but cannot raise the <c>maximum</c>. Override before the first dispatch.
48+
/// </summary>
49+
private int _maxLinearMemoryPages = 16384;
50+
4151
/// <summary>
4252
/// Number of Web Workers actually used for parallel kernel dispatch.
4353
/// Set at construction from <see cref="WasmBackendOptions.WorkerCount"/>
@@ -47,6 +57,12 @@ public class WasmAccelerator : KernelAccelerator<WasmCompiledKernel, WasmKernel>
4757
/// </summary>
4858
public int WorkerCount => _workerCount;
4959

60+
/// <summary>
61+
/// Maximum SharedArrayBuffer-backed WebAssembly.Memory size in 64 KiB pages,
62+
/// configured at <see cref="Create"/> time via <see cref="WasmBackendOptions.MaxLinearMemoryPages"/>.
63+
/// </summary>
64+
public int MaxLinearMemoryPages => _maxLinearMemoryPages;
65+
5066
/// <summary>
5167
/// Reusable worker pool — lazily initialized on first dispatch.
5268
/// Workers are created once and reused across kernel dispatches.
@@ -209,6 +225,7 @@ public static async Task<WasmAccelerator> Create(Context context, WasmBackendOpt
209225
}
210226
catch { }
211227
accelerator._workerCount = options?.WorkerCount ?? Math.Max(2, hwConcurrency - 2);
228+
accelerator._maxLinearMemoryPages = options?.MaxLinearMemoryPages ?? 16384;
212229
var backend = new WasmBackend(context, options ?? new WasmBackendOptions());
213230
accelerator.Backend = backend;
214231
accelerator.Init(backend);
@@ -711,7 +728,7 @@ private async Task RunKernelAsync(
711728
_cachedWasmPages = wasmPages;
712729
_cachedWasmMemory = js.Call<JSObject>(
713730
"eval",
714-
$"new WebAssembly.Memory({{ initial: {wasmPages}, maximum: 16384, shared: true }})");
731+
$"new WebAssembly.Memory({{ initial: {wasmPages}, maximum: {_maxLinearMemoryPages}, shared: true }})");
715732
_cachedMemoryBuffer = _cachedWasmMemory.JSRef!.Get<SharedArrayBuffer>("buffer");
716733
_initializedWorkers.Clear();
717734
}
@@ -746,7 +763,7 @@ private async Task RunKernelAsync(
746763
_cachedWasmPages = wasmPages;
747764
_cachedWasmMemory = js.Call<JSObject>(
748765
"eval",
749-
$"new WebAssembly.Memory({{ initial: {wasmPages}, maximum: 16384, shared: true }})");
766+
$"new WebAssembly.Memory({{ initial: {wasmPages}, maximum: {_maxLinearMemoryPages}, shared: true }})");
750767
_cachedMemoryBuffer = _cachedWasmMemory.JSRef!.Get<SharedArrayBuffer>("buffer");
751768
_initializedWorkers.Clear();
752769
}

0 commit comments

Comments
 (0)