@@ -11,7 +11,6 @@ import (
1111 "os"
1212 "strconv"
1313 "strings"
14- "sync"
1514 "time"
1615
1716 "code.gitea.io/gitea/models/avatars"
@@ -20,7 +19,7 @@ import (
2019 "code.gitea.io/gitea/modules/cache"
2120 "code.gitea.io/gitea/modules/git"
2221 "code.gitea.io/gitea/modules/gitrepo"
23- "code.gitea.io/gitea/modules/graceful "
22+ "code.gitea.io/gitea/modules/globallock "
2423 "code.gitea.io/gitea/modules/log"
2524 api "code.gitea.io/gitea/modules/structs"
2625)
@@ -32,8 +31,7 @@ const (
3231
3332var (
3433 ErrAwaitGeneration = errors .New ("generation took longer than " )
35- awaitGenerationTime = time .Second * 5
36- generateLock = sync.Map {}
34+ awaitGenerationTime = time .Second * 60
3735)
3836
3937type WeekData struct {
@@ -81,41 +79,44 @@ func findLastSundayBeforeDate(dateStr string) (string, error) {
8179func GetContributorStats (ctx context.Context , cache cache.StringCache , repo * repo_model.Repository , revision string ) (map [string ]* ContributorData , error ) {
8280 // as GetContributorStats is resource intensive we cache the result
8381 cacheKey := fmt .Sprintf (contributorStatsCacheKey , repo .FullName (), revision )
84- if ! cache .IsExist (cacheKey ) {
85- genReady := make (chan struct {})
86-
87- // dont start multiple async generations
88- _ , run := generateLock .Load (cacheKey )
89- if run {
90- return nil , ErrAwaitGeneration
82+ if cache .IsExist (cacheKey ) {
83+ // TODO: renew timeout of cache cache.UpdateTimeout(cacheKey, contributorStatsCacheTimeout)
84+ var res map [string ]* ContributorData
85+ if _ , cacheErr := cache .GetJSON (cacheKey , & res ); cacheErr != nil {
86+ return nil , fmt .Errorf ("cached error: %w" , cacheErr .ToError ())
9187 }
88+ return res , nil
89+ }
90+
91+ // dont start multiple async generations
92+ releaser , err := globallock .Lock (ctx , cacheKey )
93+ if err != nil {
94+ return nil , err
95+ }
96+ defer releaser ()
9297
93- generateLock . Store ( cacheKey , struct {}{})
94- // run generation async
95- go generateContributorStats ( genReady , cache , cacheKey , repo , revision )
98+ // set a timeout for the generation
99+ ctx , cancel := context . WithTimeout ( ctx , awaitGenerationTime )
100+ defer cancel ( )
96101
97- select {
98- case <- time .After (awaitGenerationTime ):
99- return nil , ErrAwaitGeneration
100- case <- genReady :
101- // we got generation ready before timeout
102- break
102+ // run generation async
103+ res , err := generateContributorStats (ctx , cache , cacheKey , repo , revision )
104+ if err != nil {
105+ switch {
106+ case errors .Is (err , context .DeadlineExceeded ):
107+ _ = cache .PutJSON (cacheKey , fmt .Errorf ("generateContributorStats: %w" , ErrAwaitGeneration ), contributorStatsCacheTimeout )
108+ default :
109+ _ = cache .PutJSON (cacheKey , fmt .Errorf ("generateContributorStats: %w" , err ), contributorStatsCacheTimeout )
103110 }
111+ return nil , err
104112 }
105- // TODO: renew timeout of cache cache.UpdateTimeout(cacheKey, contributorStatsCacheTimeout)
106- var res map [string ]* ContributorData
107- if _ , cacheErr := cache .GetJSON (cacheKey , & res ); cacheErr != nil {
108- return nil , fmt .Errorf ("cached error: %w" , cacheErr .ToError ())
109- }
113+
114+ _ = cache .PutJSON (cacheKey , res , contributorStatsCacheTimeout )
110115 return res , nil
111116}
112117
113118// getExtendedCommitStats return the list of *ExtendedCommitStats for the given revision
114- func getExtendedCommitStats (repo * git.Repository , revision string /*, limit int */ ) ([]* ExtendedCommitStats , error ) {
115- baseCommit , err := repo .GetCommit (revision )
116- if err != nil {
117- return nil , err
118- }
119+ func getExtendedCommitStats (ctx context.Context , repoPath string , baseCommit * git.Commit , revision string /*, limit int */ ) ([]* ExtendedCommitStats , error ) {
119120 stdoutReader , stdoutWriter , err := os .Pipe ()
120121 if err != nil {
121122 return nil , err
@@ -131,15 +132,21 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int
131132
132133 var extendedCommitStats []* ExtendedCommitStats
133134 stderr := new (strings.Builder )
134- err = gitCmd .Run (repo . Ctx , & git.RunOpts {
135- Dir : repo . Path ,
135+ err = gitCmd .Run (ctx , & git.RunOpts {
136+ Dir : repoPath ,
136137 Stdout : stdoutWriter ,
137138 Stderr : stderr ,
138139 PipelineFunc : func (ctx context.Context , cancel context.CancelFunc ) error {
139140 _ = stdoutWriter .Close ()
140141 scanner := bufio .NewScanner (stdoutReader )
141142
142143 for scanner .Scan () {
144+ select {
145+ case <- ctx .Done ():
146+ return ctx .Err ()
147+ default :
148+ }
149+
143150 line := strings .TrimSpace (scanner .Text ())
144151 if line != "---" {
145152 continue
@@ -199,27 +206,26 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int
199206 return extendedCommitStats , nil
200207}
201208
202- func generateContributorStats (genDone chan struct {}, cache cache.StringCache , cacheKey string , repo * repo_model.Repository , revision string ) {
203- ctx := graceful .GetManager ().HammerContext ()
204-
209+ func generateContributorStats (ctx context.Context , cache cache.StringCache , cacheKey string , repo * repo_model.Repository , revision string ) (map [string ]* ContributorData , error ) {
205210 gitRepo , closer , err := gitrepo .RepositoryFromContextOrOpen (ctx , repo )
206211 if err != nil {
207- _ = cache .PutJSON (cacheKey , fmt .Errorf ("OpenRepository: %w" , err ), contributorStatsCacheTimeout )
208- return
212+ return nil , err
209213 }
210214 defer closer .Close ()
211215
212216 if len (revision ) == 0 {
213217 revision = repo .DefaultBranch
214218 }
215- extendedCommitStats , err := getExtendedCommitStats ( gitRepo , revision )
219+ baseCommit , err := gitRepo . GetCommit ( revision )
216220 if err != nil {
217- _ = cache .PutJSON (cacheKey , fmt .Errorf ("ExtendedCommitStats: %w" , err ), contributorStatsCacheTimeout )
218- return
221+ return nil , err
222+ }
223+ extendedCommitStats , err := getExtendedCommitStats (ctx , repo .RepoPath (), baseCommit , revision )
224+ if err != nil {
225+ return nil , err
219226 }
220227 if len (extendedCommitStats ) == 0 {
221- _ = cache .PutJSON (cacheKey , fmt .Errorf ("no commit stats returned for revision '%s'" , revision ), contributorStatsCacheTimeout )
222- return
228+ return nil , fmt .Errorf ("no commit stats returned for revision '%s'" , revision )
223229 }
224230
225231 layout := time .DateOnly
@@ -300,9 +306,5 @@ func generateContributorStats(genDone chan struct{}, cache cache.StringCache, ca
300306 total .TotalCommits ++
301307 }
302308
303- _ = cache .PutJSON (cacheKey , contributorsCommitStats , contributorStatsCacheTimeout )
304- generateLock .Delete (cacheKey )
305- if genDone != nil {
306- genDone <- struct {}{}
307- }
309+ return contributorsCommitStats , nil
308310}
0 commit comments