From d9010481c9d9209e4f83ca9ba7e1aaae6b3caa56 Mon Sep 17 00:00:00 2001 From: "William T. Nelson" <35801+wtn@users.noreply.github.com> Date: Thu, 12 Mar 2026 21:42:00 -0500 Subject: [PATCH] Avoid draining pool in gardener cleanup. Co-authored-by: Claude --- lib/async/pool/controller.rb | 8 +++++++- test/async/pool/controller.rb | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/async/pool/controller.rb b/lib/async/pool/controller.rb index fc2bea2..3ed1d43 100644 --- a/lib/async/pool/controller.rb +++ b/lib/async/pool/controller.rb @@ -260,7 +260,13 @@ def start_gardener end ensure @gardener = nil - self.close + + # During cancellation, busy resources will never be released, + # so force-retire everything instead of draining gracefully. + while (resource, _usage = @resources.first) + retire(resource) + end + @available.clear end end diff --git a/test/async/pool/controller.rb b/test/async/pool/controller.rb index 979bde0..d52adf4 100644 --- a/test/async/pool/controller.rb +++ b/test/async/pool/controller.rb @@ -301,6 +301,26 @@ expect(events).to be == [:acquire, :close, :release, :closed] end + + it "does not deadlock when Sync scope exits with unreleased resources" do + thread = Thread.new do + Sync do + policy = proc{|pool| pool.prune(0)} + pool = subject.new(Async::Pool::Resource, policy: policy) + + # Acquire a resource (starts the gardener) but don't release it + pool.acquire + + # Let gardener start and enter its wait loop + sleep 0.05 + end + end + + result = thread.join(5) + thread.kill if result.nil? + + expect(result).not.to be_nil + end end with "#to_s" do