Skip to content

fix: avoid deadlock on Read after WriteTo drain#71

Merged
klauspost merged 3 commits into
klauspost:masterfrom
Solaris-star:fix/39-read-after-writeto-deadlock
Jul 22, 2026
Merged

fix: avoid deadlock on Read after WriteTo drain#71
klauspost merged 3 commits into
klauspost:masterfrom
Solaris-star:fix/39-read-after-writeto-deadlock

Conversation

@Solaris-star

@Solaris-star Solaris-star commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Calling io.Copy (which uses WriteTo) then Read on the same pgzip.Reader deadlocked:

rdr, _ := pgzip.NewReader(bytes.NewReader(gzipData))
io.Copy(io.Discard, rdr) // drains via WriteTo
io.ReadAll(rdr)          // hung forever

Root cause: after the final block, Read still did z.blockPool <- z.current with a nil/empty current while the pool was already full.

Fix

  • If lastBlock and no remaining current, Read returns 0, io.EOF immediately
  • Only return buffers with capacity to blockPool
  • WriteTo never returns io.EOF (so io.Copy does not surface it)

Test plan

  • go test .
  • TestReadAfterWriteToNoDeadlock
  • TestWriteToDoesNotReturnEOF

Fixes #39
Fixes #38

Summary by CodeRabbit

  • Bug Fixes
    • Improved gzip reader end-of-stream handling to prevent potential blocking when no more data is buffered.
    • Fixed reset behavior to fully clear multistream-related internal state before re-reading headers.
    • Updated WriteTo end-of-stream behavior so callers don’t receive io.EOF.
  • Tests
    • Added coverage ensuring reads after WriteTo complete do not deadlock and return io.EOF with 0 bytes.
    • Added coverage verifying WriteTo returns the correct decompressed byte count and no io.EOF error.

After WriteTo/io.Copy drained the stream, a subsequent Read blocked
forever trying to return a nil buffer to a full blockPool.

Short-circuit Read when lastBlock is set and current is empty, and only
return buffers with capacity to the pool. Also ensure WriteTo never
returns io.EOF so io.Copy does not surface it (klauspost#38).

Fixes klauspost#39
Fixes klauspost#38
@klauspost

Copy link
Copy Markdown
Owner

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b6f2d2b9-607a-4744-bbab-915e230a7575

📥 Commits

Reviewing files that changed from the base of the PR and between 57d6b29 and 22ce4dd.

📒 Files selected for processing (2)
  • gunzip.go
  • gunzip_test.go
💤 Files with no reviewable changes (1)
  • gunzip_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • gunzip.go

📝 Walkthrough

Walkthrough

Reader stream lifecycle handling now resets terminal state, avoids post-completion blocking, safely manages pooled buffers, and makes WriteTo return successful completion without io.EOF.

Changes

Reader stream lifecycle

Layer / File(s) Summary
Reset and Read state guards
gunzip.go, gunzip_test.go
Reader.Reset clears terminal and buffered state; Reader.Read returns io.EOF for completed streams and only pools non-nil buffers. Post-drain reads are tested for immediate EOF without deadlock.
WriteTo completion semantics
gunzip.go, gunzip_test.go
Reader.WriteTo skips empty output, conditionally returns buffers to the pool, suppresses terminal io.EOF, and is tested for correct byte counts and nil completion errors.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main fix: avoiding deadlock when Read is called after WriteTo drains the reader.
Linked Issues check ✅ Passed The changes address the deadlock and spurious EOF issues after WriteTo completes, with tests covering both behaviors for #38 and #39.
Out of Scope Changes check ✅ Passed The modified reader logic and tests are directly tied to the linked EOF and deadlock fixes, with no unrelated changes visible.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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.

@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 `@gunzip.go`:
- Around line 175-177: Preserve blockPool ownership on both abandonment paths in
gunzip.go: in the reset logic around lines 175-177, return z.current to
blockPool before clearing it; in the write/error handling around lines 570-584,
return read.b to blockPool before every short-write or writer-error return. Use
the existing pool-return mechanism and retain the current control flow
otherwise.
🪄 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: CHILL

Plan: Pro

Run ID: 538afa20-f79f-43d1-b69e-d4608884cc5f

📥 Commits

Reviewing files that changed from the base of the PR and between 89611d0 and 57d6b29.

📒 Files selected for processing (2)
  • gunzip.go
  • gunzip_test.go

Comment thread gunzip.go
@klauspost

Copy link
Copy Markdown
Owner

PTAL at the feedback and the formatting.

@Solaris-star

Copy link
Copy Markdown
Contributor Author

Addressed the actionable feedback in 22ce4dd:

  • return read.b to blockPool before both short-write and writer-error exits
  • ran gofmt (including the extra blank line in gunzip_test.go)

I did not add a second Reset return because killReadAhead already returns current and clears it, avoiding a double return. Validation: go test ./... passes. PTAL.

@klauspost klauspost left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

lgtm

@klauspost
klauspost merged commit 7249ebc into klauspost:master Jul 22, 2026
13 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

2 participants