Skip to content

Commit 9cd77d1

Browse files
Detect stalled pagination loops in polling helpers
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent b55847a commit 9cd77d1

2 files changed

Lines changed: 63 additions & 0 deletions

File tree

hyperbrowser/client/polling.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ def collect_paginated_results(
227227
total_page_batches = 0
228228
first_check = True
229229
failures = 0
230+
stagnation_failures = 0
230231

231232
while first_check or current_page_batch < total_page_batches:
232233
if has_exceeded_max_wait(start_time, max_wait_seconds):
@@ -235,13 +236,27 @@ def collect_paginated_results(
235236
)
236237
should_sleep = True
237238
try:
239+
previous_page_batch = current_page_batch
238240
page_response = get_next_page(current_page_batch + 1)
239241
on_page_success(page_response)
240242
current_page_batch = get_current_page_batch(page_response)
241243
total_page_batches = get_total_page_batches(page_response)
242244
failures = 0
243245
first_check = False
246+
if (
247+
current_page_batch < total_page_batches
248+
and current_page_batch <= previous_page_batch
249+
):
250+
stagnation_failures += 1
251+
if stagnation_failures >= max_attempts:
252+
raise HyperbrowserPollingError(
253+
f"No pagination progress for {operation_name} after {max_attempts} attempts (stuck on page batch {current_page_batch} of {total_page_batches})"
254+
)
255+
else:
256+
stagnation_failures = 0
244257
should_sleep = current_page_batch < total_page_batches
258+
except HyperbrowserPollingError:
259+
raise
245260
except Exception as exc:
246261
failures += 1
247262
if failures >= max_attempts:
@@ -274,6 +289,7 @@ async def collect_paginated_results_async(
274289
total_page_batches = 0
275290
first_check = True
276291
failures = 0
292+
stagnation_failures = 0
277293

278294
while first_check or current_page_batch < total_page_batches:
279295
if has_exceeded_max_wait(start_time, max_wait_seconds):
@@ -282,13 +298,27 @@ async def collect_paginated_results_async(
282298
)
283299
should_sleep = True
284300
try:
301+
previous_page_batch = current_page_batch
285302
page_response = await get_next_page(current_page_batch + 1)
286303
on_page_success(page_response)
287304
current_page_batch = get_current_page_batch(page_response)
288305
total_page_batches = get_total_page_batches(page_response)
289306
failures = 0
290307
first_check = False
308+
if (
309+
current_page_batch < total_page_batches
310+
and current_page_batch <= previous_page_batch
311+
):
312+
stagnation_failures += 1
313+
if stagnation_failures >= max_attempts:
314+
raise HyperbrowserPollingError(
315+
f"No pagination progress for {operation_name} after {max_attempts} attempts (stuck on page batch {current_page_batch} of {total_page_batches})"
316+
)
317+
else:
318+
stagnation_failures = 0
291319
should_sleep = current_page_batch < total_page_batches
320+
except HyperbrowserPollingError:
321+
raise
292322
except Exception as exc:
293323
failures += 1
294324
if failures >= max_attempts:

tests/test_polling.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,20 @@ def test_collect_paginated_results_raises_after_page_failures():
289289
)
290290

291291

292+
def test_collect_paginated_results_raises_when_page_batch_stagnates():
293+
with pytest.raises(HyperbrowserPollingError, match="No pagination progress"):
294+
collect_paginated_results(
295+
operation_name="sync paginated stagnation",
296+
get_next_page=lambda page: {"current": 1, "total": 2, "items": []},
297+
get_current_page_batch=lambda response: response["current"],
298+
get_total_page_batches=lambda response: response["total"],
299+
on_page_success=lambda response: None,
300+
max_wait_seconds=1.0,
301+
max_attempts=2,
302+
retry_delay_seconds=0.0001,
303+
)
304+
305+
292306
def test_collect_paginated_results_async_times_out():
293307
async def run() -> None:
294308
with pytest.raises(
@@ -311,6 +325,25 @@ async def run() -> None:
311325
asyncio.run(run())
312326

313327

328+
def test_collect_paginated_results_async_raises_when_page_batch_stagnates():
329+
async def run() -> None:
330+
with pytest.raises(HyperbrowserPollingError, match="No pagination progress"):
331+
await collect_paginated_results_async(
332+
operation_name="async paginated stagnation",
333+
get_next_page=lambda page: asyncio.sleep(
334+
0, result={"current": 1, "total": 2, "items": []}
335+
),
336+
get_current_page_batch=lambda response: response["current"],
337+
get_total_page_batches=lambda response: response["total"],
338+
on_page_success=lambda response: None,
339+
max_wait_seconds=1.0,
340+
max_attempts=2,
341+
retry_delay_seconds=0.0001,
342+
)
343+
344+
asyncio.run(run())
345+
346+
314347
def test_wait_for_job_result_returns_fetched_value():
315348
status_values = iter(["running", "completed"])
316349

0 commit comments

Comments
 (0)