From 3a7dce0249d9724480b5a475a8bf84d419435a2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 25 Aug 2026 19:13:41 +0200 Subject: [PATCH 1/3] fix(net): match socket overload native ABI --- .github/actions/setup-llvm22/action.yml | 52 ++++++++++--------- changelog.d/8831-release-net-abi.md | 1 + .../src/lower_call/native_module_dispatch.rs | 13 +++++ .../src/lower_call/native_table/net_events.rs | 23 ++++---- 4 files changed, 52 insertions(+), 37 deletions(-) create mode 100644 changelog.d/8831-release-net-abi.md diff --git a/.github/actions/setup-llvm22/action.yml b/.github/actions/setup-llvm22/action.yml index 1aaeba8085..b3c99bf28d 100644 --- a/.github/actions/setup-llvm22/action.yml +++ b/.github/actions/setup-llvm22/action.yml @@ -93,29 +93,25 @@ runs: $llvmArch = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() -eq "Arm64") { "aarch64" } else { "x86_64" } $url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-$ver/clang+llvm-$ver-$llvmArch-pc-windows-msvc.tar.xz" Write-Host "downloading $url" - # #8468: bound the transfer. `--retry` only re-runs a request that - # FINISHED and failed; a stalled connection with no timeout hangs - # forever, and a composite-action step cannot carry its own - # `timeout-minutes`, so the stall consumed this job's whole budget — - # doc-tests (windows) burned GitHub's 6 h default in this exact step - # and was cancelled having run zero tests, which fails - # `full-suite-gate` and blocked the release (run 32298711372). - # `--connect-timeout`/`--max-time` turn that into a fast, retryable - # failure; `--retry-all-errors` makes the retries actually cover - # transport stalls. Keep each attempt to five minutes and the retry - # window to twenty: `--max-time` resets for every retry, so the former - # 25-minute value could take 150+ minutes across six attempts and - # outlive this job's 120-minute cap without reaching any tests. + # #8468: bound the transfer. A composite-action step cannot carry its + # own timeout, and curl's retry timers have proven insufficient when a + # Windows runner wedges inside the native process. Run curl as a child + # and enforce a six-minute wall-clock ceiling around the whole retry + # sequence; healthy hosted-runner setup finishes in under 80 seconds. $archive = "$env:RUNNER_TEMP\llvm.tar.xz" - curl.exe -sSL --retry 5 --retry-all-errors --retry-delay 10 ` - --connect-timeout 30 --max-time 300 --retry-max-time 1200 ` - -o "$archive" $url - # A native command's non-zero exit does NOT trip - # $ErrorActionPreference, so check it explicitly — otherwise a failed - # download fell through to `tar` and surfaced as a confusing archive - # error instead of naming the real problem. - if ($LASTEXITCODE -ne 0) { - Write-Error "::error::LLVM download failed (curl exit $LASTEXITCODE): $url" + $curlArgs = @( + "-sSL", "--retry", "2", "--retry-all-errors", "--retry-delay", "10", + "--connect-timeout", "30", "--max-time", "120", "--retry-max-time", "300", + "-o", $archive, $url + ) + $curl = Start-Process -FilePath "curl.exe" -ArgumentList $curlArgs -NoNewWindow -PassThru + if (-not $curl.WaitForExit(360000)) { + Stop-Process -Id $curl.Id -Force -ErrorAction SilentlyContinue + Write-Error "::error::LLVM download exceeded the 6-minute hard limit: $url" + exit 1 + } + if ($curl.ExitCode -ne 0) { + Write-Error "::error::LLVM download failed (curl exit $($curl.ExitCode)): $url" exit 1 } $bytes = (Get-Item $archive).Length @@ -125,9 +121,15 @@ runs: exit 1 } New-Item -ItemType Directory -Force -Path C:\llvm | Out-Null - tar -xf "$archive" -C C:\llvm --strip-components=1 - if ($LASTEXITCODE -ne 0) { - Write-Error "::error::extracting the LLVM archive failed (tar exit $LASTEXITCODE)" + $tarArgs = @("-xf", $archive, "-C", "C:\llvm", "--strip-components=1") + $tar = Start-Process -FilePath "tar.exe" -ArgumentList $tarArgs -NoNewWindow -PassThru + if (-not $tar.WaitForExit(300000)) { + Stop-Process -Id $tar.Id -Force -ErrorAction SilentlyContinue + Write-Error "::error::extracting the LLVM archive exceeded the 5-minute hard limit" + exit 1 + } + if ($tar.ExitCode -ne 0) { + Write-Error "::error::extracting the LLVM archive failed (tar exit $($tar.ExitCode))" exit 1 } $v = & "C:\llvm\bin\llvm-config.exe" --version diff --git a/changelog.d/8831-release-net-abi.md b/changelog.d/8831-release-net-abi.md new file mode 100644 index 0000000000..520b2bc7b7 --- /dev/null +++ b/changelog.d/8831-release-net-abi.md @@ -0,0 +1 @@ +- Fix `net.Socket.write()`/`end()` overloads passing NaN-boxed arguments through the wrong native ABI, which made a no-argument `end()` write garbage and could hang loopback clients; also hard-bound Windows LLVM download and extraction processes so a wedged runner fails promptly instead of exhausting the doc-test job timeout. diff --git a/crates/perry-codegen/src/lower_call/native_module_dispatch.rs b/crates/perry-codegen/src/lower_call/native_module_dispatch.rs index 66d79d8cfd..d84dcccad0 100644 --- a/crates/perry-codegen/src/lower_call/native_module_dispatch.rs +++ b/crates/perry-codegen/src/lower_call/native_module_dispatch.rs @@ -429,4 +429,17 @@ mod ffi_return_type_tests { "request and reply `.header` must resolve to different runtime symbols" ); } + + #[test] + fn net_socket_write_and_end_match_their_float_ffi_abi() { + for method in ["write", "end"] { + let sig = super::native_module_lookup("net", true, method, Some("Socket")) + .unwrap_or_else(|| panic!("net.Socket.{method} must resolve")); + assert_eq!( + sig.args, + [super::NativeArgKind::F64; 3], + "net.Socket.{method} takes three NaN-boxed f64 arguments" + ); + } + } } diff --git a/crates/perry-codegen/src/lower_call/native_table/net_events.rs b/crates/perry-codegen/src/lower_call/native_table/net_events.rs index f6d1f58575..94065737c5 100644 --- a/crates/perry-codegen/src/lower_call/native_table/net_events.rs +++ b/crates/perry-codegen/src/lower_call/native_table/net_events.rs @@ -310,13 +310,12 @@ pub(super) const NET_EVENTS_ROWS: &[NativeModSig] = &[ method: "write", class_filter: Some("Socket"), runtime: "js_ext_net_socket_write3", - // Issue #1131 — pass the full NaN-boxed JS value (NA_JSV) so - // the runtime can probe Buffer-vs-string-vs-number and read - // through the correct header layout. NA_PTR pre-stripped the - // tag, so `sock.write("ping")` handed the runtime a bare - // StringHeader pointer that it reinterpreted as a - // BufferHeader → garbage on the wire. - args: &[NA_JSV, NA_JSV, NA_JSV], + // Issue #1131 — pass each full NaN-boxed JS value as an f64 so the + // runtime can probe Buffer-vs-string-vs-number and read through the + // correct header layout. This must be NA_F64, not NA_JSV: the Rust FFI + // receives f64 arguments, while NA_JSV uses the integer ABI for + // runtimes whose signatures explicitly take raw i64 bits. + args: &[NA_F64, NA_F64, NA_F64], ret: NR_VOID, }, NativeModSig { @@ -325,11 +324,11 @@ pub(super) const NET_EVENTS_ROWS: &[NativeModSig] = &[ method: "end", class_filter: Some("Socket"), runtime: "js_ext_net_socket_end3", - // Issue #1852 — `socket.end([data])` writes the optional final - // chunk before half-closing. NA_JSV carries the full NaN-boxed - // value so the runtime can probe Buffer/string/number; the - // no-arg `socket.end()` form pads this slot with `undefined`. - args: &[NA_JSV, NA_JSV, NA_JSV], + // Issue #1852 — `socket.end([data])` writes the optional final chunk + // before half-closing. NA_F64 preserves the full NaN-boxed value in + // the floating-point ABI expected by `js_ext_net_socket_end3`; the + // no-arg form pads every missing slot with JS `undefined`. + args: &[NA_F64, NA_F64, NA_F64], ret: NR_VOID, }, NativeModSig { From f55ebcda5acaf71d80683e09c704683ada7a0844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 25 Aug 2026 19:26:26 +0200 Subject: [PATCH 2/3] ci: recover from stalled Windows LLVM extraction --- .github/actions/setup-llvm22/action.yml | 79 ++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 9 deletions(-) diff --git a/.github/actions/setup-llvm22/action.yml b/.github/actions/setup-llvm22/action.yml index b3c99bf28d..4b0df876d1 100644 --- a/.github/actions/setup-llvm22/action.yml +++ b/.github/actions/setup-llvm22/action.yml @@ -121,16 +121,77 @@ runs: exit 1 } New-Item -ItemType Directory -Force -Path C:\llvm | Out-Null - $tarArgs = @("-xf", $archive, "-C", "C:\llvm", "--strip-components=1") - $tar = Start-Process -FilePath "tar.exe" -ArgumentList $tarArgs -NoNewWindow -PassThru - if (-not $tar.WaitForExit(300000)) { - Stop-Process -Id $tar.Id -Force -ErrorAction SilentlyContinue - Write-Error "::error::extracting the LLVM archive exceeded the 5-minute hard limit" - exit 1 + $tarOk = $false + if ($env:PERRY_FORCE_WINDOWS_LLVM_FALLBACK -eq "1") { + Write-Host "diagnostic override: skipping tar.exe and exercising the 7-Zip fallback" + } else { + # A hosted Windows runner has twice left tar.exe alive indefinitely + # after this 800 MiB download completed. Healthy extractions take + # 47-74 seconds, so two minutes leaves margin without sacrificing five + # runner-minutes every time the native extractor wedges. + Write-Host "extracting LLVM with tar.exe (2-minute hard limit)" + $tarArgs = @("-xf", $archive, "-C", "C:\llvm", "--strip-components=1") + $tar = Start-Process -FilePath "tar.exe" -ArgumentList $tarArgs -NoNewWindow -PassThru + if (-not $tar.WaitForExit(120000)) { + Stop-Process -Id $tar.Id -Force -ErrorAction SilentlyContinue + $tar.WaitForExit() + Write-Warning "tar.exe exceeded the 2-minute hard limit; falling back to 7-Zip" + } elseif ($tar.ExitCode -ne 0) { + Write-Warning "tar.exe failed with exit $($tar.ExitCode); falling back to 7-Zip" + } else { + $tarOk = $true + } } - if ($tar.ExitCode -ne 0) { - Write-Error "::error::extracting the LLVM archive failed (tar exit $($tar.ExitCode))" - exit 1 + + if (-not $tarOk) { + # Windows 2022 hosted runners ship 7-Zip. Stream the xz payload into a + # second 7z process so fallback does not require a multi-gigabyte + # intermediate .tar file. Run the pipeline through a bounded child + # cmd process: PowerShell's object pipeline is not binary-safe. + $sevenZipCommand = Get-Command 7z.exe -ErrorAction SilentlyContinue + $sevenZip = if ($sevenZipCommand) { + $sevenZipCommand.Path + } else { + Join-Path $env:ProgramFiles "7-Zip\7z.exe" + } + if (-not (Test-Path -LiteralPath $sevenZip)) { + Write-Error "::error::tar.exe failed and 7z.exe is unavailable at $sevenZip" + exit 1 + } + + Remove-Item -LiteralPath C:\llvm -Recurse -Force + New-Item -ItemType Directory -Force -Path C:\llvm | Out-Null + $stage = Join-Path $env:RUNNER_TEMP "llvm-7zip-stage" + Remove-Item -LiteralPath $stage -Recurse -Force -ErrorAction SilentlyContinue + New-Item -ItemType Directory -Force -Path $stage | Out-Null + + $extractScript = Join-Path $env:RUNNER_TEMP "extract-llvm.cmd" + $extractLines = @( + "@echo off", + "`"$sevenZip`" x `"$archive`" -so | `"$sevenZip`" x -si -ttar `"-o$stage`" -y", + "exit /b %errorlevel%" + ) + Set-Content -LiteralPath $extractScript -Encoding ascii -Value $extractLines + Write-Host "extracting LLVM with 7-Zip (5-minute hard limit)" + $extractArgs = @("/d", "/c", "`"$extractScript`"") + $extract = Start-Process -FilePath "cmd.exe" -ArgumentList $extractArgs -NoNewWindow -PassThru + if (-not $extract.WaitForExit(300000)) { + & taskkill.exe /PID $extract.Id /T /F 2>$null | Out-Null + Write-Error "::error::7-Zip LLVM extraction exceeded the 5-minute hard limit" + exit 1 + } + if ($extract.ExitCode -ne 0) { + Write-Error "::error::7-Zip LLVM extraction failed (exit $($extract.ExitCode))" + exit 1 + } + + $root = Join-Path $stage "clang+llvm-$ver-$llvmArch-pc-windows-msvc" + if (-not (Test-Path -LiteralPath $root -PathType Container)) { + Write-Error "::error::7-Zip extraction did not produce the expected LLVM root: $root" + exit 1 + } + Get-ChildItem -LiteralPath $root -Force | Move-Item -Destination C:\llvm -Force + Remove-Item -LiteralPath $stage -Recurse -Force } $v = & "C:\llvm\bin\llvm-config.exe" --version if (-not $v.StartsWith("22.")) { Write-Error "LLVM is not 22.x ($v)"; exit 1 } From 350d204c8cb2d9e099edf139e2ffb38560878c62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 25 Aug 2026 19:28:41 +0200 Subject: [PATCH 3/3] ci: quote Windows LLVM archive paths --- .github/actions/setup-llvm22/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup-llvm22/action.yml b/.github/actions/setup-llvm22/action.yml index 4b0df876d1..bcc76f4926 100644 --- a/.github/actions/setup-llvm22/action.yml +++ b/.github/actions/setup-llvm22/action.yml @@ -102,7 +102,7 @@ runs: $curlArgs = @( "-sSL", "--retry", "2", "--retry-all-errors", "--retry-delay", "10", "--connect-timeout", "30", "--max-time", "120", "--retry-max-time", "300", - "-o", $archive, $url + "-o", "`"$archive`"", $url ) $curl = Start-Process -FilePath "curl.exe" -ArgumentList $curlArgs -NoNewWindow -PassThru if (-not $curl.WaitForExit(360000)) { @@ -130,7 +130,7 @@ runs: # 47-74 seconds, so two minutes leaves margin without sacrificing five # runner-minutes every time the native extractor wedges. Write-Host "extracting LLVM with tar.exe (2-minute hard limit)" - $tarArgs = @("-xf", $archive, "-C", "C:\llvm", "--strip-components=1") + $tarArgs = @("-xf", "`"$archive`"", "-C", "C:\llvm", "--strip-components=1") $tar = Start-Process -FilePath "tar.exe" -ArgumentList $tarArgs -NoNewWindow -PassThru if (-not $tar.WaitForExit(120000)) { Stop-Process -Id $tar.Id -Force -ErrorAction SilentlyContinue