Skip to content

fixes write close#96

Open
abhinavcode wants to merge 1 commit into
mainfrom
ah_1273
Open

fixes write close#96
abhinavcode wants to merge 1 commit into
mainfrom
ah_1273

Conversation

@abhinavcode

@abhinavcode abhinavcode commented Feb 24, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

Bug Fixes

  • Improved upload cancellation handling to prevent resource cleanup conflicts during operation termination.

@greptile-apps greptile-apps 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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai

coderabbitai Bot commented Feb 24, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The changes modify blob upload cleanup to prevent double-closing of the upload writer. The CancelBlobUpload function now delegates writer closure to the Cancel() method, while the local upload handler uses a guarded defer that conditionally closes the upload only if cancellation didn't occur.

Changes

Cohort / File(s) Summary
Upload Lifecycle Management
registry/app/pkg/docker/controller.go, registry/app/pkg/docker/local.go
Modified cleanup sequencing to prevent double-close of blob upload writer. CancelBlobUpload no longer explicitly closes the writer (relying on Cancel() to do so internally), and local.go introduces an uploadCancelled flag with guarded defer to avoid closing an already-closed upload on error paths.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A writer that closes itself with care,
No double-close bugs will ensnare,
When Cancel rings true, cleanup flows right,
With guarded defers shining bright,
Safe uploads dance without despair! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'fixes write close' is vague and generic. While it refers to a real aspect of the changes (handling of close operations on write/upload objects), it lacks specificity about what is being fixed or why, making it unclear to someone scanning the history. Consider a more descriptive title like 'Fix double-close of upload writer' or 'Prevent duplicate close calls on blob upload' to clearly convey the specific issue being resolved.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ah_1273

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.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
registry/app/pkg/docker/controller.go (1)

431-435: ⚠️ Potential issue | 🟠 Major

The resource leak concern is valid — blobWriter.Cancel() does not call Close() when fileWriter.Cancel() fails.

The comment claiming "Cancel() internally closes the writer" is incorrect for error paths. Both blobWriter.Cancel() and globalBlobWriter.Cancel() implementations return early if fileWriter.Cancel() returns an error, skipping the subsequent Close() call entirely. By removing the defer blobCtx.Upload.Close(), there is no recovery path if Cancel() fails, leaving the writer open.

blobwriter.go (lines 122–132)
func (bw *blobWriter) Cancel(ctx context.Context) error {
	dcontext.GetLogger(ctx, log.Debug()).Msg("(*blobWriter).Cancel")
	if err := bw.fileWriter.Cancel(ctx); err != nil {
		return err  // Early return, bw.Close() never called
	}

	if err := bw.Close(); err != nil {
		dcontext.GetLogger(ctx, log.Error()).Msgf("error closing blobwriter: %s", err)
	}

	return bw.removeResources(ctx)
}

Either restore the defer Close() safety net or refactor Cancel() to unconditionally call Close() on all code paths.

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

