Skip to content

Commit 61be906

Browse files
committed
Add configuration for async GitHub request (re)tries
Currently in some places we use synchronous retries and for checking merge status on state updates we use a single asynchronous check. This configuration can be used to combine the best of those 2 approaches - multiple retries with increasing delays can be used to appropriately handle both cases where GitHub achieves eventual consistency quickly and the cases where it's achieved slowly (e.g. in low-traffic repositories). sort.Slice is added in Go version 1.8. Travis defaults to Go version 1.7.4. Using 1.x in Travis conf forces Travis to use the latest minor version, which is 1.8 right now.
1 parent f3c80cf commit 61be906

File tree

3 files changed

+121
-8
lines changed

3 files changed

+121
-8
lines changed

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
language: go
2+
go:
3+
- 1.x
24
sudo: false

config.go

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package main
22

33
import (
4+
"sort"
45
"strconv"
6+
"strings"
57
"time"
68

79
"github.com/deiwin/gonfigure"
@@ -13,13 +15,17 @@ var (
1315
secretProperty = gonfigure.NewRequiredEnvProperty("GITHUB_SECRET")
1416
// In the format defined in time.ParseDuration. E.g. "300ms", "-1.5h" or "2h45m".
1517
githubAPIDelayProperty = gonfigure.NewEnvProperty("GITHUB_API_DELAY", "2s")
18+
// A comma separated list of durations in the format defined in
19+
// time.ParseDuration. E.g. "300ms,1.5h,2h45m".
20+
githubAPITriesProperty = gonfigure.NewEnvProperty("GITHUB_API_TRIES", "0s,10s,30s,3m")
1621
)
1722

1823
type Config struct {
19-
Port int
20-
AccessToken string
21-
Secret string
22-
GithubAPIDelay time.Duration
24+
Port int
25+
AccessToken string
26+
Secret string
27+
GithubAPIDelay time.Duration
28+
GithubAPITryDeltas []time.Duration
2329
}
2430

2531
func NewConfig() Config {
@@ -34,9 +40,33 @@ func NewConfig() Config {
3440
}
3541

3642
return Config{
37-
Port: port,
38-
AccessToken: accessTokenProperty.Value(),
39-
Secret: secretProperty.Value(),
40-
GithubAPIDelay: githubAPIDelay,
43+
Port: port,
44+
AccessToken: accessTokenProperty.Value(),
45+
Secret: secretProperty.Value(),
46+
GithubAPIDelay: githubAPIDelay,
47+
GithubAPITryDeltas: getDeltasFromDurationsString(githubAPITriesProperty.Value()),
4148
}
4249
}
50+
51+
func getDeltasFromDurationsString(durationsString string) []time.Duration {
52+
durationStringList := strings.Split(durationsString, ",")
53+
durationList := make([]time.Duration, len(durationStringList))
54+
for i, durationString := range durationStringList {
55+
var err error
56+
durationList[i], err = time.ParseDuration(durationString)
57+
if err != nil {
58+
panic(err)
59+
}
60+
}
61+
sort.Slice(durationList, func(i, j int) bool { return durationList[i] < durationList[j] })
62+
63+
deltas := make([]time.Duration, len(durationList))
64+
for i, duration := range durationList {
65+
if i == 0 {
66+
deltas[i] = duration
67+
} else {
68+
deltas[i] = duration - durationList[i-1]
69+
}
70+
}
71+
return deltas
72+
}

config_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,87 @@ var _ = Describe("Config", func() {
136136
})
137137
})
138138
})
139+
140+
Describe("GITHUB_API_TRIES", func() {
141+
name := "GITHUB_API_TRIES"
142+
143+
Context("when set to sorted durations", func() {
144+
durationsString := "0s,2m,2h,4h"
145+
setEnvVars(requiredEnvVars)
146+
setEnvVar(envVar{name: name, value: durationsString})
147+
148+
It("is passed as an array of duration deltas", func() {
149+
conf := grh.NewConfig()
150+
Expect(conf.GithubAPITryDeltas).To(Equal([]time.Duration{
151+
0,
152+
2 * time.Minute,
153+
time.Hour + 58*time.Minute,
154+
2 * time.Hour,
155+
}))
156+
})
157+
})
158+
159+
Context("when set to unsorted durations", func() {
160+
durationsString := "0s,2h,2m,4h"
161+
setEnvVars(requiredEnvVars)
162+
setEnvVar(envVar{name: name, value: durationsString})
163+
164+
It("is passed as a sorted array of duration deltas", func() {
165+
conf := grh.NewConfig()
166+
Expect(conf.GithubAPITryDeltas).To(Equal([]time.Duration{
167+
0,
168+
2 * time.Minute,
169+
time.Hour + 58*time.Minute,
170+
2 * time.Hour,
171+
}))
172+
})
173+
})
174+
175+
Context("when contains an invalid duration", func() {
176+
durationsString := "two minutes,2h"
177+
setEnvVars(requiredEnvVars)
178+
setEnvVar(envVar{name: name, value: durationsString})
179+
180+
It("panics", func() {
181+
Expect(func() {
182+
grh.NewConfig()
183+
}).To(Panic())
184+
})
185+
})
186+
187+
Context("when contains an empty duration", func() {
188+
durationsString := ",2h"
189+
setEnvVars(requiredEnvVars)
190+
setEnvVar(envVar{name: name, value: durationsString})
191+
192+
It("panics", func() {
193+
Expect(func() {
194+
grh.NewConfig()
195+
}).To(Panic())
196+
})
197+
})
198+
199+
Context("when contains spaces", func() {
200+
durationsString := "2m, 2h"
201+
setEnvVars(requiredEnvVars)
202+
setEnvVar(envVar{name: name, value: durationsString})
203+
204+
It("panics", func() {
205+
Expect(func() {
206+
grh.NewConfig()
207+
}).To(Panic())
208+
})
209+
})
210+
211+
Context("when not set", func() {
212+
setEnvVars(requiredEnvVars)
213+
214+
It("defaults to a value", func() {
215+
conf := grh.NewConfig()
216+
Expect(conf.GithubAPITryDeltas).NotTo(BeNil())
217+
})
218+
})
219+
})
139220
})
140221

141222
var setEnvVar = func(variable envVar) {

0 commit comments

Comments
 (0)