@@ -190,20 +190,39 @@ impl<M: ManageObject> Pool<M> {
190190 ///
191191 /// The pool reserves only capacity that is immediately available and never waits for
192192 /// checked-out objects. Existing idle objects count toward the target, and the pool's
193- /// maximum size is never exceeded. Concurrent calls and checkouts can change the observed
194- /// idle count while this method is running, so the target is best effort rather than a
195- /// postcondition.
193+ /// maximum size is never exceeded. Targets above the maximum size are treated as the maximum.
194+ /// Concurrent calls and checkouts can change the observed idle count while this method is
195+ /// running, so the target is best effort rather than a postcondition.
196196 ///
197197 /// Returns the number of objects created. If [`ManageObject::create`] fails, objects created by
198198 /// this call before the failure remain in the pool and the error is returned.
199199 pub async fn replenish_to ( & self , target_idle : usize ) -> Result < usize , M :: Error > {
200- let Some ( mut permit) = self . permits . clone ( ) . try_acquire_up_to_owned ( target_idle) else {
200+ let target_idle = target_idle. min ( self . config . max_size ) ;
201+ let Some ( mut reservation) = ReplenishReservation :: reserve_up_to ( & self . permits , target_idle)
202+ else {
201203 return Ok ( 0 ) ;
202204 } ;
203205
204- let idle_count = self . slots . lock ( ) . idle_count ( ) ;
205- let to_create = target_idle. saturating_sub ( idle_count) . min ( permit. permits ( ) ) ;
206- permit. release ( permit. permits ( ) - to_create) ;
206+ let ( idle_count, available_slots) = {
207+ let slots = self . slots . lock ( ) ;
208+ let idle_count = slots. idle_count ( ) ;
209+
210+ // Idle objects occupy pool slots without holding permits. Available permits plus this
211+ // reservation represent capacity not committed to other checkouts, creations, or
212+ // replenishments; subtracting idle objects leaves the slots this call may create.
213+ let uncommitted_capacity = self
214+ . permits
215+ . available_permits ( )
216+ . checked_add ( reservation. permits ( ) )
217+ . expect ( "invariant broken: semaphore capacity must not overflow" ) ;
218+ let available_slots = uncommitted_capacity. saturating_sub ( idle_count) ;
219+ ( idle_count, available_slots)
220+ } ;
221+ let to_create = target_idle
222+ . saturating_sub ( idle_count)
223+ . min ( reservation. permits ( ) )
224+ . min ( available_slots) ;
225+ reservation. release ( reservation. permits ( ) - to_create) ;
207226
208227 let mut replenished = 0 ;
209228 for _ in 0 ..to_create {
@@ -213,7 +232,7 @@ impl<M: ManageObject> Pool<M> {
213232 slots. add_idle ( ObjectState :: new ( object) ) ;
214233 }
215234 replenished += 1 ;
216- permit . release ( 1 ) ;
235+ reservation . release ( 1 ) ;
217236 }
218237
219238 Ok ( replenished)
@@ -354,6 +373,40 @@ impl<M: ManageObject> Pool<M> {
354373 }
355374}
356375
376+ // Temporarily removes capacity while `replenish_to` creates objects. Idle objects do not consume
377+ // semaphore permits, so successful insertions release their reservation. Dropping the guard
378+ // restores any unfinished capacity after an error or cancellation.
379+ struct ReplenishReservation < ' a > {
380+ semaphore : & ' a Semaphore ,
381+ permits : usize ,
382+ }
383+
384+ impl < ' a > ReplenishReservation < ' a > {
385+ fn reserve_up_to ( semaphore : & ' a Semaphore , up_to : usize ) -> Option < Self > {
386+ let permits = semaphore. drain_permits ( up_to) ;
387+ ( permits != 0 ) . then_some ( Self { semaphore, permits } )
388+ }
389+
390+ fn permits ( & self ) -> usize {
391+ self . permits
392+ }
393+
394+ fn release ( & mut self , permits : usize ) {
395+ assert ! (
396+ permits <= self . permits,
397+ "cannot release more permits than this reservation holds"
398+ ) ;
399+ self . permits -= permits;
400+ self . semaphore . release ( permits) ;
401+ }
402+ }
403+
404+ impl Drop for ReplenishReservation < ' _ > {
405+ fn drop ( & mut self ) {
406+ self . semaphore . release ( self . permits ) ;
407+ }
408+ }
409+
357410/// A wrapper of the actual pooled object.
358411///
359412/// This object implements [`Deref`] and [`DerefMut`]. You can use it as if it was of type
0 commit comments