cubemaster: clamp the gRPC pool refcount in the stored value, not a local copy - #1475
cubemaster: clamp the gRPC pool refcount in the stored value, not a local copy#1475dwin-gharibi wants to merge 1 commit into
Conversation
…l refcount clamp Signed-off-by: Dwin Gharibi <dwin.gharibi@email.kntu.ac.ir>
| for { | ||
| cur := atomic.LoadInt32(&p.ref) | ||
| if cur <= 0 { | ||
| break |
There was a problem hiding this comment.
Minor behavior change worth a thought: when p.ref is already 0, this loop breaks at cur <= 0 with newRef still at its zero value — so the idle-trim block below (newRef == 0 && atomic.LoadInt32(&p.current) > MaxIdle) fires even though no decrement actually occurred. Under the old code the stored value went to −1 in that case, so the inner atomic.LoadInt32(&p.ref) == 0 guard always blocked the trim on an unbalanced release.
The result is benign today (the trim is idempotent and only proceeds when p.ref is genuinely 0, and the redundant-trim window with current > MaxIdle is tiny), but the code can no longer distinguish "decremented to zero" from "was already zero." If that distinction ever matters, track success with a bool (e.g. decremented := false) and only run the trim branch when a decrement really happened.
Review: cubemaster: clamp the gRPC pool refcount in the stored value, not a local copy (#1475)AI-generated review — verified against the base-branch workspace. Not a human approval. VerdictThe PR fixes a real bug. In the old code both No correctness bug found in the new code itself. Three non-blocking items below. Findings1. [Medium] The identical bug is left unfixed in the Cubelet copy 2. [Low] 3. [Low] Test gap: no test exercises the idle-trim path Minor notes
|
Closes #1474.
Motivation
Both
incrRefanddecrRefclamped a localint32after the atomic add, so neither clamp hadany effect on
p.ref:staticcheckflags the increment side asSA4003. The consequential half is the decrement: a singleunbalanced
decrRefdrives the stored counter to-1, and from then on theatomic.LoadInt32(&p.ref) == 0guard insidedecrRefnever holds again — sodeleteFrom(MaxIdle)isnever reached and idle connections above
MaxIdleare never reclaimed. A silent connection leak.What this changes
CubeMaster/pkg/base/grpc-middleware/pool/pool.go— both operations become compare-and-swap loops sothe bound is enforced on the stored value:
incrRefsaturates atmath.MaxInt32instead of wrapping negative.decrRefstops at0instead of going negative, which keeps the idle-trim branch reachable.decrRef's existing idle-trim block is unchanged; it now sees a counter that can actually reach0.No comment changes.
Testing
New:
CubeMaster/pkg/base/grpc-middleware/pool/pool_refcount_test.goTestIncrRefDoesNotOverflowPastMaxInt32— asserts the stored value saturates.TestIncrRefIncrementsNormally— normal path unchanged.TestDecrRefDoesNotGoNegative— repeated unbalanced releases leave the stored value at0.TestDecrRefDecrementsNormally— normal path unchanged.TestRefCountIsBalancedUnderConcurrency— 64 goroutines × 500 balanced incr/decr pairs end at0,which exercises the CAS retry loops.
Red/green verified — with
pool.goreverted to master:and with the fix:
Also clean under the race detector:
CI gates checked locally:
gofmt -lon the package — clean (fmt-check).GOOS=linux go buildon the package — clean.staticcheck -checks 'SA*'— theSA4003finding is gone; the remainingSA1019hits arepre-existing
grpc.DialContext/WithInsecure/WithBlockdeprecations inoptions.go, nottouched here.