@@ -108,6 +108,20 @@ impl MemoryPool {
108108 }
109109 }
110110
111+ /// Returns the number of bytes in overdraft (used - capacity). Returns zero if
112+ /// usage is still within capacity.
113+ #[ inline]
114+ pub fn overdraft ( & self ) -> usize {
115+ match & self . inner {
116+ Some ( inner) => {
117+ let capacity = inner. capacity . load ( Ordering :: Relaxed ) ;
118+ let used = inner. used . load ( Ordering :: Relaxed ) ;
119+ used. saturating_sub ( capacity)
120+ }
121+ None => 0 ,
122+ }
123+ }
124+
111125 /// Tries to reserve `size` bytes without waiting.
112126 ///
113127 /// Returns `None` if insufficient capacity.
@@ -172,6 +186,59 @@ impl MemoryPool {
172186 }
173187 }
174188
189+ /// Waits until there's any available budget. There's no guarantees
190+ /// though that by the time the caller is woken up, that the budget
191+ /// will still be available.
192+ pub async fn wait_until_available ( & self ) {
193+ let Some ( inner) = & self . inner else {
194+ return ;
195+ } ;
196+ loop {
197+ let notified = inner. notify . notified ( ) ;
198+ if self . available ( ) > 0 {
199+ break ;
200+ }
201+ notified. await ;
202+ }
203+ }
204+
205+ /// Reserves `size` bytes unconditionally, without checking capacity.
206+ ///
207+ /// The pool may go into overdraft: `used` exceeds `capacity`, `available()`
208+ /// reports 0, and ordinary `try_reserve`/`reserve` callers wait until enough
209+ /// leases are returned to repay the debt. Never waits.
210+ ///
211+ /// # Panics
212+ ///
213+ /// Panics if the total amount of reserved memory would exceed `usize::MAX`.
214+ #[ inline]
215+ pub fn force_reserve ( & self , size : usize ) -> MemoryLease {
216+ if size == 0 {
217+ return MemoryLease {
218+ budget : self . clone ( ) ,
219+ size,
220+ } ;
221+ }
222+ match & self . inner {
223+ Some ( inner) => {
224+ inner
225+ . used
226+ . fetch_update ( Ordering :: Relaxed , Ordering :: Relaxed , |used| {
227+ used. checked_add ( size)
228+ } )
229+ . expect ( "MemoryPool used counter overflowed" ) ;
230+ MemoryLease {
231+ budget : self . clone ( ) ,
232+ size,
233+ }
234+ }
235+ None => MemoryLease {
236+ budget : self . clone ( ) ,
237+ size,
238+ } ,
239+ }
240+ }
241+
175242 #[ inline]
176243 pub fn empty_lease ( & self ) -> MemoryLease {
177244 MemoryLease {
@@ -480,7 +547,9 @@ const _: () = {
480547
481548#[ cfg( test) ]
482549mod tests {
550+ use std:: assert_matches;
483551 use std:: num:: NonZeroUsize ;
552+ use std:: time:: Duration ;
484553
485554 use super :: * ;
486555
@@ -730,4 +799,83 @@ mod tests {
730799 assert_eq ! ( count. load( Ordering :: Relaxed ) , 100 ) ;
731800 assert_eq ! ( budget. used( ) , bytes( 0 ) ) ;
732801 }
802+
803+ #[ tokio:: test( start_paused = true ) ]
804+ async fn force_reserve ( ) {
805+ let budget = budget ( 100 ) ;
806+ let r1 = budget. try_reserve ( 90 ) . expect ( "should succeed" ) ;
807+ assert_eq ! ( r1. size( ) , bytes( 90 ) ) ;
808+ assert_eq ! ( budget. used( ) , bytes( 90 ) ) ;
809+ assert_eq ! ( budget. available( ) , 10 ) ;
810+ assert_eq ! ( budget. overdraft( ) , 0 ) ;
811+
812+ assert_matches ! ( budget. try_reserve( 20 ) , None ) ;
813+ let r2 = budget. force_reserve ( 20 ) ;
814+ assert_eq ! ( r2. size( ) , bytes( 20 ) ) ;
815+ assert_eq ! ( budget. used( ) , bytes( 110 ) ) ;
816+ // available() should still report 0 even though we're in overdraft
817+ assert_eq ! ( budget. available( ) , 0 ) ;
818+ assert_eq ! ( budget. overdraft( ) , 10 ) ;
819+
820+ // Try to reserve anything while in overdraft will fail
821+ assert_matches ! ( budget. try_reserve( 1 ) , None ) ;
822+
823+ let r3 = budget. force_reserve ( 50 ) ;
824+ assert_eq ! ( r3. size( ) , bytes( 50 ) ) ;
825+ assert_eq ! ( budget. used( ) , bytes( 160 ) ) ;
826+ assert_eq ! ( budget. available( ) , 0 ) ;
827+ assert_eq ! ( budget. overdraft( ) , 60 ) ;
828+
829+ let mut waiter1 = std:: pin:: pin!( budget. reserve( 10 ) ) ;
830+ let mut waiter2 = std:: pin:: pin!( budget. wait_until_available( ) ) ;
831+
832+ // Waiters will be blocked
833+ assert ! (
834+ tokio:: time:: timeout( Duration :: from_millis( 100 ) , waiter1. as_mut( ) )
835+ . await
836+ . is_err( )
837+ ) ;
838+ assert ! (
839+ tokio:: time:: timeout( Duration :: from_millis( 100 ) , waiter2. as_mut( ) )
840+ . await
841+ . is_err( )
842+ ) ;
843+
844+ drop ( r3) ;
845+ assert_eq ! ( budget. used( ) , bytes( 110 ) ) ;
846+ assert_eq ! ( budget. available( ) , 0 ) ;
847+ assert_eq ! ( budget. overdraft( ) , 10 ) ;
848+
849+ // Returning capacity while in overdraft won't fullfill waiters
850+ assert ! (
851+ tokio:: time:: timeout( Duration :: from_millis( 100 ) , waiter1. as_mut( ) )
852+ . await
853+ . is_err( )
854+ ) ;
855+ assert ! (
856+ tokio:: time:: timeout( Duration :: from_millis( 100 ) , waiter2. as_mut( ) )
857+ . await
858+ . is_err( )
859+ ) ;
860+
861+ drop ( r2) ;
862+ assert_eq ! ( budget. used( ) , bytes( 90 ) ) ;
863+ assert_eq ! ( budget. available( ) , 10 ) ;
864+ assert_eq ! ( budget. overdraft( ) , 0 ) ;
865+
866+ // Only then will waiters be unblocked
867+ // waiter2 is waiting for available() to be > 0, so it should be
868+ // immediately unblocked.
869+ assert ! (
870+ tokio:: time:: timeout( Duration :: from_millis( 100 ) , waiter2. as_mut( ) )
871+ . await
872+ . is_ok( )
873+ ) ;
874+ // waiter1 is trying to reserve 10 bytes, which it'll be able to acquire
875+ assert ! (
876+ tokio:: time:: timeout( Duration :: from_millis( 100 ) , waiter1. as_mut( ) )
877+ . await
878+ . is_ok( )
879+ ) ;
880+ }
733881}
0 commit comments