From bb976e776e15b67b2fc55f1ccedf36fd63c1f44b Mon Sep 17 00:00:00 2001 From: Antoine Pourchet Date: Tue, 26 Feb 2019 23:26:37 -0800 Subject: [PATCH 1/2] Locking SendWithAuth to fix race condition When SendWithAuth gets called in a multithreaded environment on the same Client object, a concurrent read and write of c.Token might happen in GetAccessToken. This patch solves the issue by locking the client while we get a new access token in SendWithAuth. --- client.go | 3 +++ types.go | 2 ++ 2 files changed, 5 insertions(+) diff --git a/client.go b/client.go index b494af8..bf4d370 100644 --- a/client.go +++ b/client.go @@ -125,10 +125,12 @@ func (c *Client) Send(req *http.Request, v interface{}) error { // making the main request // client.Token will be updated when changed func (c *Client) SendWithAuth(req *http.Request, v interface{}) error { + c.Lock() if c.Token != nil { if !c.tokenExpiresAt.IsZero() && c.tokenExpiresAt.Sub(time.Now()) < RequestNewTokenBeforeExpiresIn { // c.Token will be updated in GetAccessToken call if _, err := c.GetAccessToken(); err != nil { + c.Unlock() return err } } @@ -136,6 +138,7 @@ func (c *Client) SendWithAuth(req *http.Request, v interface{}) error { req.Header.Set("Authorization", "Bearer "+c.Token.Token) } + c.Unlock() return c.Send(req, v) } diff --git a/types.go b/types.go index 2eb4c82..6441ddf 100644 --- a/types.go +++ b/types.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net/http" + "sync" "time" ) @@ -168,6 +169,7 @@ type ( // Client represents a Paypal REST API Client Client struct { + sync.Mutex Client *http.Client ClientID string Secret string From 4303b79440b4c6145046cd1cfc5ff77bf583d042 Mon Sep 17 00:00:00 2001 From: Antoine Pourchet Date: Thu, 28 Feb 2019 20:17:03 -0800 Subject: [PATCH 2/2] Added comments for clarity --- client.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client.go b/client.go index bf4d370..ee78652 100644 --- a/client.go +++ b/client.go @@ -126,6 +126,9 @@ func (c *Client) Send(req *http.Request, v interface{}) error { // client.Token will be updated when changed func (c *Client) SendWithAuth(req *http.Request, v interface{}) error { c.Lock() + // Note: Here we do not want to `defer c.Unlock()` because we need `c.Send(...)` + // to happen outside of the locked section. + if c.Token != nil { if !c.tokenExpiresAt.IsZero() && c.tokenExpiresAt.Sub(time.Now()) < RequestNewTokenBeforeExpiresIn { // c.Token will be updated in GetAccessToken call @@ -138,6 +141,8 @@ func (c *Client) SendWithAuth(req *http.Request, v interface{}) error { req.Header.Set("Authorization", "Bearer "+c.Token.Token) } + // Unlock the client mutex before sending the request, this allows multiple requests + // to be in progress at the same time. c.Unlock() return c.Send(req, v) }