mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-12 04:04:26 +00:00
fix: remove write-lock convoy in getLocal + fix mutateState CAS bug
UniversalCache.getLocal(): when a cached token expires, the RLock fast path (line 385-398) previously fell through to c.mu.Lock() (write lock). Under Yaegi, the write-lock holder takes 10-100ms for LRU manipulation, and Go's RWMutex writer-priority blocks ALL new RLock callers. A single expired-token event turned every concurrent request from read-parallel into write-serialized — the convoy that produced the 737-goroutine pileup at 0x400275a608 (pprof captured at /tmp/traefik-spike-1779663149). Fix: return (nil, false) immediately on expiry for Token/JWK/Session cache types. The periodic cleanup goroutine handles eviction. Write lock is never taken on the read path for these cache types. refreshAttemptTracker.mutateState(): the CAS loop used t.state.CompareAndSwap(t.state.Load(), next) — a second Load that can see a different value from a concurrent writer, silently overwriting their update. Fixed to CompareAndSwap(cur, next) using the snapshot we computed the mutation from.
This commit is contained in:
@@ -498,7 +498,7 @@ func (t *refreshAttemptTracker) mutateState(mutate func(cur *attemptState) *atte
|
|||||||
if next == nil {
|
if next == nil {
|
||||||
return cur
|
return cur
|
||||||
}
|
}
|
||||||
if t.state.CompareAndSwap(t.state.Load(), next) {
|
if t.state.CompareAndSwap(cur, next) {
|
||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-2
@@ -396,8 +396,16 @@ func (c *UniversalCache) getLocal(key string) (interface{}, bool) {
|
|||||||
return value, true
|
return value, true
|
||||||
}
|
}
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
// Expired — fall through to the write-locked slow path below to
|
// Expired — return miss immediately. The periodic cleanup goroutine
|
||||||
// remove the entry under exclusive access.
|
// will evict the stale entry. NEVER fall through to the write-locked
|
||||||
|
// slow path for Token/JWK/Session caches: under Yaegi the write Lock
|
||||||
|
// at line 403 costs 10-100ms per acquisition, and Go's RWMutex
|
||||||
|
// writer-priority semantics block ALL new RLock callers while a Lock
|
||||||
|
// is pending. A single expired-token event turns every concurrent
|
||||||
|
// request from read-parallel into write-serialized — the exact
|
||||||
|
// convoy that produced the 737-goroutine pileup at 0x400275a608.
|
||||||
|
atomic.AddInt64(&c.misses, 1)
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
|
|||||||
Reference in New Issue
Block a user