In `@registry/app/pkg/docker/controller.go` around lines 431 - 435, The comment
shows Cancel() implementations (blobWriter.Cancel and globalBlobWriter.Cancel)
return early when fileWriter.Cancel() errors, skipping Close(); restore a safety
net by reintroducing a deferred Close() on the upload writer (e.g., call defer
blobCtx.Upload.Close() immediately after obtaining blobCtx.Upload) or change
blobWriter.Cancel/globalBlobWriter.Cancel so they always call bw.Close() even if
fileWriter.Cancel() fails (ensure Close() is invoked in all error paths and log
any Close() error); reference blobCtx.Upload.Cancel/blobCtx.Upload.Close and
blobWriter.Cancel/globalBlobWriter.Cancel/fileWriter.Cancel when making the
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@registry/app/pkg/docker/local.go`:
- Around line 1097-1103: The defer that closes ctx.Upload is being suppressed by
setting uploadCancelled = true before calling Cancel(), which leaks the writer
if blobWriter.Cancel() fails; in the code paths around the Upload/Cancel logic
(look for the uploadCancelled variable, the deferred ctx.Upload.Close(), and the
BlobWriter.Cancel()/blobWriter.Cancel() call sites) move the assignment
uploadCancelled = true so it is only set after Cancel() returns nil, and if
Cancel() returns an error ensure you do not set uploadCancelled (let defer run)
and propagate/log the error as appropriate so Close() still executes on failure.

---

Outside diff comments:
In `@registry/app/pkg/docker/controller.go`:
- Around line 431-435: The comment shows Cancel() implementations
(blobWriter.Cancel and globalBlobWriter.Cancel) return early when
fileWriter.Cancel() errors, skipping Close(); restore a safety net by
reintroducing a deferred Close() on the upload writer (e.g., call defer
blobCtx.Upload.Close() immediately after obtaining blobCtx.Upload) or change
blobWriter.Cancel/globalBlobWriter.Cancel so they always call bw.Close() even if
fileWriter.Cancel() fails (ensure Close() is invoked in all error paths and log
any Close() error); reference blobCtx.Upload.Cancel/blobCtx.Upload.Close and
blobWriter.Cancel/globalBlobWriter.Cancel/fileWriter.Cancel when making the
change.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9680578 and 797700c.

📒 Files selected for processing (2)
  • registry/app/pkg/docker/controller.go
  • registry/app/pkg/docker/local.go

Comment on lines +1097 to +1103
// Track if upload was cancelled to prevent double-close in defer
uploadCancelled := false
defer func() {
if !uploadCancelled {
ctx.Upload.Close()
}
}()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the code at lines 1097-1103 and around line 1174-1178
cd registry && cat -n app/pkg/docker/local.go | sed -n '1090,1180p'

Repository: abhinavcode/harness

Length of output: 3068


🏁 Script executed:

# Search for Cancel() calls and the context around line 1174
rg -n "uploadCancelled\s*=" app/pkg/docker/local.go -B 5 -A 5

Repository: abhinavcode/harness

Length of output: 126


🏁 Script executed:

# Look for the Cancel() method signature and any related code patterns
rg -n "Cancel\(" app/pkg/docker/local.go -B 3 -A 3

Repository: abhinavcode/harness

Length of output: 126


🏁 Script executed:

# Find the type definition of ctx.Upload (likely BlobWriter)
cd registry && rg -n "type.*BlobWriter" app/pkg/docker/local.go -B 2 -A 10

Repository: abhinavcode/harness

Length of output: 45


🏁 Script executed:

# Search for ctx.Upload assignment or type hints
cd registry && rg -n "ctx\.Upload\s*=" app/pkg/docker/local.go | head -20

Repository: abhinavcode/harness

Length of output: 176


🏁 Script executed:

# Look for any imports of distribution library
cd registry && head -50 app/pkg/docker/local.go | grep -n "import\|distribution"

Repository: abhinavcode/harness

Length of output: 74


🏁 Script executed:

# Read the imports section
cd registry && head -60 app/pkg/docker/local.go | cat -n

Repository: abhinavcode/harness

Length of output: 2557


🏁 Script executed:

# Look at line 1448 and context to understand ctx.Upload assignment
cd registry && sed -n '1440,1460p' app/pkg/docker/local.go

Repository: abhinavcode/harness

Length of output: 442


🏁 Script executed:

# Search for BlobWriter or Upload interface definition in the codebase
cd registry && rg -n "BlobWriter\|type Upload" --type go -A 5 | head -50

Repository: abhinavcode/harness

Length of output: 45


🏁 Script executed:

# Search for BlobWriter in storage package
cd registry && find . -type f -name "*.go" -path "*/storage/*" -exec grep -l "BlobWriter\|Upload" {} \; | head -20

Repository: abhinavcode/harness

Length of output: 355


🏁 Script executed:

# Look for the actual type/interface used
cd registry && rg -n "type.*\bUpload\b" --type go -A 3 | head -50

Repository: abhinavcode/harness

Length of output: 45


🏁 Script executed:

# Search for Cancel method definition
cd registry && rg -n "func.*Cancel\(" --type go -B 2 -A 10 | head -100

Repository: abhinavcode/harness

Length of output: 3981


🏁 Script executed:

# Read complete blobwriter.go Cancel method
cd registry && sed -n '120,135p' app/storage/blobwriter.go

Repository: abhinavcode/harness

Length of output: 565


🏁 Script executed:

# Read complete globalblobwriter.go Cancel method
cd registry && sed -n '173,195p' app/storage/globalblobwriter.go

Repository: abhinavcode/harness

Length of output: 783


🏁 Script executed:

# Check the fileWriter type to understand Cancel contract
cd registry && rg -n "fileWriter.*interface\|fileWriter\s*\w+\s*interface" --type go -B 2 -A 15 app/storage/blobwriter.go

Repository: abhinavcode/harness

Length of output: 45


🏁 Script executed:

# Check what type ctx.Upload actually is by looking at the function signature
cd registry && sed -n '1000,1100p' app/storage/blobwriter.go | head -50

Repository: abhinavcode/harness

Length of output: 45


🏁 Script executed:

# Check for interface definition of BlobWriter/Upload
cd registry && rg -n "type.*interface.*\{" app/storage/blobwriter.go -A 20 | head -50

Repository: abhinavcode/harness

Length of output: 45


🏁 Script executed:

# Confirm which implementation is typically used
cd registry && rg -n "globalBlobWriter\|blobWriter" app/pkg/docker/local.go | head -10

Repository: abhinavcode/harness

Length of output: 45


🏁 Script executed:

# Check the blobwriter.go file structure and Close method
cd registry && cat -n app/storage/blobwriter.go | head -150

Repository: abhinavcode/harness

Length of output: 5296


🏁 Script executed:

# Look for the interface definition of BlobWriter
cd registry && rg -n "interface\s*\{" app/storage/ --type go -B 5 | grep -A 15 "Cancel\|Close"

Repository: abhinavcode/harness

Length of output: 952


🏁 Script executed:

# Check what types implement the blob writer interface
cd registry && rg -n "ctx\.Upload\." app/pkg/docker/local.go | head -20

Repository: abhinavcode/harness

Length of output: 235


🏁 Script executed:

# Check the BlobWriter interface definition to confirm Cancel contract
cd registry && sed -n '100,120p' app/storage/blobs.go

Repository: abhinavcode/harness

Length of output: 1044


uploadCancelled = true set before Cancel() creates resource leak if Cancel() fails.

The BlobWriter.Cancel() interface contract states: "frees any associated resources". However, the blobWriter.Cancel() implementation (line 124-125) returns immediately on error without calling Close(), leaving the writer potentially unclosed and leaked.

Setting uploadCancelled = true before calling Cancel() (line 1174) suppresses the deferred Close() even if Cancel() fails and doesn't close the writer internally.

Move uploadCancelled = true to only after a successful Cancel():

Fix: only suppress defer on successful cancel
-       uploadCancelled = true // Mark as cancelled to prevent defer Close() from running
        if err := ctx.Upload.Cancel(ctx); err != nil {
            // If the cleanup fails, all we can do is observe and report.
            log.Ctx(ctx).Error().Msgf("error canceling upload after error: %v", err)
+       } else {
+           uploadCancelled = true // Cancel() closed the writer; suppress defer Close()
        }

Also applies to: 1174-1178

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

In `@registry/app/pkg/docker/local.go` around lines 1097 - 1103, The defer that
closes ctx.Upload is being suppressed by setting uploadCancelled = true before
calling Cancel(), which leaks the writer if blobWriter.Cancel() fails; in the
code paths around the Upload/Cancel logic (look for the uploadCancelled
variable, the deferred ctx.Upload.Close(), and the
BlobWriter.Cancel()/blobWriter.Cancel() call sites) move the assignment
uploadCancelled = true so it is only set after Cancel() returns nil, and if
Cancel() returns an error ensure you do not set uploadCancelled (let defer run)
and propagate/log the error as appropriate so Close() still executes on failure.

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.

1 participant