From 05ffd8f0f13345044d5ffb4405949794cb316bd6 Mon Sep 17 00:00:00 2001 From: Tom Clegg Date: Tue, 5 Dec 2023 10:31:13 -0500 Subject: [PATCH] 21227: Use a separate global requestLimiter for each target host. Arvados-DCO-1.1-Signed-off-by: Tom Clegg --- sdk/go/arvados/client.go | 24 ++++++++++++++++++------ sdk/go/arvados/limiter.go | 7 ------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/sdk/go/arvados/client.go b/sdk/go/arvados/client.go index 172763fe80..8a7c7fa60f 100644 --- a/sdk/go/arvados/client.go +++ b/sdk/go/arvados/client.go @@ -26,6 +26,7 @@ import ( "regexp" "strconv" "strings" + "sync" "sync/atomic" "time" @@ -356,17 +357,28 @@ func (c *Client) Last503() time.Time { return t } -// globalRequestLimiter doesn't have a maximum number of outgoing -// connections, but is just used to backoff after 503 errors. -var globalRequestLimiter requestLimiter +// globalRequestLimiter entries (one for each APIHost) don't have a +// hard limit on outgoing connections, but do add a delay and reduce +// concurrency after 503 errors. +var ( + globalRequestLimiter = map[string]*requestLimiter{} + globalRequestLimiterLock sync.Mutex +) -// Get this client's requestLimiter, or the global requestLimiter -// singleton if the client doesn't have its own. +// Get this client's requestLimiter, or a global requestLimiter +// singleton for c's APIHost if this client doesn't have its own. func (c *Client) getRequestLimiter() *requestLimiter { if c.requestLimiter != nil { return c.requestLimiter } - return &globalRequestLimiter + globalRequestLimiterLock.Lock() + defer globalRequestLimiterLock.Unlock() + limiter := globalRequestLimiter[c.APIHost] + if limiter == nil { + limiter = &requestLimiter{} + globalRequestLimiter[c.APIHost] = limiter + } + return limiter } // cancelOnClose calls a provided CancelFunc when its wrapped diff --git a/sdk/go/arvados/limiter.go b/sdk/go/arvados/limiter.go index 9edc5386a7..dc944160ab 100644 --- a/sdk/go/arvados/limiter.go +++ b/sdk/go/arvados/limiter.go @@ -16,7 +16,6 @@ import ( var ( requestLimiterQuietPeriod = time.Second requestLimiterInitialLimit int64 = 8 - requestLimiterMinimumLimit int64 = 4 ) type requestLimiter struct { @@ -146,12 +145,6 @@ func (rl *requestLimiter) Report(resp *http.Response, err error) bool { if max := rl.current * 2; max < rl.limit { rl.limit = max } - if min := requestLimiterMinimumLimit; min > rl.limit { - // If limit is too low, programs like - // controller and test suites can end up with - // too few slots to complete a single request. - rl.limit = min - } if rl.maxlimit > 0 && rl.maxlimit < rl.limit { rl.limit = rl.maxlimit } -- 2.30.2