Browse Source

ensure deadlock tests play nicely with -n auto

This makes the tests more co-operative under failure, the tradeoff being that debugging may require some extra work.
pull/15388/head
oliver 1 month ago
parent
commit
118ffd34ec
  1. 11
      tests/test_concurrency.py
  2. 24
      tests/test_depends_deadlock.py

11
tests/test_concurrency.py

@ -37,7 +37,6 @@ async def test_contextmanager_in_threadpool() -> None:
@pytest.mark.anyio
@pytest.mark.timeout(10, func_only=True)
@pytest.mark.usefixtures("reset_teardown_limiter")
async def test_competing_acquire_release() -> None:
"""Check that the main threadpool does not block the teardown threadpool."""
@ -46,15 +45,19 @@ async def test_competing_acquire_release() -> None:
acquired = []
def acquire() -> None:
while not acquirable:
# We wait a max of 5s to acquire, which should be more than enough
for _ in range(5000):
if acquirable:
break
time.sleep(0.001)
assert acquirable, "Failed to acquire resource within timeout"
acquired.append(True)
def release() -> bool:
def release() -> None:
nonlocal acquirable
time.sleep(0.001)
acquirable = True
return acquirable
async with anyio.create_task_group() as tg:
for _ in range(pool_size):

24
tests/test_depends_deadlock.py

@ -1,4 +1,5 @@
import asyncio
import multiprocessing
import threading
import time
from collections.abc import Iterator
@ -40,11 +41,30 @@ def get_deadlock(dep: None = Depends(release_resource)) -> Item:
return Item(name="foo", id=1)
# NOTE: the failure mode here can cause the test suite to hang.
# Since the default threadpool is a global which is not easily patched
# (and doing so would make the test somewhat fragile/pointless)
# we need to use a subprocess to isolate the bad behaviour.
# If you are struggling to debug, comment out this function and rename
# the impl_ version to test_depends_deadlock
def test_depends_deadlock() -> None:
proc = multiprocessing.Process(target=impl_test_depends_deadlock, daemon=True)
proc.start()
proc.join(timeout=10)
try:
assert not proc.is_alive(), "Process is still alive after timeout, likely due to a deadlock"
finally:
proc.kill() # Ensure the process is terminated if it's still alive
assert proc.exitcode == 0, "Process did not exit cleanly, see logs for details"
# Fire off 100 requests in parallel(ish) in order to create contention
# over the shared resource (simulating a fastapi server that interacts with
# a database connection pool).
@pytest.mark.timeout(10, func_only=True)
def test_depends_deadlock() -> None:
def impl_test_depends_deadlock() -> None:
async def make_request(client: AsyncClient):
await client.get("/deadlock")

Loading…
Cancel
Save