You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(metrics): _Accumulator.observe() has no lock, causing lost increments under concurrent async tasks #253
_Accumulator.observe() in metrics.py performs a read-modify-write on plain Python int/float fields with no synchronisation. While ProductMetrics.record_routing() acquires the outer _lock, the observe() call within is not atomic under asyncio + thread-pool workers — two concurrent observers can read the same counter value, both increment it, and write back the same result, silently losing one observation.
Root Cause
backend/app/productization/metrics.py around line 38:
class_Accumulator:
count: int=0total: float=0.0defobserve(self, value: float) ->None:
self.count+=1# ← not atomicself.total+=value# ← not atomic
ProductMetrics._lock guards the record_routing() method body, but if multiple tasks call record_routing() truly concurrently from different thread-pool slots, and CPython's GIL is released between the LOAD_FAST and STORE_FAST bytecodes for self.count += 1, a race occurs. Under high concurrency with uvicorn workers the effect compounds.
Failure Scenario
1,000 concurrent routing requests all call record_routing().
Two workers simultaneously read self.routing_count=500, both write 501.
One increment is silently lost. Repeated thousands of times.
/metrics reports rag_routing_total=850 instead of 1000.
Activity
piyushmakhija28 commentedon Jun 21, 2026
Fixed in PR #280 (Sprint R3/R4: fix(sprint-r3): root-cause fixes for all 39 bugs from code review round 3 + Sprint R4 round 4)