diff --git a/asyncband/src/pool/bounded.rs b/asyncband/src/pool/bounded.rs index c6309911..9b3e2fd5 100644 --- a/asyncband/src/pool/bounded.rs +++ b/asyncband/src/pool/bounded.rs @@ -293,8 +293,7 @@ impl Pool { Some(object) => { let mut unready_object = UnreadyObject { state: Some(object), - pool: Arc::downgrade(self), - recycle_cancelled_strategy: self.config.recycle_cancelled_strategy, + pool: self, }; let state = unready_object.state(); @@ -556,34 +555,32 @@ impl Object { /// If the check fails, `detach()` should be called to permanently remove the object /// from the pool. If dropped without calling either method (due to being cancelled), /// the behavior depends on the pool's [`RecycleCancelledStrategy`] configuration. -struct UnreadyObject { +struct UnreadyObject<'a, M: ManageObject> { state: Option>, - pool: Weak>, - recycle_cancelled_strategy: RecycleCancelledStrategy, + // The enclosing `get` call keeps the pool alive through cancellation cleanup. + pool: &'a Arc>, } -impl Drop for UnreadyObject { +impl Drop for UnreadyObject<'_, M> { fn drop(&mut self) { if let Some(mut state) = self.state.take() { - if let Some(pool) = self.pool.upgrade() { - match self.recycle_cancelled_strategy { - RecycleCancelledStrategy::Detach => { - pool.detach_object(&mut state.o); - } - RecycleCancelledStrategy::ReturnToPool => { - pool.restore_idle(state); - } + match self.pool.config.recycle_cancelled_strategy { + RecycleCancelledStrategy::Detach => { + self.pool.detach_object(&mut state.o); + } + RecycleCancelledStrategy::ReturnToPool => { + self.pool.restore_idle(state); } } } } } -impl UnreadyObject { +impl UnreadyObject<'_, M> { fn ready(mut self, permit: OwnedSemaphorePermit) -> Object { // INVARIANT: `state` is `Some` until this object becomes ready, detaches, or is dropped. let state = Some(self.state.take().unwrap()); - let pool = self.pool.clone(); + let pool = Arc::downgrade(self.pool); Object { state, permit, @@ -593,9 +590,7 @@ impl UnreadyObject { fn detach(&mut self) { if let Some(mut state) = self.state.take() { - if let Some(pool) = self.pool.upgrade() { - pool.detach_object(&mut state.o); - } + self.pool.detach_object(&mut state.o); } } diff --git a/asyncband/src/pool/unbounded.rs b/asyncband/src/pool/unbounded.rs index ac0baffb..6f7337ce 100644 --- a/asyncband/src/pool/unbounded.rs +++ b/asyncband/src/pool/unbounded.rs @@ -342,8 +342,7 @@ impl> Pool { Some(object) => { let mut unready_object = UnreadyObject { state: Some(object), - pool: Arc::downgrade(self), - recycle_cancelled_strategy: self.config.recycle_cancelled_strategy, + pool: self, }; let state = unready_object.state(); @@ -571,46 +570,42 @@ impl> Object { /// If the check fails, `detach()` should be called to permanently remove the object /// from the pool. If dropped without calling either method (due to being cancelled), /// the behavior depends on the pool's [`RecycleCancelledStrategy`] configuration. -struct UnreadyObject> { - state: Option>, - pool: Weak>, - recycle_cancelled_strategy: RecycleCancelledStrategy, +struct UnreadyObject<'a, M: ManageObject> { + state: Option>, + // The enclosing `get` call keeps the pool alive through cancellation cleanup. + pool: &'a Arc>, } -impl> Drop for UnreadyObject { +impl Drop for UnreadyObject<'_, M> { fn drop(&mut self) { if let Some(mut state) = self.state.take() { - if let Some(pool) = self.pool.upgrade() { - match self.recycle_cancelled_strategy { - RecycleCancelledStrategy::Detach => { - pool.detach_object(&mut state.o); - } - RecycleCancelledStrategy::ReturnToPool => { - pool.restore_idle(state); - } + match self.pool.config.recycle_cancelled_strategy { + RecycleCancelledStrategy::Detach => { + self.pool.detach_object(&mut state.o); + } + RecycleCancelledStrategy::ReturnToPool => { + self.pool.restore_idle(state); } } } } } -impl> UnreadyObject { - fn ready(mut self) -> Object { +impl UnreadyObject<'_, M> { + fn ready(mut self) -> Object { // INVARIANT: `state` is `Some` until this object becomes ready, detaches, or is dropped. let state = Some(self.state.take().unwrap()); - let pool = self.pool.clone(); + let pool = Arc::downgrade(self.pool); Object { state, pool } } fn detach(&mut self) { if let Some(mut state) = self.state.take() { - if let Some(pool) = self.pool.upgrade() { - pool.detach_object(&mut state.o); - } + self.pool.detach_object(&mut state.o); } } - fn state(&mut self) -> &mut ObjectState { + fn state(&mut self) -> &mut ObjectState { // INVARIANT: `state` is `Some` until this object becomes ready, detaches, or is dropped. self.state.as_mut().unwrap() } diff --git a/tests-integration/tests/pool_test/recycle.rs b/tests-integration/tests/pool_test/recycle.rs index 03ff53ef..d877cc61 100644 --- a/tests-integration/tests/pool_test/recycle.rs +++ b/tests-integration/tests/pool_test/recycle.rs @@ -113,6 +113,28 @@ macro_rules! recycle_contract { } } + #[test] + fn recycled_object_can_outlive_the_pool() { + for detach in [false, true] { + let manager = Manager::default(); + let pool = pool(1, manager.clone(), None); + drop(ready(pool.get()).unwrap()); + let object = ready(pool.get()).unwrap(); + assert_eq!(object.status().recycle_count(), 1); + + let weak_pool = Arc::downgrade(&pool); + drop(pool); + assert!(weak_pool.upgrade().is_none()); + assert_eq!(*object, 0); + if detach { + assert_eq!(object.detach(), 0); + } else { + drop(object); + } + assert!(manager.detached().is_empty()); + } + } + #[test] fn cancelling_one_validation_leaves_other_idle_objects_available() { let manager = Manager::default();