@@ -184,7 +184,7 @@ type Server struct {
184
184
wg sync.WaitGroup // tracks running background jobs
185
185
186
186
// cloneLimiter limits the number of concurrent
187
- // clones. Use s.acquireCloneLimiter() and instead of using it directly.
187
+ // clones.
188
188
cloneLimiter * limiter.MutableLimiter
189
189
190
190
// rpsLimiter limits the remote code host git operations done per second
@@ -249,14 +249,6 @@ func (s *Server) getRemoteURL(ctx context.Context, name api.RepoName) (*vcs.URL,
249
249
return vcs .ParseURL (remoteURL )
250
250
}
251
251
252
- // acquireCloneLimiter() acquires a cancellable context associated with the
253
- // clone limiter.
254
- func (s * Server ) acquireCloneLimiter (ctx context.Context ) (context.Context , context.CancelFunc , error ) {
255
- pendingClones .Inc ()
256
- defer pendingClones .Dec ()
257
- return s .cloneLimiter .Acquire (ctx )
258
- }
259
-
260
252
func (s * Server ) IsRepoCloneable (ctx context.Context , repo api.RepoName ) (protocol.IsRepoCloneableResponse , error ) {
261
253
// We use an internal actor here as the repo may be private. It is safe since all
262
254
// we return is a bool indicating whether the repo is cloneable or not. Perhaps
@@ -323,7 +315,9 @@ func (s *Server) repoUpdateOrClone(ctx context.Context, repoName api.RepoName) e
323
315
324
316
// Use caller context, if the caller is not interested anymore before we
325
317
// start cloning, we can skip the clone altogether.
326
- _ , cancelCloneLimiter , err := s .acquireCloneLimiter (ctx )
318
+ pendingClones .Inc ()
319
+ _ , cancelCloneLimiter , err := s .cloneLimiter .Acquire (ctx )
320
+ pendingClones .Dec ()
327
321
if err != nil {
328
322
lock .Release ()
329
323
return err
@@ -371,7 +365,7 @@ func (s *Server) repoUpdateOrClone(ctx context.Context, repoName api.RepoName) e
371
365
repoClonedCounter .Inc ()
372
366
logger .Info ("cloned repo" , log .String ("repo" , string (repoName )))
373
367
} else {
374
- if err := s .doRepoUpdate (ctx , repoName ); err != nil {
368
+ if err := s .doRepoUpdate (ctx , repoName , lock ); err != nil {
375
369
// The repo update might have failed due to the repo being corrupt
376
370
s .LogIfCorrupt (ctx , repoName , err )
377
371
@@ -502,7 +496,7 @@ func (s *Server) cloneRepo(ctx context.Context, repo api.RepoName, lock Reposito
502
496
// produced, the ideal solution would be that readCloneProgress stores it in
503
497
// chunks.
504
498
output := & linebasedBufferedWriter {}
505
- eg := readCloneProgress (logger , lock , io .TeeReader (progressReader , output ), repo )
499
+ eg := readFetchProgress (logger , lock , io .TeeReader (progressReader , output ), repo )
506
500
507
501
cloneTimeout := conf .GitLongCommandTimeout ()
508
502
cloneCtx , cancel := context .WithTimeout (ctx , cloneTimeout )
@@ -516,7 +510,7 @@ func (s *Server) cloneRepo(ctx context.Context, repo api.RepoName, lock Reposito
516
510
}
517
511
518
512
// best-effort update the output of the clone
519
- if err := s .db .GitserverRepos ().SetLastOutput (context . Background () , repo , output .String ()); err != nil {
513
+ if err := s .db .GitserverRepos ().SetLastOutput (ctx , repo , output .String ()); err != nil {
520
514
s .logger .Error ("Setting last output in DB" , log .Error (err ))
521
515
}
522
516
@@ -602,19 +596,19 @@ func (w *linebasedBufferedWriter) Bytes() []byte {
602
596
return w .buf
603
597
}
604
598
605
- // readCloneProgress scans the reader and saves the most recent line of output
599
+ // readFetchProgress scans the reader and saves the most recent line of output
606
600
// as the lock status, and optionally writes to a log file if siteConfig.cloneProgressLog
607
601
// is enabled.
608
- func readCloneProgress (logger log.Logger , lock RepositoryLock , pr io.Reader , repo api.RepoName ) * errgroup.Group {
602
+ func readFetchProgress (logger log.Logger , lock RepositoryLock , pr io.Reader , repo api.RepoName ) * errgroup.Group {
609
603
var logFile * os.File
610
604
611
605
if conf .Get ().CloneProgressLog {
612
606
var err error
613
607
logFile , err = os .CreateTemp ("" , "" )
614
608
if err != nil {
615
- logger .Warn ("failed to create temporary clone log file" , log .Error (err ), log .String ("repo" , string (repo )))
609
+ logger .Warn ("failed to create temporary fetch log file" , log .Error (err ), log .String ("repo" , string (repo )))
616
610
} else {
617
- logger .Info ("logging clone output" , log .String ("file" , logFile .Name ()), log .String ("repo" , string (repo )))
611
+ logger .Info ("logging fetch output" , log .String ("file" , logFile .Name ()), log .String ("repo" , string (repo )))
618
612
defer logFile .Close ()
619
613
}
620
614
}
@@ -691,7 +685,7 @@ var (
691
685
692
686
var doBackgroundRepoUpdateMock func (api.RepoName ) error
693
687
694
- func (s * Server ) doRepoUpdate (ctx context.Context , repo api.RepoName ) (err error ) {
688
+ func (s * Server ) doRepoUpdate (ctx context.Context , repo api.RepoName , lock RepositoryLock ) (err error ) {
695
689
logger := s .logger .Scoped ("repoUpdate" ).With (log .String ("repo" , string (repo )))
696
690
697
691
if doBackgroundRepoUpdateMock != nil {
@@ -722,21 +716,35 @@ func (s *Server) doRepoUpdate(ctx context.Context, repo api.RepoName) (err error
722
716
// ensure the background update doesn't hang forever
723
717
fetchCtx , cancelTimeout := context .WithTimeout (ctx , fetchTimeout )
724
718
defer cancelTimeout ()
725
- output , err := syncer .Fetch (fetchCtx , repo , dir )
726
- // best-effort update the output of the fetch
727
- if err := s .db .GitserverRepos ().SetLastOutput (ctx , repo , string (output )); err != nil {
728
- s .logger .Warn ("Setting last output in DB" , log .Error (err ))
719
+
720
+ progressReader , progressWriter := io .Pipe ()
721
+ // We also capture the entire output in memory for the call to SetLastOutput
722
+ // further down.
723
+ // TODO: This might require a lot of memory depending on the amount of logs
724
+ // produced, the ideal solution would be that readCloneProgress stores it in
725
+ // chunks.
726
+ output := & linebasedBufferedWriter {}
727
+ eg := readFetchProgress (logger , lock , io .TeeReader (progressReader , output ), repo )
728
+
729
+ fetchErr := syncer .Fetch (fetchCtx , repo , dir , progressWriter )
730
+ progressWriter .Close ()
731
+
732
+ if err := eg .Wait (); err != nil {
733
+ s .logger .Error ("reading fetch progress" , log .Error (err ))
729
734
}
730
735
731
- if err != nil {
736
+ // best-effort store the output of the fetch
737
+ if err := s .db .GitserverRepos ().SetLastOutput (ctx , repo , output .String ()); err != nil {
738
+ s .logger .Error ("Setting last output in DB" , log .Error (err ))
739
+ }
740
+
741
+ if fetchErr != nil {
732
742
if err := fetchCtx .Err (); err != nil {
733
743
return err
734
744
}
735
- if output != nil {
736
- return errors .Wrapf (err , "failed to fetch repo %q with output %q" , repo , string (output ))
737
- } else {
738
- return errors .Wrapf (err , "failed to fetch repo %q" , repo )
739
- }
745
+ // TODO: Should we really return the entire output here in an error?
746
+ // It could be a super big error string.
747
+ return errors .Wrapf (err , "failed to fetch repo %q with output %q" , repo , output .String ())
740
748
}
741
749
742
750
return postRepoFetchActions (ctx , logger , s .fs , s .db , s .getBackendFunc (dir , repo ), s .hostname , repo , dir , syncer )
0 commit comments