test: make async deadline checks contention-safe
This commit is contained in:
parent
7e173640ed
commit
c99dd3e3be
|
|
@ -1,5 +1,6 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
from urllib.parse import parse_qs, urlsplit
|
from urllib.parse import parse_qs, urlsplit
|
||||||
|
|
||||||
|
|
@ -1617,13 +1618,18 @@ async def test_sign_in_throttle_lookup_does_not_block_the_event_loop(
|
||||||
async def test_failed_sign_in_recording_does_not_block_the_event_loop(
|
async def test_failed_sign_in_recording_does_not_block_the_event_loop(
|
||||||
access_control, monkeypatch
|
access_control, monkeypatch
|
||||||
):
|
):
|
||||||
|
recording_started = threading.Event()
|
||||||
|
recording_finished = threading.Event()
|
||||||
|
|
||||||
class SlowAttempts:
|
class SlowAttempts:
|
||||||
def retry_after(self, source):
|
def retry_after(self, source):
|
||||||
time.sleep(0.02)
|
time.sleep(0.02)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def record_failure(self, source):
|
def record_failure(self, source):
|
||||||
|
recording_started.set()
|
||||||
time.sleep(0.15)
|
time.sleep(0.15)
|
||||||
|
recording_finished.set()
|
||||||
|
|
||||||
monkeypatch.setattr(main, "_login_attempt_store", lambda: SlowAttempts())
|
monkeypatch.setattr(main, "_login_attempt_store", lambda: SlowAttempts())
|
||||||
transport = httpx.ASGITransport(app=main.app, client=("203.0.113.11", 1234))
|
transport = httpx.ASGITransport(app=main.app, client=("203.0.113.11", 1234))
|
||||||
|
|
@ -1631,27 +1637,30 @@ async def test_failed_sign_in_recording_does_not_block_the_event_loop(
|
||||||
sign_in = asyncio.create_task(
|
sign_in = asyncio.create_task(
|
||||||
client.post("/api/v1/session", json={"access_token": "wrong"})
|
client.post("/api/v1/session", json={"access_token": "wrong"})
|
||||||
)
|
)
|
||||||
|
assert await asyncio.to_thread(recording_started.wait, 1)
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
||||||
started = time.perf_counter()
|
assert not recording_finished.is_set()
|
||||||
await asyncio.sleep(0.04)
|
|
||||||
heartbeat_elapsed = time.perf_counter() - started
|
|
||||||
response = await sign_in
|
response = await sign_in
|
||||||
|
|
||||||
assert response.status_code == 401
|
assert response.status_code == 401
|
||||||
assert heartbeat_elapsed < 0.08
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_successful_sign_in_throttle_clear_does_not_block_the_event_loop(
|
async def test_successful_sign_in_throttle_clear_does_not_block_the_event_loop(
|
||||||
access_control, monkeypatch
|
access_control, monkeypatch
|
||||||
):
|
):
|
||||||
|
clear_started = threading.Event()
|
||||||
|
clear_finished = threading.Event()
|
||||||
|
|
||||||
class SlowAttempts:
|
class SlowAttempts:
|
||||||
def retry_after(self, source):
|
def retry_after(self, source):
|
||||||
time.sleep(0.02)
|
time.sleep(0.02)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def clear(self, source):
|
def clear(self, source):
|
||||||
|
clear_started.set()
|
||||||
time.sleep(0.15)
|
time.sleep(0.15)
|
||||||
|
clear_finished.set()
|
||||||
|
|
||||||
monkeypatch.setattr(main, "_login_attempt_store", lambda: SlowAttempts())
|
monkeypatch.setattr(main, "_login_attempt_store", lambda: SlowAttempts())
|
||||||
transport = httpx.ASGITransport(app=main.app, client=("203.0.113.12", 1234))
|
transport = httpx.ASGITransport(app=main.app, client=("203.0.113.12", 1234))
|
||||||
|
|
@ -1662,14 +1671,12 @@ async def test_successful_sign_in_throttle_clear_does_not_block_the_event_loop(
|
||||||
json={"access_token": "correct horse battery staple"},
|
json={"access_token": "correct horse battery staple"},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
assert await asyncio.to_thread(clear_started.wait, 1)
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
||||||
started = time.perf_counter()
|
assert not clear_finished.is_set()
|
||||||
await asyncio.sleep(0.04)
|
|
||||||
heartbeat_elapsed = time.perf_counter() - started
|
|
||||||
response = await sign_in
|
response = await sign_in
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert heartbeat_elapsed < 0.08
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
|
|
|
||||||
|
|
@ -347,14 +347,14 @@ async def test_bulk_mark_read_cancels_unfinished_work_at_batch_deadline(monkeypa
|
||||||
monkeypatch.setattr(main, "_read_notification_ids", set())
|
monkeypatch.setattr(main, "_read_notification_ids", set())
|
||||||
monkeypatch.setattr(main, "_live_snapshot_value", None)
|
monkeypatch.setattr(main, "_live_snapshot_value", None)
|
||||||
transport = httpx.ASGITransport(app=main.app)
|
transport = httpx.ASGITransport(app=main.app)
|
||||||
started_at = time.monotonic()
|
|
||||||
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
||||||
response = await client.patch(
|
response = await asyncio.wait_for(
|
||||||
"/api/v1/notifications/read", json={"ids": list(range(1, 11))}
|
client.patch(
|
||||||
|
"/api/v1/notifications/read", json={"ids": list(range(1, 11))}
|
||||||
|
),
|
||||||
|
timeout=0.5,
|
||||||
)
|
)
|
||||||
elapsed = time.monotonic() - started_at
|
|
||||||
|
|
||||||
assert elapsed < 0.06
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == {"marked": [], "failed": list(range(1, 11))}
|
assert response.json() == {"marked": [], "failed": list(range(1, 11))}
|
||||||
assert response.headers["retry-after"] == "1"
|
assert response.headers["retry-after"] == "1"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user