added concurrency limiting
This commit is contained in:
1 parent
4aa0e43289
commit
3a8a12ac6e
2 files changed
+93
-3
No files matched your search
@@ -0,0 +1,87 @@
|
||||
"""unit test: execute_pagination honours a caller-supplied shared semaphore
|
||||
|
||||
run from the repo root:
|
||||
uv run python tests/test_pagination_semaphore.py
|
||||
|
||||
offline — no network. A fake page func records peak concurrency so we can
|
||||
assert the fan-out limit actually holds, both for the shared-semaphore path
|
||||
(one instance capping many categories at once) and the default per-call path.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from monitor_utils.runner import execute_pagination
|
||||
|
||||
|
||||
def _make_tracking_func(state: dict):
|
||||
"""Return a page func that tracks peak simultaneous in-flight calls."""
|
||||
|
||||
async def func(session, offset, **kwargs):
|
||||
state["live"] += 1
|
||||
state["peak"] = max(state["peak"], state["live"])
|
||||
await asyncio.sleep(0.01)
|
||||
state["live"] -= 1
|
||||
|
||||
return func
|
||||
|
||||
|
||||
def test_shared_semaphore_caps_total_concurrency_across_categories():
|
||||
"""One shared semaphore bounds combined fan-out over concurrent categories.
|
||||
|
||||
Two categories paginate at once through the same semaphore(limit); without
|
||||
sharing, each would build its own semaphore and the peak could reach twice
|
||||
the limit. Peak must stay at or below the shared limit.
|
||||
"""
|
||||
|
||||
limit = 3
|
||||
shared = asyncio.Semaphore(limit)
|
||||
state = {"live": 0, "peak": 0}
|
||||
func = _make_tracking_func(state)
|
||||
|
||||
async def run():
|
||||
await asyncio.gather(
|
||||
execute_pagination(
|
||||
"catA",
|
||||
total_products=300,
|
||||
products_per_page=30,
|
||||
func=func,
|
||||
session=None,
|
||||
semaphore=shared,
|
||||
),
|
||||
execute_pagination(
|
||||
"catB",
|
||||
total_products=300,
|
||||
products_per_page=30,
|
||||
func=func,
|
||||
session=None,
|
||||
semaphore=shared,
|
||||
),
|
||||
)
|
||||
|
||||
asyncio.run(run())
|
||||
assert state["peak"] <= limit, f"peak {state['peak']} exceeded shared limit {limit}"
|
||||
|
||||
|
||||
def test_default_falls_back_to_max_concurrent_tasks():
|
||||
"""Without a semaphore, max_concurrent_tasks still bounds a single call."""
|
||||
|
||||
state = {"live": 0, "peak": 0}
|
||||
func = _make_tracking_func(state)
|
||||
|
||||
async def run():
|
||||
await execute_pagination(
|
||||
"cat",
|
||||
total_products=300,
|
||||
products_per_page=30,
|
||||
func=func,
|
||||
session=None,
|
||||
max_concurrent_tasks=2,
|
||||
)
|
||||
|
||||
asyncio.run(run())
|
||||
assert state["peak"] <= 2, f"peak {state['peak']} exceeded max_concurrent_tasks 2"
|
||||
|
||||
|
||||
test_shared_semaphore_caps_total_concurrency_across_categories()
|
||||
test_default_falls_back_to_max_concurrent_tasks()
|
||||
print("pagination semaphore test passed")
|
||||
Reference in new issue
Block a user