request/nonce: Refactor to simplify package and prevent consecutive mutex lock calls when accessing/setting nonce values (#1506)

* improv. timed mutex

* Add all protection back in and jankyness because races. :'(

* Add intial benchmarkeroos

* Add master benchmarks

* goodness me

* what?

* what again?

* glorious: nits

* just a swaperino instead

* clean up package nonce so that we only need to aquire mutex once

* unlock before checking master

* commentary

* wha

* more comment

* ch comment

* nonce: Allow for broad customisation externally with a ~2ns overhead

* glorious: nits maybe works?

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
This commit is contained in:
Ryan O'Hara-Reid
2024-04-12 16:54:21 +10:00
committed by GitHub
parent 9657a570dd
commit e823f9edd8
12 changed files with 92 additions and 152 deletions

View File

@@ -226,11 +226,7 @@ func (r *Requester) doRequest(ctx context.Context, endpoint EndpointLimit, newRe
}
if verbose {
log.Errorf(log.RequestSys,
"%s request has failed. Retrying request in %s, attempt %d",
r.name,
delay,
attempt)
log.Errorf(log.RequestSys, "%s request has failed. Retrying request in %s, attempt %d", r.name, delay, attempt)
}
time.Sleep(delay)
@@ -281,21 +277,12 @@ func (r *Requester) doRequest(ctx context.Context, endpoint EndpointLimit, newRe
err = resp.Body.Close()
if err != nil {
log.Errorf(log.RequestSys,
"%s failed to close request body %s",
r.name,
err)
log.Errorf(log.RequestSys, "%s failed to close request body %s", r.name, err)
}
if verbose {
log.Debugf(log.RequestSys,
"HTTP status: %s, Code: %v",
resp.Status,
resp.StatusCode)
log.Debugf(log.RequestSys, "HTTP status: %s, Code: %v", resp.Status, resp.StatusCode)
if !p.HTTPDebugging {
log.Debugf(log.RequestSys,
"%s raw response: %s",
r.name,
string(contents))
log.Debugf(log.RequestSys, "%s raw response: %s", r.name, string(contents))
}
}
return unmarshallError
@@ -304,44 +291,19 @@ func (r *Requester) doRequest(ctx context.Context, endpoint EndpointLimit, newRe
func (r *Requester) drainBody(body io.ReadCloser) {
if _, err := io.Copy(io.Discard, io.LimitReader(body, drainBodyLimit)); err != nil {
log.Errorf(log.RequestSys,
"%s failed to drain request body %s",
r.name,
err)
log.Errorf(log.RequestSys, "%s failed to drain request body %s", r.name, err)
}
if err := body.Close(); err != nil {
log.Errorf(log.RequestSys,
"%s failed to close request body %s",
r.name,
err)
log.Errorf(log.RequestSys, "%s failed to close request body %s", r.name, err)
}
}
// GetNonce returns a nonce for requests. This locks and enforces concurrent
// nonce FIFO on the buffered job channel
func (r *Requester) GetNonce(isNano bool) nonce.Value {
func (r *Requester) GetNonce(set nonce.Setter) nonce.Value {
r.timedLock.LockForDuration()
if r.Nonce.Get() == 0 {
if isNano {
r.Nonce.Set(time.Now().UnixNano())
} else {
r.Nonce.Set(time.Now().Unix())
}
return r.Nonce.Get()
}
return r.Nonce.GetInc()
}
// GetNonceMilli returns a nonce for requests. This locks and enforces concurrent
// nonce FIFO on the buffered job channel this is for millisecond
func (r *Requester) GetNonceMilli() nonce.Value {
r.timedLock.LockForDuration()
if r.Nonce.Get() == 0 {
r.Nonce.Set(time.Now().UnixMilli())
return r.Nonce.Get()
}
return r.Nonce.GetInc()
return r.Nonce.GetAndIncrement(set)
}
// SetProxy sets a proxy address for the client transport

View File

@@ -18,7 +18,10 @@ import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/exchanges/nonce"
"golang.org/x/time/rate"
)
@@ -494,37 +497,35 @@ func TestDoRequest_NotRetryable(t *testing.T) {
func TestGetNonce(t *testing.T) {
t.Parallel()
r, err := New("test",
new(http.Client),
WithLimiter(&globalshell))
if err != nil {
t.Fatal(err)
}
if n1, n2 := r.GetNonce(false), r.GetNonce(false); n1 == n2 {
t.Fatal(unexpected)
}
r, err := New("test", new(http.Client), WithLimiter(&globalshell))
require.NoError(t, err)
n1 := r.GetNonce(nonce.Unix)
assert.NotZero(t, n1)
n2 := r.GetNonce(nonce.Unix)
assert.NotZero(t, n2)
assert.NotEqual(t, n1, n2)
r2, err := New("test",
new(http.Client),
WithLimiter(&globalshell))
if err != nil {
t.Fatal(err)
}
if n1, n2 := r2.GetNonce(true), r2.GetNonce(true); n1 == n2 {
t.Fatal(unexpected)
}
r2, err := New("test", new(http.Client), WithLimiter(&globalshell))
require.NoError(t, err)
n3 := r2.GetNonce(nonce.UnixNano)
assert.NotZero(t, n3)
n4 := r2.GetNonce(nonce.UnixNano)
assert.NotZero(t, n4)
assert.NotEqual(t, n3, n4)
assert.NotEqual(t, n1, n3)
assert.NotEqual(t, n2, n4)
}
func TestGetNonceMillis(t *testing.T) {
t.Parallel()
r, err := New("test",
new(http.Client),
WithLimiter(&globalshell))
if err != nil {
t.Fatal(err)
}
if m1, m2 := r.GetNonceMilli(), r.GetNonceMilli(); m1 == m2 {
log.Fatal(unexpected)
// 40532461 30.29 ns/op 0 B/op 0 allocs/op (prev)
// 45329203 26.53 ns/op 0 B/op 0 allocs/op
func BenchmarkGetNonce(b *testing.B) {
r, err := New("test", new(http.Client), WithLimiter(&globalshell))
require.NoError(b, err)
b.ResetTimer()
for i := 0; i < b.N; i++ {
r.GetNonce(nonce.UnixNano)
r.timedLock.UnlockIfLocked()
}
}