diff --git a/.chronus/changes/fix-java-emitter-build-progress-hang-2026-07-21-17-30-00.md b/.chronus/changes/fix-java-emitter-build-progress-hang-2026-07-21-17-30-00.md new file mode 100644 index 0000000000..01b5d90b28 --- /dev/null +++ b/.chronus/changes/fix-java-emitter-build-progress-hang-2026-07-21-17-30-00.md @@ -0,0 +1,7 @@ +--- +changeKind: internal +packages: + - "@azure-tools/typespec-java" +--- + +Fix the Java emitter build intermittently hanging a full parallel `pnpm build`. The build scripts run under turbo, which puts each task in a background process group under the build's controlling terminal; when a PowerShell cmdlet (notably `Copy-Item -Recurse`) rendered `Write-Progress`, pwsh touched that terminal and the OS stopped it with SIGTTIN/SIGTTOU, hanging forever. The `.ps1` build scripts now set `$ProgressPreference = 'SilentlyContinue'`. Also make the pinned-`core/` fetch conditional (only when the commit is missing) and non-interactive (`GIT_TERMINAL_PROMPT=0`, `pwsh -NonInteractive`) to remove secondary hang/network vectors. diff --git a/packages/typespec-java/Build-Generator.ps1 b/packages/typespec-java/Build-Generator.ps1 index 437b78acf5..2e75a5b461 100644 --- a/packages/typespec-java/Build-Generator.ps1 +++ b/packages/typespec-java/Build-Generator.ps1 @@ -10,6 +10,11 @@ $ErrorActionPreference = "Stop" +# See Copy-Sources.ps1: silence progress so no cmdlet touches the controlling +# terminal from turbo's background process group (which would SIGTTIN/SIGTTOU-stop +# this process and hang the parallel build). +$ProgressPreference = "SilentlyContinue" + $packageRoot = $PSScriptRoot $generatorRoot = Join-Path $packageRoot "generator" $generatorPom = Join-Path $generatorRoot "pom.xml" diff --git a/packages/typespec-java/Copy-Sources.ps1 b/packages/typespec-java/Copy-Sources.ps1 index 607b8db41a..45c8e6f8e1 100644 --- a/packages/typespec-java/Copy-Sources.ps1 +++ b/packages/typespec-java/Copy-Sources.ps1 @@ -13,6 +13,16 @@ $ErrorActionPreference = "Stop" +# Disable progress rendering. Under a full parallel `pnpm build`, turbo runs each +# task's process in its own background process group under the build's controlling +# terminal. When a cmdlet here (notably `Copy-Item -Recurse`, which emits a +# Write-Progress) renders progress, PowerShell touches that terminal from a +# background process group and the OS stops the process with SIGTTIN/SIGTTOU -- it +# never resumes, so the emitter build appears to hang forever (intermittently, and +# only for this package since it is the only one that shells out to pwsh). Silencing +# progress removes that terminal interaction entirely. +$ProgressPreference = "SilentlyContinue" + $packageRoot = $PSScriptRoot $repoRoot = Resolve-Path (Join-Path $packageRoot ".." "..") $coreRoot = Join-Path $repoRoot "core" diff --git a/packages/typespec-java/CoreCommit.ps1 b/packages/typespec-java/CoreCommit.ps1 index 3cbc8299fb..815dcbe29e 100644 --- a/packages/typespec-java/CoreCommit.ps1 +++ b/packages/typespec-java/CoreCommit.ps1 @@ -41,6 +41,19 @@ function Get-CoreSourceRoot { [string] $PackageRoot ) + # Never let any git invocation below block on an interactive credential or + # host-key prompt. During a parallel `pnpm build` the emitter build runs + # headless (turbo captures its stdio / attaches a pseudo-terminal), so a git + # prompt has nowhere to read from and would hang the whole build forever -- + # observed as an intermittent, Java-only build hang. Make git fail fast instead. + $env:GIT_TERMINAL_PROMPT = "0" + + # Silence progress: a cmdlet rendering Write-Progress (e.g. the archive + # extraction below) touches the controlling terminal, and under turbo's + # background process group that stops this process with SIGTTIN/SIGTTOU and + # hangs the build. Scoped to this function so callers are unaffected. + $ProgressPreference = "SilentlyContinue" + $originSha = (git -C $CoreRoot rev-parse HEAD).Trim() if ($LASTEXITCODE -ne 0) { Write-Error "Failed to read the current SHA of the 'core' submodule at: $CoreRoot`nMake sure it is checked out (git submodule update --init --recursive)." @@ -65,9 +78,17 @@ function Get-CoreSourceRoot { return @{ Root = $liveRoot; TempDir = $null } } - # Make sure the target commit is available locally (it may be newer than what - # has been fetched); ignore failure if it is already present. - git -C $CoreRoot fetch --quiet origin $targetSha 2>$null + # Make sure the target commit is available locally (the pin may be newer than + # what has been fetched). Only hit the network when the commit is genuinely + # missing: this code runs on EVERY emitter build and doc regen, and a `git + # fetch` on core/ is the only per-build network operation in the whole repo + # build, so doing it unconditionally turns any transient network/credential + # stall into a Java-only build hang. `git cat-file -e ^{commit}` is a + # cheap, offline existence check. + git -C $CoreRoot cat-file -e "${targetSha}^{commit}" 2>$null + if ($LASTEXITCODE -ne 0) { + git -C $CoreRoot fetch --quiet origin $targetSha 2>$null + } # Only move forward: use the target when it is a descendant (newer) of the # current checkout. `merge-base --is-ancestor A B` exits 0 when A is an ancestor @@ -85,23 +106,31 @@ function Get-CoreSourceRoot { $tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("typespec-java-core-" + [System.Guid]::NewGuid().ToString("N")) New-Item -ItemType Directory -Path $tempDir | Out-Null $zipPath = Join-Path $tempDir "core.zip" + $extractDir = Join-Path $tempDir "src" try { - # Produce a zip (not tar) and expand it with the built-in Expand-Archive: + # Produce a zip (not tar) and expand it with .NET's ZipFile.ExtractToDirectory: # this keeps the whole flow inside PowerShell/.NET and avoids depending on an # external `tar`, whose behavior varies by platform (notably Git for Windows' # GNU tar treats a drive-letter path like C:\...\core.tar as a remote host). + # + # ExtractToDirectory is used instead of Expand-Archive on purpose: the archived + # subtree holds several thousand files, and Expand-Archive emits a per-entry + # Write-Progress that makes it pathologically slow on that many files (worse when + # its output is captured by a parallel `pnpm build`), to the point the emitter + # build looks hung. ZipFile.ExtractToDirectory has no such overhead. git -C $CoreRoot archive --format=zip -o $zipPath "${targetSha}:$script:CoreJavaSubtree" if ($LASTEXITCODE -ne 0) { Write-Error "Failed to archive core commit ${targetSha}:$script:CoreJavaSubtree." } - Expand-Archive -Path $zipPath -DestinationPath $tempDir -Force + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory($zipPath, $extractDir) } catch { Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue throw } Remove-Item $zipPath -Force - return @{ Root = $tempDir; TempDir = $tempDir } + return @{ Root = $extractDir; TempDir = $tempDir } } # Clean up the temporary extraction created by Get-CoreSourceRoot (no-op when the diff --git a/packages/typespec-java/package.json b/packages/typespec-java/package.json index b8aa438970..43e339844e 100644 --- a/packages/typespec-java/package.json +++ b/packages/typespec-java/package.json @@ -30,8 +30,8 @@ "scripts": { "clean": "rimraf ./dist ./temp ./generator", "build": "pnpm build:emitter && pnpm build:generator", - "build:emitter": "pwsh -NoProfile -File Copy-Sources.ps1 && tsc -p .", - "build:generator": "pwsh -NoProfile -File Build-Generator.ps1", + "build:emitter": "pwsh -NoProfile -NonInteractive -File Copy-Sources.ps1 && tsc -p .", + "build:generator": "pwsh -NoProfile -NonInteractive -File Build-Generator.ps1", "watch": "tsc -p . --watch", "test": "vitest run", "regen-docs": "pnpm run build:emitter && tspd doc . --enable-experimental --output-dir ../../website/src/content/docs/docs/emitters/clients/typespec-java/reference --skip-js"