Silent control flow
The corpus prior¶
Defensive-looking control flow that never asks what state it is in.
Retrying, catching, or polling all have the shape of caution. What they lack is the question underneath: "what is the state of the thing I am operating on, and did my handler change it?"
Wrong shape¶
# WRONG — the transaction is already aborted; this retry always raises
try:
cursor.execute(sql, params)
except Exception:
sql = rebuild_with_alternate_column()
cursor.execute(sql, params) # InFailedSqlTransaction
# WRONG — blocking the event loop inside async def
async def _confirm_subscription(message):
with urllib.request.urlopen(req, timeout=10) as resp: # stalls the loop
...
# WRONG — the poll absorbs the very stall the test means to detect
for _ in range(200):
if parked.is_set():
break
await asyncio.sleep(0.01) # on a blocked loop this just waits out the stall
Right shape¶
Ask what must be true for a second attempt to differ. If nothing changed, the retry is theatre.
# RIGHT — recover the resource before reusing it
except Exception as first_exc:
conn.rollback() # transaction is aborted until this
...
try:
cursor.execute(sql, params)
except Exception as second_exc:
conn.rollback()
raise RuntimeError(...) from second_exc
# RIGHT — dispatch the blocking call off the loop
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, _confirm)
For a non-blocking test, make the deadline the signal: park the worker for
far longer than the probe's timeout, so a blocked loop surfaces as
wait_for raising TimeoutError rather than a slow pass.
Why this matters here¶
Three real instances:
| Site | Failure |
|---|---|
repository.py:171-193 |
Retry on an aborted psycopg3 transaction. The effective_to fallback was unreachable dead code, and a soft column-name mismatch surfaced as a hard 500 on any as_of read. |
channels/ses_webhooks.py:49 |
urlopen(timeout=10) inline in async def. One slow SNS confirmation stalls every concurrent request in the worker, including health checks. |
runtime/social_auth.py:97,127 |
A synchronous Google tokeninfo round-trip inside async def, on every mobile sign-in. |
The repo already had the right pattern at channels/providers/email.py:179;
these two sites were simply never swept.
Why the gates missed them¶
Existing exception gates key on the handler body: silent, trivial, or
single-statement (test_swallow_ratchet, test_no_bare_except_pass). A
twenty-statement handler that re-executes is counted by none of them, and none
models resource state across the except boundary.
test_no_db_retry_without_recovery.py keys on that boundary instead: a retried
cursor.execute with no prior recovery. Recovery after the retry does not
count, and neither does a rollback in a sibling except — the first draft of
that gate made exactly that mistake and passed the un-fixed code.
Related¶
- exceptions-as-control-flow — the same prior in user app code rather than the runtime.