-
Notifications
You must be signed in to change notification settings - Fork 460
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added a mutex around API methods for concurrent calls #476
Conversation
Moved the mutex to the Configuration struct. It threw one race after another when I was trying to be too specific. Once I just added it to the overall call, everything worked out fine and was much cleaner. Also added There's other race possibilities in here as well, like if you run multiple |
error_test.go
Outdated
APIBackend, | ||
ts.URL, | ||
&http.Client{}, | ||
Type: APIBackend, URL: ts.URL, HTTPClient: &http.Client{}, | ||
}) | ||
|
||
err := GetBackend(APIBackend).Call("GET", "/v1/account", "sk_test_badKey", nil, nil, nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not something to do in this PR, but probably switching these "GET"
, and other http method calls to the official http.MethodGet
types and all that might be less error prone, -- not that this is currently the source of any issues.
Let me know what your thoughts are. Edit: I changed the method receivers to all be consistent for |
Sorry about all the comments/commits. I have no idea how this passed locally on the last few commits. Thank Travis for catching a few hiccups. |
stripe.go
Outdated
@@ -78,6 +79,7 @@ type BackendConfiguration struct { | |||
Type SupportedBackend | |||
URL string | |||
HTTPClient *http.Client | |||
sync.Mutex |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's give this a name instead of embedding the struct. mu
is usually a good candidate and used elsewhere in the code here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
Also, some of the code that looks like I formatted it is just artifacts of my first (naiive) iteration. I can change those back if you wish -- apologies for the "larger than it needs to be" PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe hold off just a sec — I'm a little tied up right now, but am in progress of writing a slightly more comprehensive comment on here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:-X Pushed before I saw this.
Hi @natdm, thanks for taking a crack at this! This is looking pretty good and I think we should get it merged. The single biggest thing I noticed is that we should move the invocations of our mutex closer to where it's actually needed. You've added a mutex on This would be my proposal: diff --git a/stripe.go b/stripe.go
index fd25675..e088978 100644
--- a/stripe.go
+++ b/stripe.go
@@ -14,6 +14,7 @@ import (
"os/exec"
"runtime"
"strings"
+ "sync"
"time"
"github.com/stripe/stripe-go/form"
@@ -101,6 +102,7 @@ const (
// Backends are the currently supported endpoints.
type Backends struct {
API, Uploads Backend
+ mu sync.RWMutex
}
// stripeClientUserAgent contains information about the current runtime which
@@ -166,22 +168,39 @@ func NewBackends(httpClient *http.Client) *Backends {
// GetBackend returns the currently used backend in the binding.
func GetBackend(backend SupportedBackend) Backend {
- var ret Backend
switch backend {
case APIBackend:
- if backends.API == nil {
- backends.API = BackendConfiguration{backend, apiURL, httpClient}
+ backends.mu.RLock()
+ ret := backends.API
+ backends.mu.RUnlock()
+
+ if ret != nil {
+ return ret
}
- ret = backends.API
+ backends.mu.Lock()
+ defer backends.mu.Unlock()
+
+ backends.API = BackendConfiguration{backend, apiURL, httpClient}
+ return backends.API
+
case UploadsBackend:
- if backends.Uploads == nil {
- backends.Uploads = BackendConfiguration{backend, uploadsURL, httpClient}
+ backends.mu.RLock()
+ ret := backends.Uploads
+ backends.mu.RUnlock()
+
+ if ret != nil {
+ return ret
}
- ret = backends.Uploads
+
+ backends.mu.Lock()
+ defer backends.mu.Unlock()
+
+ backends.Uploads = BackendConfiguration{backend, uploadsURL, httpClient}
+ return backends.Uploads
}
- return ret
+ return nil
}
// SetBackend sets the backend used in the binding. (I also moved over to an Thoughts? A few other notes:
Here's a couple responses in no particular order:
Thanks for adding that! It makes sense to add this in the test suite I think, but as noted in its release article, a race-enabled binary uses ~10x CPU and memory, so let's leave it out of the standard build paths.
Nice! Yeah, I can't believe we mixed pointer and non-pointer receivers there. They should all be the same. |
Makefile
Outdated
@@ -10,7 +10,7 @@ check-gofmt: | |||
scripts/check_gofmt.sh | |||
|
|||
test: | |||
go test ./... | |||
go test --race ./... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Go tends to prefer the BSD-style flags (i.e. one hyphen instead of two, and no short names) for its executables, so let's use -race
instead of this as described here. I'm actually fairly surprised that --race
even works!
Good call on all of that. I definitely didn't mean to add a larger diff with the named fields -- I should have cleaned that up when I realized it was not needed. I'll make the requested changes. Thanks for the comments -- just like last time, much cleaner. |
stripe_test.go
Outdated
@@ -22,6 +23,25 @@ func TestCheckinUseBearerAuth(t *testing.T) { | |||
assert.Equal(t, "Bearer "+key, req.Header.Get("Authorization")) | |||
} | |||
|
|||
// TestMultipleAPICalls will fail the test run if a race condition is thrown while running multople NewRequest calls. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very minor, but "multiple" is misspelled here.
No worries at all! I left one more nit above, but otherwise this LGTM. Thanks! |
Fixed. |
Thanks again! |
Released as 28.3.1. |
Fixes the below trace:
How to reproduce:
Orders has a Charge object, but it's not filled out. So to get that, I get all the charges. Charges have a Transaction/Balance entry, but also not populated. My code looks like this, with each call to stripe going to a List type of call (ListTransactions, etc):
Looks like the
List()
call on some of these all touch this bit of code which isn't concurrent safe.I did not include a test with the first commitTurns out I was wrong. Tests aren't being ran with the --race flag, so going to enable that, and make a test for this.This fixes all of my calls that concurrently call a
List()
type method.