Skip to content

Commit 895ae14

Browse files
authored
Fix middleware application race condition (#24)
We occasionally see a nil pointer exception that suggests the "next" RoundTripper in the client middleware chain is nil. In reviewing the middleware code, I noticed that the use of append() at client creation time could modify the global slice associated with the creator instance without a lock. This is definitely a race condition, and could be the cause of the observed exception (but I have no direct evidence.) Change the application function to take individual slices and iterate over them directly, rather than creating a single slice.
1 parent e352709 commit 895ae14

File tree

1 file changed

+29
-22
lines changed

1 file changed

+29
-22
lines changed

githubapp/client_creator.go

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,9 @@ func WithClientMiddleware(middleware ...ClientMiddleware) ClientOption {
165165

166166
func (c *clientCreator) NewAppClient() (*github.Client, error) {
167167
base := &http.Client{Transport: http.DefaultTransport}
168-
169168
installation, transportError := newAppInstallation(c.integrationID, c.privKeyBytes, c.v3BaseURL)
170-
middleware := append(c.middleware, installation)
169+
170+
middleware := []ClientMiddleware{installation}
171171
if c.cacheFunc != nil {
172172
middleware = append(middleware, cache(c.cacheFunc), cacheControl(c.alwaysValidate))
173173
}
@@ -184,14 +184,13 @@ func (c *clientCreator) NewAppClient() (*github.Client, error) {
184184

185185
func (c *clientCreator) NewAppV4Client() (*githubv4.Client, error) {
186186
base := &http.Client{Transport: http.DefaultTransport}
187-
188187
installation, transportError := newAppInstallation(c.integrationID, c.privKeyBytes, c.v3BaseURL)
189188

190189
// The v4 API primarily uses POST requests (except for introspection queries)
191-
// which we cannot cache, so don't construct the middleware
192-
middleware := append(c.middleware, installation)
190+
// which we cannot cache, so don't add the cache middleware
191+
middleware := []ClientMiddleware{installation}
193192

194-
client, err := c.newV4Client(base, middleware, "application", 0)
193+
client, err := c.newV4Client(base, middleware, "application")
195194
if err != nil {
196195
return nil, err
197196
}
@@ -203,9 +202,9 @@ func (c *clientCreator) NewAppV4Client() (*githubv4.Client, error) {
203202

204203
func (c *clientCreator) NewInstallationClient(installationID int64) (*github.Client, error) {
205204
base := &http.Client{Transport: http.DefaultTransport}
206-
207205
installation, transportError := newInstallation(c.integrationID, int(installationID), c.privKeyBytes, c.v3BaseURL)
208-
middleware := append(c.middleware, installation)
206+
207+
middleware := []ClientMiddleware{installation}
209208
if c.cacheFunc != nil {
210209
middleware = append(middleware, cache(c.cacheFunc), cacheControl(c.alwaysValidate))
211210
}
@@ -222,14 +221,13 @@ func (c *clientCreator) NewInstallationClient(installationID int64) (*github.Cli
222221

223222
func (c *clientCreator) NewInstallationV4Client(installationID int64) (*githubv4.Client, error) {
224223
base := &http.Client{Transport: http.DefaultTransport}
225-
226224
installation, transportError := newInstallation(c.integrationID, int(installationID), c.privKeyBytes, c.v3BaseURL)
227225

228226
// The v4 API primarily uses POST requests (except for introspection queries)
229227
// which we cannot cache, so don't construct the middleware
230-
middleware := append(c.middleware, installation)
228+
middleware := []ClientMiddleware{installation}
231229

232-
client, err := c.newV4Client(base, middleware, fmt.Sprintf("installation: %d", installationID), installationID)
230+
client, err := c.newV4Client(base, middleware, fmt.Sprintf("installation: %d", installationID))
233231
if err != nil {
234232
return nil, err
235233
}
@@ -242,18 +240,21 @@ func (c *clientCreator) NewInstallationV4Client(installationID int64) (*githubv4
242240
func (c *clientCreator) NewTokenClient(token string) (*github.Client, error) {
243241
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
244242
tc := oauth2.NewClient(context.Background(), ts)
245-
return c.newClient(tc, c.middleware, "oauth token", 0)
243+
return c.newClient(tc, nil, "oauth token", 0)
246244
}
247245

248246
func (c *clientCreator) NewTokenV4Client(token string) (*githubv4.Client, error) {
249247
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
250248
tc := oauth2.NewClient(context.Background(), ts)
251-
return c.newV4Client(tc, c.middleware, "oauth token", 0)
249+
return c.newV4Client(tc, nil, "oauth token")
252250
}
253251

254252
func (c *clientCreator) newClient(base *http.Client, middleware []ClientMiddleware, details string, installID int64) (*github.Client, error) {
255-
middleware = append([]ClientMiddleware{setInstallationID(installID)}, middleware...)
256-
applyMiddleware(base, middleware)
253+
applyMiddleware(base, [][]ClientMiddleware{
254+
{setInstallationID(installID)},
255+
c.middleware,
256+
middleware,
257+
})
257258

258259
baseURL, err := url.Parse(c.v3BaseURL)
259260
if err != nil {
@@ -267,11 +268,12 @@ func (c *clientCreator) newClient(base *http.Client, middleware []ClientMiddlewa
267268
return client, nil
268269
}
269270

270-
func (c *clientCreator) newV4Client(base *http.Client, middleware []ClientMiddleware, details string, installID int64) (*githubv4.Client, error) {
271-
ua := makeUserAgent(c.userAgent, details)
272-
273-
middleware = append([]ClientMiddleware{setUserAgentHeader(ua)}, middleware...)
274-
applyMiddleware(base, middleware)
271+
func (c *clientCreator) newV4Client(base *http.Client, middleware []ClientMiddleware, details string) (*githubv4.Client, error) {
272+
applyMiddleware(base, [][]ClientMiddleware{
273+
{setUserAgentHeader(makeUserAgent(c.userAgent, details))},
274+
c.middleware,
275+
middleware,
276+
})
275277

276278
v4BaseURL, err := url.Parse(c.v4BaseURL)
277279
if err != nil {
@@ -282,9 +284,14 @@ func (c *clientCreator) newV4Client(base *http.Client, middleware []ClientMiddle
282284
return client, nil
283285
}
284286

285-
func applyMiddleware(base *http.Client, middleware []ClientMiddleware) {
287+
// applyMiddleware behaves as if it concatenates all middleware slices in the
288+
// order given and then composes the middleware so that the first element is
289+
// the outermost function and the last element is the innermost function.
290+
func applyMiddleware(base *http.Client, middleware [][]ClientMiddleware) {
286291
for i := len(middleware) - 1; i >= 0; i-- {
287-
base.Transport = middleware[i](base.Transport)
292+
for j := len(middleware[i]) - 1; j >= 0; j-- {
293+
base.Transport = middleware[i][j](base.Transport)
294+
}
288295
}
289296
}
290297

0 commit comments

Comments
 (0)