mutated × shared — the answer changes between runs
Pool-Exhaustion Heisenbug
Whether a request succeeds or stalls depends on how many others hold the pool right now -- so the same call passes alone and fails under load.
01the recipe
In the wild
compound ofResource ContentionCWE-400 Resource ExhaustionTime, Money & EntropycompoundCWE-362 Race Condition
example.py
# SMELL: a short pool + a per-call timeout -> success depends on live load.
# (resource contention x time/money/entropy)
pool = ConnectionPool(size=4)
def handler(req):
conn = pool.acquire(timeout=0.2) # under load: sometimes returns None
return conn.query(req.sql) # ...then AttributeError, only sometimes
# RIGHT: size for peak, fail explicitly, never act on a missed acquire.
def handler(req):
conn = pool.acquire(timeout=2.0)
if conn is None:
raise ServiceBusy() # deterministic, observable backpressure
try:
return conn.query(req.sql)
finally:
pool.release(conn)With a pool smaller than peak concurrency, whether you get a connection depends on how many requests happen to overlap with yours -- pure timing. It passes alone and fails only in the demo under load. Size for peak and turn a missed acquire into explicit backpressure, not a silent None.
// observed
contended: intermittent None -> AttributeError under concurrency right: explicit ServiceBusy; outcome no longer depends on who else is in flight
02weakness catalog
Mapped weaknesses (CWE)
On its own, this defect is catalogued by MITRE as one or more of these weaknesses. The exploitable vulnerability usually appears only when it chains or combines with another.