Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 89 additions & 26 deletions .github/actions/setup-llvm22/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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
Expand All @@ -125,10 +121,77 @@ 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)"
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 (-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 }
Expand Down
1 change: 1 addition & 0 deletions changelog.d/8831-release-net-abi.md
Original file line number Diff line number Diff line change
@@ -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.
13 changes: 13 additions & 0 deletions crates/perry-codegen/src/lower_call/native_module_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
);
}
}
}
23 changes: 11 additions & 12 deletions crates/perry-codegen/src/lower_call/native_table/net_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
Loading