Skip to content

Dockerfile.windows: harden the xwin step (serialize splat, add --http-retry) - #377

Merged
sosukesuzuki merged 2 commits into
mainfrom
farm/3a3f1633/xwin-splat-race
Jul 31, 2026
Merged

Dockerfile.windows: harden the xwin step (serialize splat, add --http-retry)#377
sosukesuzuki merged 2 commits into
mainfrom
farm/3a3f1633/xwin-splat-race

Conversation

@robobun

@robobun robobun commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Two intermittent failures of the windows-cross base-image layer seen on main, both in Dockerfile.windows step [base 6/14].

1. Multi-arch splat symlink race

bun-webkit-windows-amd64 in run 30514534688 (current main HEAD):

#19 [base  6/14] RUN xwin --accept-license --arch x86_64,aarch64 ... splat ... --output /winsdk
#19 17.10 Error: failed to splat Win11SDK_10.0.26100_libs_x86_64.msi
#19 17.10 Caused by:
#19 17.10     0: unable to symlink from /winsdk/sdk/lib/10.0.26100 to .
#19 17.10     1: File exists (os error 17)

The other four windows-cross matrix entries ran the same layer without cache in the same run and passed.

xwin processes payloads on a rayon pool (payloads.into_par_iter() in ctx.rs). With --arch x86_64,aarch64 there are two SdkLibs MSIs, and each, on completion, creates the same sdk/lib/<version> -> . compatibility symlink guarded only by an .exists() check (splat.rs:782-784). When both arches finish close together both see !exists() and both call symlink(); the second loses with EEXIST. The same pattern exists for sdk/include/<version>. Still present on xwin main (0.9.0 is latest).

Fix: split the step into xwin unpack with full parallelism (download + extract, the network- and CPU-bound part) followed by xwin splat with RAYON_NUM_THREADS=1 so the per-payload symlink creation is serial. splat reuses the warmed download (get_and_validate checks cache_path.exists()) and unpack (prep_unpack reads the .unpack marker) caches, so the serial pass is just file moves and symlinks and adds only a few seconds to a layer that took ~15-22s before. Retrying the whole command is not deterministic, and pre-creating the symlink before xwin runs doesn't work because prep_splat wipes sdk/ on entry.

2. Transient download I/O failure

bun-webkit-windows-amd64 in run 29997282267 (2026-07-23):

#19 14.72 Error: failed to retrieve https://download.visualstudio.microsoft.com/.../78fa3c824c2c48bd4a49ab5969adaaf7.cab
          after 1 tries due to I/O failures reading the response body,
          try using --http-retries to increase the retry count

--http-retry defaults to 0.

Fix: pass --http-retry 5 on both invocations.

…race

xwin processes payloads on a rayon pool. With --arch x86_64,aarch64 there
are two SdkLibs MSIs and each, on completion, creates the same
sdk/lib/<version> -> . compatibility symlink guarded only by an
.exists() check (src/splat.rs:782 in xwin 0.9.0). When both finish close
together the second one fails with EEXIST:

    Error: failed to splat Win11SDK_10.0.26100_libs_x86_64.msi
    Caused by:
        0: unable to symlink from /winsdk/sdk/lib/10.0.26100 to .
        1: File exists (os error 17)

Run `xwin unpack` first on all cores (download + extract, the expensive
part) and then `xwin splat` with RAYON_NUM_THREADS=1 so the symlink
creation is serial. splat reuses the warmed download and unpack caches,
so the serial pass adds only a few seconds.
--http-retry defaults to 0, so a single dropped read from
download.visualstudio.microsoft.com fails the whole layer build
(seen in run 29997282267 on main):

    Error: failed to retrieve https://download.visualstudio.microsoft.com/.../78fa3c824c2c48bd4a49ab5969adaaf7.cab
    after 1 tries due to I/O failures reading the response body
@robobun robobun changed the title Dockerfile.windows: serialize xwin splat to avoid multi-arch symlink race Dockerfile.windows: harden the xwin step (serialize splat, add --http-retry) Jul 31, 2026
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Changes

The Windows Docker image now unpacks xwin payloads before running single-threaded splat. Both phases retry HTTP failures five times and retain the existing architecture, CRT, and SDK options.

Windows cross-compilation

Layer / File(s) Summary
Two-phase xwin setup
Dockerfile.windows
The xwin installation extracts x64 and arm64 payloads with five HTTP retries, then runs splat with RAYON_NUM_THREADS=1.

