Skip to content

fix: Trim tar record padding to avoid broken-pipe failure on Podman#1684

Merged
HofmeisterAn merged 5 commits intotestcontainers:developfrom
artiomchi:bugfix/fix-tar-padding-pipe-failure-on-podman
Apr 27, 2026
Merged

fix: Trim tar record padding to avoid broken-pipe failure on Podman#1684
HofmeisterAn merged 5 commits intotestcontainers:developfrom
artiomchi:bugfix/fix-tar-padding-pipe-failure-on-podman

Conversation

@artiomchi
Copy link
Copy Markdown
Contributor

@artiomchi artiomchi commented Apr 26, 2026

Problem

When running MongoDb replica-set tests (or any container that uses WithResourceMapping) against a Podman daemon, the container start intermittently fails with:

Docker.DotNet.DockerApiException : Docker API responded with status code='InternalServerError',
response='{"cause":"broken pipe","message":"passing bulk input to subprocess: write |1: broken pipe","response":500}'

The failure is flaky: it's rare on an idle host, but becomes near-certain when other containers using the same image are already running. In the MongoDb test suite specifically, the four replica-set test classes fail while the twelve non-replica-set tests pass, because the replica-set code path is the only one that calls WithResourceMapping (to upload a keyfile init script).

Fix

Set blockFactor: 1 in TarOutputMemoryStream's base constructor call (src/Testcontainers/Containers/TarOutputMemoryStream.cs).

With blockFactor = 1 the record size equals the block size (512 bytes). Each block is flushed to the underlying MemoryStream immediately, and WriteFinalRecord finds no partial record to pad. The emitted archive is exactly:

[header block 512 B][data block(s) padded to 512 B] × N  +  [EOF block 1 512 B][EOF block 2 512 B]

— no trailing padding. After the subprocess reads the second EOF block and exits, the HTTP sender has nothing more to write. The race window is closed.

The on-the-wire format is unchanged in every other respect. The tar specification does not mandate any particular record size for archive consumers; all conformant readers (GNU tar, BSD tar, Go archive/tar, Docker's in-process reader, Podman's reader) accept a block factor of 1 archive. This is a strictly smaller byte stream, not a malformed one, so it is safe on Docker as well.

Impact

  • One file changed, one argument added. No public API change.
  • All WithResourceMapping callers benefit, not just MongoDb — any module that uploads bytes or files into a container on Podman was susceptible to the same race.
  • Docker behaviour is unchanged — the padding was always inert for Docker, which reads the tar in-process.
  • Works on Podman versions without the upstream Buildah fix — which is every shipping Podman release as of this writing.

Related issues

References

Root cause

This is a confirmed race condition in Podman's containers/buildah copier package — see containers/buildah#6573.

Podman's PUT /containers/{id}/archive handler pipes the HTTP request body into a tar subprocess via io.Copy. The subprocess reads the body through tar.Reader; as soon as Next() returns io.EOF (after the two 512-byte end-of-archive zero blocks), the subprocess exits and closes the read end of the pipe. If the HTTP sender is still writing bytes at that moment, it gets EPIPE → the 500 above.

