Skip to content

getblobs parallelization - #502

Open
mathieu-plak wants to merge 2 commits into
mainfrom
mm/getblobs_parallelization
Open

getblobs parallelization#502
mathieu-plak wants to merge 2 commits into
mainfrom
mm/getblobs_parallelization

Conversation

@mathieu-plak

Copy link
Copy Markdown
Contributor

This PR adds support for GetBlobs parallelization (chosen by the caller) of the reads.

Copilot AI review requested due to automatic review settings July 24, 2026 10:14

Copilot AI 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.

Pull request overview

Adds caller-controlled parallelization to Repository.GetBlobs, allowing packfile range reads and blob decoding to be performed concurrently while attempting to preserve the existing “set semantics” (deduped requests) and error-per-blob delivery model.

Changes:

  • Introduces GetBlobsOpts with a Concurrency setting (defaulting to 1) and threads it into GetBlobs.
  • Refactors GetBlobs into a feeder/worker pipeline using channels and cancellation to avoid goroutine leaks on early iterator exit.
  • Adds a dedicated concurrency test suite covering roundtrip correctness, range read errors, early break/cancellation, and worker/read count mismatches.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
repository/getblobs.go Adds GetBlobsOpts and implements concurrent range-read execution with cancellation-aware shutdown.
repository/getblobs_concurrency_test.go Adds tests validating correctness under concurrency and checking for goroutine leaks on early exit/cancel.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread repository/getblobs.go
Comment thread repository/getblobs.go
Comment on lines +125 to +149
returnChannel := make(chan iterItem, opts.Concurrency)
var wg sync.WaitGroup
for range opts.Concurrency {
wg.Go(func() {
for rp := range readsChannel {
data, err := r.GetPackfileRange(rp.loc)

for _, b := range rp.blobs {
it := iterItem{b: BlobResp{b.BlobReq, nil}}
if err != nil {
it.err = err
} else {
blobStart := b.Offset - rp.loc.Offset
blobBytes := data[blobStart : blobStart+uint64(b.Length)]
it.b.Data, it.err = r.decodeBuffer(blobBytes)
}

select {
case returnChannel <- it:
case <-innerCtx.Done():
return
}
}
}
})

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You might want to refresh your memories on the WaitGroup API :).

* This adds a new know that let the caller specify how much concurrency
  they want for the actual Gets.
@mathieu-plak
mathieu-plak force-pushed the mm/getblobs_parallelization branch from 25b049d to 7fad0c1 Compare July 24, 2026 10:25
Copilot AI review requested due to automatic review settings July 24, 2026 10:25

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread repository/getblobs.go
Comment on lines +56 to +62
if opts == nil {
opts = makeDefaultGetBlobsOpts()
}

if opts.Concurrency == 0 {
opts.Concurrency = 1
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's a pointer.... caller should think it'll be mutated. Thoughts from humans?

Comment thread repository/getblobs.go
Comment on lines +99 to +107
// Derived context to handle cancellation from the iterator loop,
// otherwise an early exit will leak all goroutines.
innerCtx, cancel := context.WithCancel(ctx)
defer cancel()

for _, b := range g.blobs {
if err != nil {
if !yield(BlobResp{b.BlobReq, nil}, err) {
return
}
// Feed the read plans to the workers.
readsChannel := make(chan *rangeRead)
go func() {
defer close(readsChannel)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is all dominated by i/o so no I don't think it's worth the added complexity... Thoughts from humans?

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