KeyPool.take can lose a pooled resource entirely if the caller is cancelled at the wrong moment. The resource is neither destroyed nor returned to the pool, so whatever it wraps leaks.
The resource comes out of the pool map here:
optR <- Resource.eval(kp.kpVar.modify(go))
releasedState <- Resource.eval(Ref[F].of[Reusable](kp.kpDefaultReuseState))
resource <- Resource.makeFull[F, (B, F[Unit])] { poll => ... }
Resource.fold interprets Eval as a plain fa.flatMap(...), no bracket and no mask, and all of this sits in the permit's polled use position. So between modify(go) committing and makeFull entering its uncancelable region, the resource exists only in a flatMap continuation. Cancel there and it's gone.
The annoying part is that nothing looks broken afterwards. The permit is released correctly and the idle count is decremented correctly, so pool state stays consistent. You just lose the resource without the destroy hook ever running, which is why this shows up as leaked file descriptors with nothing in the pool metrics to explain it.
Repro: https://gist.github.com/stasimus/3d9b0d6eedcae885b410cac0a7e040dd#file-takecancelspec-scala
Not reachable under TestControl. take is straight-line between the modify and the bracket, so a deterministic single-threaded scheduler never interleaves a cancel there. 500 replications, zero hits.
KeyPool.takecan lose a pooled resource entirely if the caller is cancelled at the wrong moment. The resource is neither destroyed nor returned to the pool, so whatever it wraps leaks.The resource comes out of the pool map here:
Resource.foldinterpretsEvalas a plainfa.flatMap(...), no bracket and no mask, and all of this sits in the permit's polleduseposition. So betweenmodify(go)committing andmakeFullentering its uncancelable region, the resource exists only in a flatMap continuation. Cancel there and it's gone.The annoying part is that nothing looks broken afterwards. The permit is released correctly and the idle count is decremented correctly, so pool state stays consistent. You just lose the resource without the destroy hook ever running, which is why this shows up as leaked file descriptors with nothing in the pool metrics to explain it.
Repro: https://gist.github.com/stasimus/3d9b0d6eedcae885b410cac0a7e040dd#file-takecancelspec-scala
Not reachable under
TestControl.takeis straight-line between the modify and the bracket, so a deterministic single-threaded scheduler never interleaves a cancel there. 500 replications, zero hits.