Skip to content
This repository was archived by the owner on Apr 1, 2026. It is now read-only.

Commit 5ae7acc

Browse files
committed
added set_next to TrackedBackoffGenerator
1 parent 67c08fd commit 5ae7acc

2 files changed

Lines changed: 72 additions & 1 deletion

File tree

google/cloud/bigtable/data/_helpers.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,18 +264,45 @@ def __init__(self, initial=0.01, maximum=60, multiplier=2):
264264
self.subgenerator = exponential_sleep_generator(
265265
initial=initial, maximum=maximum, multiplier=multiplier
266266
)
267+
self._next_override: float | None = None
267268

268269
def __iter__(self):
269270
return self
270271

272+
def set_next(self, next_value: float):
273+
"""
274+
Set the next backoff value, instead of generating one from subgenerator.
275+
After the value is yielded, it will go back to using self.subgenerator.
276+
277+
If set_next is called twice before the next() is called, only the latest
278+
value will be used and others discarded
279+
280+
Args:
281+
next_value: the upcomming value to yield when next() is called
282+
Raises:
283+
ValueError: if next_value is negative
284+
"""
285+
if next_value < 0:
286+
raise ValueError("backoff value cannot be less than 0")
287+
self._next_override = next_value
288+
271289
def __next__(self) -> float:
272-
next_backoff = next(self.subgenerator)
290+
if self._next_override is not None:
291+
next_backoff = self._next_override
292+
self._next_override = None
293+
else:
294+
next_backoff = next(self.subgenerator)
273295
self.history.append(next_backoff)
274296
return next_backoff
275297

276298
def get_attempt_backoff(self, attempt_idx) -> float:
277299
"""
278300
returns the backoff time for a specific attempt index, starting at 0.
301+
302+
Args:
303+
attempt_idx: the index of the attempt to return backoff for
304+
Raises:
305+
IndexError: if attempt_idx is negative, or not in history
279306
"""
280307
if attempt_idx < 0:
281308
raise IndexError("received negative attempt number")

tests/unit/data/test__helpers.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,3 +317,47 @@ def test_get_attempt_backoff_out_of_bounds(self):
317317
generator.get_attempt_backoff(2)
318318
with pytest.raises(IndexError):
319319
generator.get_attempt_backoff(-3)
320+
321+
def test_set_next_full_set(self):
322+
"""
323+
try always using set_next to populate generator
324+
"""
325+
generator = _helpers.TrackedBackoffGenerator()
326+
for idx, val in enumerate(range(100, 0, -1)):
327+
generator.set_next(val)
328+
got = next(generator)
329+
assert got == val
330+
assert generator.get_attempt_backoff(idx) == val
331+
332+
def test_set_next_negative_value(self):
333+
generator = _helpers.TrackedBackoffGenerator()
334+
with pytest.raises(ValueError):
335+
generator.set_next(-1)
336+
337+
@mock.patch("random.uniform", side_effect=lambda a, b: b)
338+
def test_interleaved_set_next(self, mock_uniform):
339+
import itertools
340+
341+
generator = _helpers.TrackedBackoffGenerator(
342+
initial=1, multiplier=2, maximum=128
343+
)
344+
# values we expect generator to create
345+
expected_values = [2**i for i in range(8)]
346+
# values we will insert
347+
inserted_values = [9, 61, 0, 4, 33, 12, 18, 2]
348+
for idx in range(8):
349+
assert next(generator) == expected_values[idx]
350+
generator.set_next(inserted_values[idx])
351+
assert next(generator) == inserted_values[idx]
352+
# check to make sure history is as we expect
353+
generator.history = itertools.chain.from_iterable(
354+
zip(expected_values, inserted_values)
355+
)
356+
357+
@mock.patch("random.uniform", side_effect=lambda a, b: b)
358+
def test_set_next_replacement(self, mock_uniform):
359+
generator = _helpers.TrackedBackoffGenerator(initial=1)
360+
generator.set_next(99)
361+
generator.set_next(88)
362+
assert next(generator) == 88
363+
assert next(generator) == 1

0 commit comments

Comments
 (0)