Skip to content

Commit 82754f0

Browse files
rremerRoyce Remer
authored and
Royce Remer
committed
Make LFS http_client parallel within a batch.
Signed-off-by: Royce Remer <[email protected]>
1 parent feca880 commit 82754f0

File tree

3 files changed

+72
-66
lines changed

3 files changed

+72
-66
lines changed

modules/lfs/http_client.go

Lines changed: 67 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616
"code.gitea.io/gitea/modules/json"
1717
"code.gitea.io/gitea/modules/log"
1818
"code.gitea.io/gitea/modules/proxy"
19+
20+
"golang.org/x/sync/errgroup"
1921
)
2022

2123
const httpBatchSize = 20
@@ -114,6 +116,7 @@ func (c *HTTPClient) Upload(ctx context.Context, objects []Pointer, callback Upl
114116
return c.performOperation(ctx, objects, nil, callback)
115117
}
116118

119+
// performOperation takes a slice of LFS object pointers, batches them, and performs the upload/download operations concurrently in each batch
117120
func (c *HTTPClient) performOperation(ctx context.Context, objects []Pointer, dc DownloadCallback, uc UploadCallback) error {
118121
if len(objects) == 0 {
119122
return nil
@@ -134,71 +137,84 @@ func (c *HTTPClient) performOperation(ctx context.Context, objects []Pointer, dc
134137
return fmt.Errorf("TransferAdapter not found: %s", result.Transfer)
135138
}
136139

140+
errGroup, groupCtx := errgroup.WithContext(context.Background())
137141
for _, object := range result.Objects {
138-
if object.Error != nil {
139-
log.Trace("Error on object %v: %v", object.Pointer, object.Error)
140-
if uc != nil {
141-
if _, err := uc(object.Pointer, object.Error); err != nil {
142-
return err
143-
}
144-
} else {
145-
if err := dc(object.Pointer, nil, object.Error); err != nil {
146-
return err
147-
}
148-
}
149-
continue
150-
}
142+
func(groupCtx context.Context, object *ObjectResponse, dc DownloadCallback, uc UploadCallback, transferAdapter TransferAdapter) {
143+
errGroup.Go(func() error {
144+
err := performSingleOperation(groupCtx, object, dc, uc, transferAdapter)
145+
return err
146+
})
147+
}(groupCtx, object, dc, uc, transferAdapter)
148+
}
151149

150+
// only the first error is returned, preserving legacy behavior before concurrency
151+
return errGroup.Wait()
152+
}
153+
154+
// performSingleOperation performs an LFS upload or download operation on a single object
155+
func performSingleOperation(ctx context.Context, object *ObjectResponse, dc DownloadCallback, uc UploadCallback, transferAdapter TransferAdapter) error {
156+
if object.Error != nil {
157+
log.Trace("Error on object %v: %v", object.Pointer, object.Error)
152158
if uc != nil {
153-
if len(object.Actions) == 0 {
154-
log.Trace("%v already present on server", object.Pointer)
155-
continue
159+
if _, err := uc(object.Pointer, object.Error); err != nil {
160+
return err
156161
}
162+
}
163+
err := dc(object.Pointer, nil, object.Error)
164+
if errors.Is(object.Error, ErrObjectNotExist) {
165+
log.Warn("Ignoring missing upstream LFS object %-v: %v", object.Pointer, err)
166+
return nil
167+
}
168+
return err
169+
}
157170

158-
link, ok := object.Actions["upload"]
159-
if !ok {
160-
log.Debug("%+v", object)
161-
return errors.New("missing action 'upload'")
162-
}
171+
if uc != nil {
172+
if len(object.Actions) == 0 {
173+
log.Trace("%v already present on server", object.Pointer)
174+
return nil
175+
}
163176

164-
content, err := uc(object.Pointer, nil)
165-
if err != nil {
166-
return err
167-
}
177+
link, ok := object.Actions["upload"]
178+
if !ok {
179+
return errors.New("missing action 'upload'")
180+
}
168181

169-
err = transferAdapter.Upload(ctx, link, object.Pointer, content)
170-
if err != nil {
171-
return err
172-
}
182+
content, err := uc(object.Pointer, nil)
183+
if err != nil {
184+
return err
185+
}
173186

174-
link, ok = object.Actions["verify"]
175-
if ok {
176-
if err := transferAdapter.Verify(ctx, link, object.Pointer); err != nil {
177-
return err
178-
}
179-
}
180-
} else {
181-
link, ok := object.Actions["download"]
182-
if !ok {
183-
// no actions block in response, try legacy response schema
184-
link, ok = object.Links["download"]
185-
}
186-
if !ok {
187-
log.Debug("%+v", object)
188-
return errors.New("missing action 'download'")
189-
}
187+
err = transferAdapter.Upload(ctx, link, object.Pointer, content)
188+
if err != nil {
189+
return err
190+
}
190191

191-
content, err := transferAdapter.Download(ctx, link)
192-
if err != nil {
192+
link, ok = object.Actions["verify"]
193+
if ok {
194+
if err := transferAdapter.Verify(ctx, link, object.Pointer); err != nil {
193195
return err
194196
}
197+
}
198+
} else {
199+
link, ok := object.Actions["download"]
200+
if !ok {
201+
// no actions block in response, try legacy response schema
202+
link, ok = object.Links["download"]
203+
}
204+
if !ok {
205+
log.Debug("%+v", object)
206+
return errors.New("missing action 'download'")
207+
}
195208

196-
if err := dc(object.Pointer, content, nil); err != nil {
197-
return err
198-
}
209+
content, err := transferAdapter.Download(ctx, link)
210+
if err != nil {
211+
return err
199212
}
200-
}
201213

214+
if err := dc(object.Pointer, content, nil); err != nil {
215+
return err
216+
}
217+
}
202218
return nil
203219
}
204220

modules/lfs/http_client_test.go

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -211,36 +211,31 @@ func TestHTTPClientDownload(t *testing.T) {
211211
expectederror: "TransferAdapter not found: ",
212212
},
213213
// case 5
214-
{
215-
endpoint: "https://error-in-response-objects.io",
216-
expectederror: "Object not found",
217-
},
218-
// case 6
219214
{
220215
endpoint: "https://empty-actions-map.io",
221216
expectederror: "missing action 'download'",
222217
},
223-
// case 7
218+
// case 6
224219
{
225220
endpoint: "https://download-actions-map.io",
226221
expectederror: "",
227222
},
228-
// case 8
223+
// case 7
229224
{
230225
endpoint: "https://upload-actions-map.io",
231226
expectederror: "missing action 'download'",
232227
},
233-
// case 9
228+
// case 8
234229
{
235230
endpoint: "https://verify-actions-map.io",
236231
expectederror: "missing action 'download'",
237232
},
238-
// case 10
233+
// case 9
239234
{
240235
endpoint: "https://unknown-actions-map.io",
241236
expectederror: "missing action 'download'",
242237
},
243-
// case 11
238+
// case 10
244239
{
245240
endpoint: "https://legacy-batch-request-download.io",
246241
expectederror: "",

modules/repository/repo.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package repository
55

66
import (
77
"context"
8-
"errors"
98
"fmt"
109
"io"
1110
"strings"
@@ -182,10 +181,6 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re
182181
downloadObjects := func(pointers []lfs.Pointer) error {
183182
err := lfsClient.Download(ctx, pointers, func(p lfs.Pointer, content io.ReadCloser, objectError error) error {
184183
if objectError != nil {
185-
if errors.Is(objectError, lfs.ErrObjectNotExist) {
186-
log.Warn("Repo[%-v]: Ignore missing LFS object %-v: %v", repo, p, objectError)
187-
return nil
188-
}
189184
return objectError
190185
}
191186

0 commit comments

Comments
 (0)