Skip to content

Commit 43ca782

Browse files
authored
Only reset token bucket if needed (#821)
Fixes #801
1 parent 0dcc9e0 commit 43ca782

3 files changed

Lines changed: 45 additions & 6 deletions

File tree

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# httr2 (development version)
22

3+
* Multiple calls to `req_throttle()` no longer reset the token bucket (#801).
34
* New `resps_ok()` returns a logical vector indicating which requests were successful (#807).
45
* httr2 will now emit OpenTelemetry traces for all requests when tracing is enabled. Requires the `otelsdk` package (@atheriel, #729).
56
* `req_perform_connection()` no longer errors with `no applicable method for 'close' applied to an object of class "c('httr2_failure', 'httr2_error', 'rlang_error', 'error', 'condition')` (#817).

R/req-throttle.R

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,15 @@ req_throttle <- function(req, rate, capacity, fill_time_s = 60, realm = NULL) {
4848
capacity <- rate * fill_time_s
4949
} else {
5050
check_number_whole(capacity, min = 0)
51+
rate <- capacity / fill_time_s
5152
}
5253
check_number_decimal(fill_time_s, min = 0)
5354
check_string(realm, allow_null = TRUE)
5455

5556
realm <- realm %||% url_parse(req$url)$hostname
56-
the$throttle[[realm]] <- TokenBucket$new(capacity, fill_time_s)
57-
57+
if (!throttle_exists(realm, capacity, rate)) {
58+
the$throttle[[realm]] <- TokenBucket$new(capacity, rate)
59+
}
5860
req_policies(req, throttle_realm = realm)
5961
}
6062

@@ -92,6 +94,15 @@ throttle_reset <- function(realm = NULL) {
9294
invisible()
9395
}
9496

97+
throttle_exists <- function(realm, capacity, fill_rate) {
98+
if (!env_has(the$throttle, realm)) {
99+
return(FALSE)
100+
}
101+
102+
cur_throttle <- the$throttle[[realm]]
103+
cur_throttle$capacity == capacity && cur_throttle$fill_rate == fill_rate
104+
}
105+
95106
throttle_delay <- function(req) {
96107
if (!req_policy_exists(req, "throttle_realm")) {
97108
0
@@ -115,10 +126,11 @@ TokenBucket <- R6::R6Class(
115126
last_fill = NULL,
116127
tokens = NULL,
117128

118-
initialize = function(capacity, fill_time_s) {
129+
initialize = function(capacity, fill_rate) {
119130
self$capacity <- capacity
120131
self$tokens <- capacity
121-
self$fill_rate <- capacity / fill_time_s
132+
self$fill_rate <- fill_rate
133+
122134
self$last_fill <- unix_time()
123135
},
124136

tests/testthat/test-req-throttle.R

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,32 @@ test_that("first request isn't throttled", {
2727
expect_equal(throttle_delay(req), 0.5)
2828
})
2929

30+
test_that("throttle only reset if parameters change", {
31+
on.exit(throttle_reset())
32+
33+
mock_time <- 0
34+
local_mocked_bindings(unix_time = function() mock_time)
35+
36+
req <- request_test() |> req_throttle(capacity = 1, fill_time_s = 1)
37+
expect_equal(throttle_delay(req), 0)
38+
39+
mock_time <- 0.5
40+
expect_equal(throttle_delay(req), 0.5)
41+
mock_time <- 1
42+
43+
req <- request_test() |> req_throttle(capacity = 1, fill_time_s = 1)
44+
expect_equal(throttle_delay(req), 1)
45+
46+
# reset: capacity changed
47+
req <- request_test() |> req_throttle(capacity = 2, fill_time_s = 1)
48+
expect_equal(throttle_delay(req), 0)
49+
50+
# reset: fill time changed
51+
req <- request_test() |> req_throttle(capacity = 2, fill_time_s = 2)
52+
expect_equal(throttle_delay(req), 0)
53+
})
54+
55+
3056
test_that("realm defaults to hostname but can be overridden", {
3157
on.exit(throttle_reset())
3258

@@ -46,7 +72,7 @@ test_that("token bucket respects capacity limits", {
4672
mock_time <- 0
4773
local_mocked_bindings(unix_time = function() mock_time)
4874

49-
bucket <- TokenBucket$new(capacity = 2, fill_time_s = 1)
75+
bucket <- TokenBucket$new(capacity = 2, fill_rate = 2)
5076
expect_equal(bucket$take_token(), 0)
5177
expect_equal(bucket$tokens, 1)
5278
expect_equal(bucket$take_token(), 0)
@@ -61,7 +87,7 @@ test_that("token bucket handles fractions correctly", {
6187
mock_time <- 0
6288
local_mocked_bindings(unix_time = function() mock_time)
6389

64-
bucket <- TokenBucket$new(capacity = 2, fill_time_s = 1)
90+
bucket <- TokenBucket$new(capacity = 2, fill_rate = 2)
6591
bucket$tokens <- 0
6692
expect_equal(bucket$take_token(), 0.5)
6793
expect_equal(bucket$tokens, -1)

0 commit comments

Comments
 (0)