4
4
"context"
5
5
"crypto/sha256"
6
6
"fmt"
7
+ "math/rand"
7
8
"net/http"
8
9
"strings"
9
10
"sync"
@@ -17,6 +18,7 @@ import (
17
18
18
19
"github.com/sourcegraph/sourcegraph/internal/httpcli"
19
20
"github.com/sourcegraph/sourcegraph/internal/redispool"
21
+ "github.com/sourcegraph/sourcegraph/lib/errors"
20
22
)
21
23
22
24
var metricWaitingRequestsGauge = promauto .NewGauge (prometheus.GaugeOpts {
@@ -63,7 +65,15 @@ func restrictGitHubDotComConcurrency(logger log.Logger, doer httpcli.Doer, r *ht
63
65
metricFailedLockRequestsGauge .Inc ()
64
66
// Note that we do NOT fail the request here, this lock is considered best
65
67
// effort.
66
- logger .Error ("failed to get mutex for GitHub.com, concurrent requests may occur and rate limits can happen" , log .Error (err ))
68
+ //
69
+ // We log a warning if the error is ErrTaken, since this can happen from time to time.
70
+ // Otherwise we log an error. It means that we didn't get the global lock in the permitted
71
+ // number of tries. Instead of blocking indefinitely, we let the request pass.
72
+ if errors .HasType (err , & redsync.ErrTaken {}) {
73
+ logger .Warn ("could not acquire mutex to talk to GitHub.com in time, trying to make request concurrently" )
74
+ } else {
75
+ logger .Error ("failed to get mutex for GitHub.com, concurrent requests may occur and rate limits can happen" , log .Error (err ))
76
+ }
67
77
} else {
68
78
didGetLock = true
69
79
}
@@ -77,7 +87,11 @@ func restrictGitHubDotComConcurrency(logger log.Logger, doer httpcli.Doer, r *ht
77
87
if didGetLock {
78
88
if _ , err := lock .UnlockContext (context .Background ()); err != nil {
79
89
metricFailedUnlockRequestsGauge .Inc ()
80
- logger .Error ("failed to unlock mutex, GitHub.com requests may be delayed briefly" , log .Error (err ))
90
+ if errors .HasType (err , & redsync.ErrTaken {}) {
91
+ logger .Warn ("failed to unlock mutex, GitHub.com requests may be delayed briefly" , log .Error (err ))
92
+ } else {
93
+ logger .Error ("failed to unlock mutex, GitHub.com requests may be delayed briefly" , log .Error (err ))
94
+ }
81
95
}
82
96
}
83
97
@@ -115,6 +129,17 @@ func (m *mockLock) UnlockContext(_ context.Context) (bool, error) {
115
129
return false , nil
116
130
}
117
131
132
+ // With a default number of retries of 32, this will average to 8 seconds.
133
+ const (
134
+ minRetryDelayMilliSec = 200
135
+ maxRetryDelayMilliSec = 300
136
+ )
137
+
138
+ // From https://github.com/go-redsync/redsync/blob/master/redsync.go
139
+ func retryDelay (tries int ) time.Duration {
140
+ return time .Duration (rand .Intn (maxRetryDelayMilliSec - minRetryDelayMilliSec )+ minRetryDelayMilliSec ) * time .Millisecond
141
+ }
142
+
118
143
func lockForToken (logger log.Logger , token string ) lock {
119
144
if testLock != nil {
120
145
return testLock
@@ -134,7 +159,7 @@ func lockForToken(logger log.Logger, token string) lock {
134
159
}
135
160
136
161
locker := redsync .New (redigo .NewPool (pool ))
137
- return locker .NewMutex (fmt .Sprintf ("github-concurrency:%s" , hashedToken ))
162
+ return locker .NewMutex (fmt .Sprintf ("github-concurrency:%s" , hashedToken ), redsync . WithRetryDelayFunc ( retryDelay ) )
138
163
}
139
164
140
165
type inMemoryLock struct { mu * sync.Mutex }
0 commit comments