Skip to content

fix: acquire read lock for r.blobs in test registry POST handler#678

Merged
ilopezluna merged 1 commit intomainfrom
fix-race
Feb 16, 2026
Merged

fix: acquire read lock for r.blobs in test registry POST handler#678
ilopezluna merged 1 commit intomainfrom
fix-race

Conversation

@ilopezluna
Copy link
Contributor

Summary

  • Fixes a pre-existing data race in the test registry detected by go test -race on PR chore(deps): bump the go-modules-root group with 2 updates #675
  • The len(r.blobs) read in the POST handler was unprotected while concurrent PUT/PATCH handlers write to the map under r.mu.Lock()
  • Wraps the read with r.mu.RLock()/r.mu.RUnlock() to match every other access in the file

Test plan

  • go test -race ./pkg/inference/models/ -run TestHandleGetModel -count=5 — passes with no race
  • go test -race ./pkg/distribution/registry/... ./pkg/inference/models/... — passes
  • go test -race ./... — full suite passes

🤖 Generated with Claude Code

The race detector flagged an unprotected read of r.blobs via len() in
the POST handler while concurrent PUT/PATCH handlers write to the map
under r.mu.Lock(). Wrap with RLock/RUnlock to match every other access.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ilopezluna, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves a data race within the test registry's blob upload POST handler. By introducing a read lock around a critical read operation, it ensures that the r.blobs map is accessed safely in concurrent environments, thereby enhancing the stability and correctness of the test registry.

Highlights

  • Data Race Fix: Addressed a pre-existing data race in the test registry's handleBlobUpload POST handler, specifically when reading len(r.blobs).
  • Concurrency Safety: Implemented r.mu.RLock() and r.mu.RUnlock() around the len(r.blobs) read to ensure thread safety and prevent concurrent writes from other handlers.
  • Code Consistency: Aligned the access pattern for r.blobs in the POST handler with other parts of the file that already use r.mu.Lock() for writes.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • pkg/distribution/registry/testregistry/registry.go
    • Added r.mu.RLock() and r.mu.RUnlock() around the len(r.blobs) call in the handleBlobUpload method for http.MethodPost.
Activity
  • Identified a pre-existing data race using go test -race on a previous PR.
  • Implemented a fix to acquire a read lock for r.blobs in the test registry POST handler.
  • Verified the fix with go test -race for specific packages and the full test suite, confirming no races were detected.
  • The changes were generated with Claude Code.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@ilopezluna ilopezluna requested a review from a team February 16, 2026 08:03
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • Using RLock around len(r.blobs) still allows multiple concurrent POSTs to compute the same uploadID (since writers are blocked but other readers are not); consider using mu.Lock or a separate counter/UUID to guarantee uniqueness instead of a read-locked map length.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Using `RLock` around `len(r.blobs)` still allows multiple concurrent POSTs to compute the same `uploadID` (since writers are blocked but other readers are not); consider using `mu.Lock` or a separate counter/UUID to guarantee uniqueness instead of a read-locked map length.

## Individual Comments

### Comment 1
<location> `pkg/distribution/registry/testregistry/registry.go:72-74` </location>
<code_context>
 	switch req.Method {
 	case http.MethodPost:
 		// Start upload
+		r.mu.RLock()
 		uploadID := fmt.Sprintf("upload-%d", len(r.blobs))
+		r.mu.RUnlock()
 		location := fmt.Sprintf("/v2/%s/blobs/uploads/%s", repo, uploadID)
 		w.Header().Set("Location", location)
</code_context>

<issue_to_address>
**issue (bug_risk):** Using RLock here still allows concurrent readers to generate duplicate upload IDs based on len(r.blobs).

Because `RLock` allows concurrent readers, multiple goroutines can observe the same `len(r.blobs)` and generate identical `uploadID`s. To ensure uniqueness, this section should either use the exclusive mutex (`mu.Lock`/`mu.Unlock`) around ID generation, or switch to a different mechanism (e.g., atomic counter or UUID). As is, the map access is safe but the ID generation still has a race condition.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +72 to +74
r.mu.RLock()
uploadID := fmt.Sprintf("upload-%d", len(r.blobs))
r.mu.RUnlock()
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Using RLock here still allows concurrent readers to generate duplicate upload IDs based on len(r.blobs).

Because RLock allows concurrent readers, multiple goroutines can observe the same len(r.blobs) and generate identical uploadIDs. To ensure uniqueness, this section should either use the exclusive mutex (mu.Lock/mu.Unlock) around ID generation, or switch to a different mechanism (e.g., atomic counter or UUID). As is, the map access is safe but the ID generation still has a race condition.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

The pull request successfully addresses a data race in the testregistry's handleBlobUpload POST handler by introducing a read lock (r.mu.RLock()/r.mu.RUnlock()) around the len(r.blobs) operation. This ensures safe concurrent access to the blobs map.

Comment on lines +72 to +74
r.mu.RLock()
uploadID := fmt.Sprintf("upload-%d", len(r.blobs))
r.mu.RUnlock()
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

While the RLock correctly prevents a data race when reading len(r.blobs), using len(r.blobs) to generate the uploadID can still lead to non-unique identifiers in a concurrent environment. Multiple concurrent requests could read the same len(r.blobs) value, resulting in identical uploadIDs. This could cause issues if clients expect unique Docker-Upload-UUIDs or if the Location header is used for subsequent operations that rely on a unique identifier. Consider using a universally unique identifier (UUID) generator (e.g., github.com/google/uuid) or an atomic counter to ensure that uploadIDs are unique across concurrent requests. This would provide a more robust and standard way to generate upload identifiers.

@ilopezluna ilopezluna merged commit 678b30f into main Feb 16, 2026
11 checks passed
@ilopezluna ilopezluna deleted the fix-race branch February 16, 2026 08:26
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