Repository navigation
Fix Text file busy race for fake executables in Infrastructure.Tests - #21749
Conversation
Write fake executables (stub gh, actionlint, curl, dotnet.sh) from a short-lived /bin/sh child so the test host never holds a writable descriptor that a concurrently forked child could inherit, which makes exec fail with ETXTBSY (exit 126). Link the helper into AnalyzeCiFailure.Tests, which now hosts AnalyzeCiFailureWorkflowTests. Supersedes #21558. Fixes #21557. Co-authored-by: David Fowler <davidfowl@gmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 21749Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 21749" |
Tests selector2 / 101 PR test projects · 0 PR jobs, from 8 changed files. Selected PR test projects (2 / 101)
Selected PR jobs (0)none How these were chosen — grouped by what changed🧪 🧪 🧪 🧪 🧪 🧪 🧪 🧪 Job reasonsnone Selection computed for commit |
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues remain for the test-infrastructure changes addressing the executable-writing race.
No open issues 🎉
What changed in this PR
Addresses the Unix “Text file busy” race in test-created executables by moving file writing and permission setting into a child process. The change is limited to test infrastructure; product and packaging behavior are unchanged.
Changes:
- Adds a shared executable-writing helper with a plain file-write path on Windows.
- Migrates fake executable creation across infrastructure and CI-analysis tests, linking the helper into the separate analysis test project.
- Adds concurrent-process regression coverage and content/permission round-trip checks.
| File | Description |
|---|---|
tests/Infrastructure.Tests/WorkflowScripts/LintHandwrittenWorkflowsScriptTests.cs |
Uses the helper to create the fake actionlint executable. |
tests/Infrastructure.Tests/WorkflowScripts/ExtensionReleaseWorkflowTests.cs |
Migrates fake gh creation and removes the separate chmod runner. |
tests/Infrastructure.Tests/TemplateManifest/TemplateManifestGenerationTests.cs |
Uses the synchronous helper for the Unix dotnet.sh stub. |
tests/Infrastructure.Tests/Pipelines/NixCliPackageTests.cs |
Migrates both fake curl executables while preserving permissions. |
tests/Infrastructure.Tests/ExecutableFileTests.cs |
Adds concurrent-launch regression and content/permission tests. |
tests/Infrastructure.Tests/ExecutableFile.cs |
Adds shared synchronous and asynchronous executable-writing helpers. |
tests/AnalyzeCiFailure.Tests/AnalyzeCiFailureWorkflowTests.cs |
Routes fake executable creation through the shared helper. |
tests/AnalyzeCiFailure.Tests/AnalyzeCiFailure.Tests.csproj |
Links the helper into the CI-analysis test project. |
Review effort: Balanced
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Jose Perez Rodriguez (joperezr)
left a comment
There was a problem hiding this comment.
Moving Unix executable-fixture writing into a child process addresses the writable-descriptor inheritance race without changing the fake scripts or their expected results. Existing argument, exit-code, and output assertions remain, and the added regression still requires the intended nonzero stub exit. No product behavior or failure expectation is relaxed.
|
✅ No documentation update needed. docs_optional → test_only (proven false positive). Triggered signal: |
Fixes #21557
Failure
On
main,Infrastructure.Tests.AnalyzeCiFailureWorkflowTests.LastSuccessfulMainRunFallsBackToOlderWindowfailed withExpected: 0 / Actual: 126(run 40984409791, Infrastructure (ubuntu-latest)).Denominator: 1 failure in 115 attempt-1
Infrastructure (ubuntu-latest)jobs in the last ~48h ofci.ymlruns (104 success, 3 failure, 8 cancelled). The other two failures were unrelated, PR-specific breaks.Root cause
This is the
ETXTBSY("Text file busy") race that #21448 fixes forAspire.Acquisition.Tests(#18732). This PR fixes it intests/Infrastructure.Tests, which #21448 doesn't touch.gh,actionlint,curl,dotnet.sh) from the test host withFile.WriteAllText[Async], thenchmodit and run a script that execs it.exec.execa file that any process has open for writing, returningETXTBSY. bash reports that as exit 126.Writing to a temp file and renaming it doesn't help, because the inherited descriptor still refers to the same inode.
Fix
ExecutableFile.WriteAsync/Writetest helper. On Unix it writes the contents (from stdin) and sets the mode inside a short-lived/bin/sh -c 'cat > "$1" && chmod "$2" "$1"'child, so the test host never holds a writable descriptor that another fork could inherit. On Windows it is a plainFile.WriteAllTextAsync.AnalyzeCiFailureWorkflowTestsLintHandwrittenWorkflowsScriptTestsNixCliPackageTestsTemplateManifestGenerationTestsExtensionReleaseWorkflowTests(the separatechmod +xstep is removed)BuildWarningPolicyTestsis unchanged because it runsbash build.shrather than exec'ing the file.Regression test
ExecutableFileTests.WrittenExecutableCanBeExecutedImmediatelyWhileOtherThreadsStartProcesses(Unix only):/bin/sh -c "exit 0".WritesContentsAndExecutableModechecks that the contents and mode round-trip.File.WriteAllTextAsync+File.SetUnixFileModeText file busy)Change since #21558
mainmovedAnalyzeCiFailureWorkflowTests.csinto the newtests/AnalyzeCiFailure.Testsproject. The helper call moved with it, soExecutableFile.csis now linked into that project (<Compile Include="../Infrastructure.Tests/ExecutableFile.cs" Link="shared/ExecutableFile.cs" />). This uses the same pattern the project already uses forRepoRoot.csandCommandResult.cs. Without the link the project fails to build with CS0103.Confidence
ExecutableFileTestsreproduces the race: the old write path failed in 20 of 20 runs, and the new helper failed in 0 of 20. No assertions changed in the converted tests.AnalyzeCiFailure.Tests: 1162 of 1163 passed. The one failure,AnalyzerBuildsFromRealSparseCheckoutWithoutHostileParentImports, comes from NuGet's vulnerability check (audit) being unable to reach nuget.org from the local machine (NU1900/NU1905), not from this change.Infrastructure.Testsclasses this PR touches, all passing: ExecutableFile 2/2, NixCliPackage 3/3, TemplateManifestGeneration 7/7, ExtensionReleaseWorkflow 9/9, LintHandwrittenWorkflowsScript 3/3..csprojchange; no product or packaging changes.How we stop this recurring: New tests that exec a freshly written stub should write it with
ExecutableFile.WriteAsyncinstead ofFile.WriteAllTextfollowed bychmod. The regression test guards the helper itself.🤖 Posted by AspireAgent (AI Agent)