Our archive producer (TarOutputMemoryStream, backed by SharpZipLib's TarOutputStream) uses a block factor of 20 by default, which causes SharpZipLib to pad the finished archive up to the next 10 240-byte record boundary after the two EOF zero blocks. For a small file (like the ~100-byte keyfile init script) that produces roughly 8 KB of trailing zero padding that serves no purpose but gives Podman's race condition something to EPIPE on.

The race is timing-dependent: on a busy host the write is slower relative to the read, so the subprocess wins the race more often and the error appears. The upstream Buildah fix (containers/buildah#6678, merged 2026-02-11) drains the full body before exiting the subprocess, but as of this writing it is not yet included in any tagged Buildah or Podman release.

The same failure has been observed in the testcontainers-java ecosystem: testcontainers/testcontainers-java#6640 (closed as "not planned"; workaround was test-level retry).

Summary by CodeRabbit

  • Bug Fixes

    • Reduced extra padding in generated tar archives to avoid broken-pipe errors and improve compatibility with container runtimes (Podman/Docker).
  • Chores

    • Updated solution to reference renamed Temporal-related projects.

@artiomchi artiomchi requested a review from HofmeisterAn as a code owner April 26, 2026 11:00
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 26, 2026

Deploy Preview for testcontainers-dotnet ready!

Name Link
🔨 Latest commit a798cbc
🔍 Latest deploy log https://app.netlify.com/projects/testcontainers-dotnet/deploys/69ef111a20cc3f00087bdc6f
😎 Deploy Preview https://deploy-preview-1684--testcontainers-dotnet.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ac1fa392-30d8-4975-b7c2-971377263934

📥 Commits

Reviewing files that changed from the base of the PR and between 8ae4c9c and a798cbc.

📒 Files selected for processing (3)
  • src/Testcontainers/Containers/TarOutputMemoryStream.cs
  • src/Testcontainers/Images/DockerfileArchive.cs
  • src/Testcontainers/TarArchiveDefaults.cs
✅ Files skipped from review due to trivial changes (2)
  • src/Testcontainers/Images/DockerfileArchive.cs
  • src/Testcontainers/Containers/TarOutputMemoryStream.cs

Walkthrough

Replaced Temporalio solution project entries with Temporal equivalents. Added internal TarArchiveDefaults.TarBlockFactor = 1 and updated tar writers to initialize TarOutputStream with an explicit record size (block factor × 512 bytes), changing tar padding/EOF behavior.

Changes

Cohort / File(s) Summary
Solution file
Testcontainers.slnx
Replaced Testcontainers.Temporalio / Testcontainers.Temporalio.Tests entries with Testcontainers.Temporal / Testcontainers.Temporal.Tests.
Tar defaults
src/Testcontainers/TarArchiveDefaults.cs
Added internal static class TarArchiveDefaults with internal const int TarBlockFactor = 1 and explanatory comment about EOF/padding.
Tar stream updates
src/Testcontainers/Containers/TarOutputMemoryStream.cs, src/Testcontainers/Images/DockerfileArchive.cs
Initialize SharpZipLib.Tar.TarOutputStream with explicit record size derived from TarArchiveDefaults.TarBlockFactor (affects tar padding/EOF). Minor using-directive adjustment in DockerfileArchive.cs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

chore

Poem

🐰 I hopped through lines to tidy the tree,

I nudged the tar blocks down to just one, you see,
EOFs now whisper, no extra zeros to spare,
A jaunty little fix with a carrot to share. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: fixing tar record padding to prevent broken-pipe failures on Podman, which aligns with the core problem and solution in the changeset.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering the problem, root cause, fix, impact, and references. It exceeds template requirements with detailed technical context and justification.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Testcontainers/Containers/TarOutputMemoryStream.cs`:
- Line 45: Replace the platform-dependent Encoding.Default used in the
TarOutputMemoryStream constructor base call with Encoding.UTF8 to ensure tar
entry names are encoded as UTF-8; locate the constructor in class
TarOutputMemoryStream and change the base(...) invocation that currently passes
Encoding.Default to pass Encoding.UTF8 instead so filenames remain consistent
and non-ASCII-safe across platforms.

In `@Testcontainers.slnx`:
- Line 69: This change includes an out-of-scope project rename: the solution
entry for Testcontainers.Temporalio (and its test project) was renamed to
Testcontainers.Temporal; either revert the rename in this branch so the bugfix
PR only contains the TarOutputMemoryStream.cs fix, or split the rename into a
separate commit/PR and update the PR description to document the rename; look
for the solution entries referencing "Testcontainers.Temporalio" and
"Testcontainers.Temporal" (and their corresponding test project names) and
either restore the original names in this PR or move those edits into a distinct
refactor commit/PR and note it in the PR description.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: db28a0d0-dcdf-46f4-856f-06e4efdbc59b

📥 Commits

Reviewing files that changed from the base of the PR and between fd87bb8 and b7674d4.

📒 Files selected for processing (2)
  • Testcontainers.slnx
  • src/Testcontainers/Containers/TarOutputMemoryStream.cs

Comment thread src/Testcontainers/Containers/TarOutputMemoryStream.cs Outdated
Comment thread Testcontainers.slnx
@artiomchi
Copy link
Copy Markdown
Contributor Author

By the way, I've fixed the broken reference to the Temporal projects in the .slnx file, since they were broken. Is there any reason you're still keeping the old .sln file? I assumed you'd remove it by now replacing it with the new solution file

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/Testcontainers/Images/DockerfileArchive.cs (1)

195-197: LGTM — consistent record sizing with TarOutputMemoryStream.

Aligning the Dockerfile archive to a 512 B record size is the right move: the produced archive is also streamed to the build endpoint, so it benefits from the same Podman broken-pipe mitigation.

Optional nit: this is the only place DockerfileArchive (Images) reaches into TarOutputMemoryStream (Containers), creating a small cross-module coupling just to share the literal 1. If you’d like to keep namespaces independent, a private const local to this file (or a shared internal helper) would work equally well. Non-blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Testcontainers/Images/DockerfileArchive.cs` around lines 195 - 197, The
code in DockerfileArchive is referencing TarOutputMemoryStream.TarBlockFactor
from another module to set the TarOutputStream block factor; to avoid
cross-module coupling, introduce a private const int (e.g., TarBlockFactor = 1)
inside the DockerfileArchive class/file and use that constant when constructing
TarOutputStream instead of TarOutputMemoryStream.TarBlockFactor; update the
TarOutputStream(...) call site in DockerfileArchive (where
TarOutputMemoryStream.TarBlockFactor is currently used) to use the new private
const.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/Testcontainers/Images/DockerfileArchive.cs`:
- Around line 195-197: The code in DockerfileArchive is referencing
TarOutputMemoryStream.TarBlockFactor from another module to set the
TarOutputStream block factor; to avoid cross-module coupling, introduce a
private const int (e.g., TarBlockFactor = 1) inside the DockerfileArchive
class/file and use that constant when constructing TarOutputStream instead of
TarOutputMemoryStream.TarBlockFactor; update the TarOutputStream(...) call site
in DockerfileArchive (where TarOutputMemoryStream.TarBlockFactor is currently
used) to use the new private const.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1ae0883f-0108-4999-b850-b87fc91a171d

📥 Commits

Reviewing files that changed from the base of the PR and between b7674d4 and 204853c.

📒 Files selected for processing (2)
  • src/Testcontainers/Containers/TarOutputMemoryStream.cs
  • src/Testcontainers/Images/DockerfileArchive.cs

@HofmeisterAn HofmeisterAn added the bug Something isn't working label Apr 26, 2026
Copy link
Copy Markdown
Collaborator

@HofmeisterAn HofmeisterAn left a comment

Choose a reason for hiding this comment

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

Thanks

@HofmeisterAn HofmeisterAn merged commit e72bacc into testcontainers:develop Apr 27, 2026
148 of 152 checks passed
@artiomchi artiomchi deleted the bugfix/fix-tar-padding-pipe-failure-on-podman branch April 27, 2026 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: WithResourceMapping intermittently fails on Podman with HTTP 500 "broken pipe"

2 participants