Suggested reviewers: jarred-sumner

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the failures and fixes in detail, but it omits the required Bugzilla link, review line, and changed-file/function summary. Add the Bugzilla bug title and URL, the “Reviewed by NOBODY (OOPS!).” line, and the required Dockerfile.windows change summary.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main Dockerfile.windows changes: serializing splat and adding HTTP retries.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — targeted CI hardening for two documented intermittent failures in the Windows cross-build base image.

What was reviewed:

  • Verified against xwin 0.9.0 source: --http-retry is the correct flag name (clap #[arg(long)] http_retry: u8), and unpack/splat are both valid subcommands.
  • RAYON_NUM_THREADS=1 on the splat pass serializes the racing per-arch symlink creation; the shared --cache-dir /xwin-cache lets splat reuse the parallel unpack's output.
  • Existing post-splat sanity checks (test -f /winsdk/...) and rm -rf /xwin-cache cleanup are preserved.
Extended reasoning...

Overview

This PR modifies a single RUN step in Dockerfile.windows — the layer that uses xwin to download and lay out the MSVC CRT + Windows SDK for cross-compilation. It splits one xwin splat invocation into a two-phase xwin unpack (full parallelism) followed by xwin splat with RAYON_NUM_THREADS=1, and adds --http-retry 5 to both. A comment block documents the rationale. No other files are touched.

Security risks

None. This is CI/build infrastructure for a Docker base image. The xwin binary is still fetched from the same pinned release with the same SHA256 checksum, the CRT/SDK versions are unchanged, and the post-splat test -f assertions still verify the expected output layout. No new network endpoints, no credential handling, no changes to what gets built or shipped.

Level of scrutiny

Low. This is a mechanical CI-reliability fix in a Dockerfile, not runtime code. The two failure modes it addresses are cited with links to the actual failing CI runs and to the exact lines in xwin's source (ctx.rs rayon pool, splat.rs unguarded symlink) that cause the race. I independently verified the flag/subcommand names against the xwin 0.9.0 source: --http-retry (not --http-retries as the error message's prose says) matches the #[arg(long)] http_retry definition, and Unpack is a real subcommand. If anything here were wrong the layer would fail deterministically on every build, not intermittently.

Other factors

The approach is sound: unpack does the network- and CPU-bound work in parallel and warms --cache-dir /xwin-cache; the serial splat then reuses that cache for cheap file moves + symlinks, avoiding the TOCTOU on the sdk/lib/<version> symlink when both arches finish together. The alternative approaches the description rules out (whole-command retry, pre-creating the symlink) are correctly rejected with reasons. The rm -rf /xwin-cache cleanup and all downstream sanity checks are unchanged. No prior reviews or comments on the PR.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Dockerfile.windows`:
- Around line 96-102: Add regression assertions after the xwin splat step and
before removing /xwin-cache, using test -L to verify
/winsdk/sdk/lib/${WIN_SDK_VERSION} and /winsdk/sdk/include/${WIN_SDK_VERSION}
are symbolic links. Keep the existing checks unchanged and make the Docker build
fail if either versioned compatibility link is missing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 282eab4f-8ef2-4dad-813d-55778be0e32a

📥 Commits

Reviewing files that changed from the base of the PR and between 5d9fa3d and b0c7884.

📒 Files selected for processing (1)
  • Dockerfile.windows

Comment thread Dockerfile.windows
Comment on lines +96 to 102
RUN xwin --accept-license --arch x86_64,aarch64 --cache-dir /xwin-cache --http-retry 5 \
--crt-version ${MSVC_CRT_VERSION} --sdk-version ${WIN_SDK_VERSION} \
unpack && \
RAYON_NUM_THREADS=1 \
xwin --accept-license --arch x86_64,aarch64 --cache-dir /xwin-cache --http-retry 5 \
--crt-version ${MSVC_CRT_VERSION} --sdk-version ${WIN_SDK_VERSION} \
splat --preserve-ms-arch-notation --include-debug-libs --output /winsdk && \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add regression checks for the compatibility symlinks.

The checks on Lines 104-106 do not verify the versioned SDK links that caused the race. These checks can pass while /winsdk/sdk/lib/${WIN_SDK_VERSION} or /winsdk/sdk/include/${WIN_SDK_VERSION} is missing. Add test -L assertions before removing /xwin-cache. The pinned xwin 0.9.0 implementation creates these links during per-payload splat work. (raw.githubusercontent.com)

Proposed checks
      splat --preserve-ms-arch-notation --include-debug-libs --output /winsdk && \
+    test -L "/winsdk/sdk/lib/${WIN_SDK_VERSION}" && \
+    test -L "/winsdk/sdk/include/${WIN_SDK_VERSION}" && \
    rm -rf /xwin-cache && \
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RUN xwin --accept-license --arch x86_64,aarch64 --cache-dir /xwin-cache --http-retry 5 \
--crt-version ${MSVC_CRT_VERSION} --sdk-version ${WIN_SDK_VERSION} \
unpack && \
RAYON_NUM_THREADS=1 \
xwin --accept-license --arch x86_64,aarch64 --cache-dir /xwin-cache --http-retry 5 \
--crt-version ${MSVC_CRT_VERSION} --sdk-version ${WIN_SDK_VERSION} \
splat --preserve-ms-arch-notation --include-debug-libs --output /winsdk && \
RUN xwin --accept-license --arch x86_64,aarch64 --cache-dir /xwin-cache --http-retry 5 \
--crt-version ${MSVC_CRT_VERSION} --sdk-version ${WIN_SDK_VERSION} \
unpack && \
RAYON_NUM_THREADS=1 \
xwin --accept-license --arch x86_64,aarch64 --cache-dir /xwin-cache --http-retry 5 \
--crt-version ${MSVC_CRT_VERSION} --sdk-version ${WIN_SDK_VERSION} \
splat --preserve-ms-arch-notation --include-debug-libs --output /winsdk && \
test -L "/winsdk/sdk/lib/${WIN_SDK_VERSION}" && \
test -L "/winsdk/sdk/include/${WIN_SDK_VERSION}" && \
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile.windows` around lines 96 - 102, Add regression assertions after
the xwin splat step and before removing /xwin-cache, using test -L to verify
/winsdk/sdk/lib/${WIN_SDK_VERSION} and /winsdk/sdk/include/${WIN_SDK_VERSION}
are symbolic links. Keep the existing checks unchanged and make the Docker build
fail if either versioned compatibility link is missing.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — targeted CI reliability fix for the Windows cross-compile base image.

What was reviewed:

  • The two-phase unpackRAYON_NUM_THREADS=1 splat split preserves all existing flags (--arch, --cache-dir, versions, --preserve-ms-arch-notation, --include-debug-libs, --output) and the downstream test -f / PE-link sanity checks.
  • --http-retry 5 is a global xwin option applied to both invocations; verified it matches the flag name in the observed error message.
  • Confirmed Dockerfile.windows is not covered by CODEOWNERS and the change touches no runtime code.
Extended reasoning...

Overview

This PR modifies a single RUN step in Dockerfile.windows (the Windows cross-compile base image). It splits the existing xwin ... splat invocation into a parallel xwin unpack pass followed by a serialized (RAYON_NUM_THREADS=1) xwin splat pass, and adds --http-retry 5 to both. A detailed comment block documents the upstream xwin race (two per-arch SdkLibs payloads racing on the same sdk/lib/<version> -> . symlink) and the transient CDN download failure that motivated the retry flag. All other flags, output paths, and the trailing test -f sanity checks are unchanged.

Security risks

None. This is CI/build tooling only — no runtime code, no auth, no network-facing surfaces beyond the existing Microsoft CDN download that was already there. The added --http-retry flag only affects retry count for that download.

Level of scrutiny

Low. This is a mechanical hardening of a Docker build step to address two observed intermittent CI failures (linked in the PR description with concrete run IDs and xwin source references). The output layout is validated by the existing test -f /winsdk/sdk/include/um/windows.h and libcmt.lib checks plus the subsequent "compile + link a PE executable for both arches" smoke test, so a regression in the splat output would fail the image build immediately rather than silently. Dockerfile.windows is not covered by any CODEOWNERS entry.

Other factors

The only outstanding review comment is a CodeRabbit nitpick suggesting extra test -L assertions on the versioned compatibility symlinks. Those symlinks are not consumed by this build (all downstream references use /winsdk/sdk/lib/um/<arch> etc. directly), so the suggestion is optional and does not block. The bug-hunting system found no issues. The fix is well-researched, minimal, and strictly improves reliability over the previous single-invocation form.

@github-actions

Copy link
Copy Markdown

Preview Builds

Commit Release Date
b0c78840 autobuild-preview-pr-377-b0c78840 2026-07-31 07:07:20 UTC

@sosukesuzuki
sosukesuzuki merged commit 45e21dc into main Jul 31, 2026
43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants