@@ -12,6 +12,7 @@ use std::pin::Pin;
1212use std:: sync:: atomic:: { AtomicU64 , Ordering } ;
1313use std:: sync:: { Arc , Mutex } ;
1414use std:: task:: { Poll , Waker } ;
15+ use std:: time:: Instant ;
1516
1617pub use global_alloc:: { Allocator , estimate_memory_usage} ;
1718pub use memory_manager:: memory_manager;
@@ -24,6 +25,8 @@ pub use spill_context::{
2425} ;
2526pub use spill_frame:: SpillFrame ;
2627
28+ use crate :: spill_context:: SpillContextStatistics ;
29+
2730// SpillTokenInner's state
2831const SPILLED_BIT : u64 = 1 ; // Set when value = None and spilled is Some.
2932const DROPPED_BIT : u64 = 2 ; // Set when the token owner has dropped. Forbids creation of new pins / lock.
@@ -32,13 +35,22 @@ const HAS_WAITERS_BIT: u64 = 8; // Only updated while holding waiters lock.
3235const RO_PIN_COUNT_UNIT : u64 = 16 ; // Added to the state for each active pin.
3336const RO_PIN_MASK : u64 = u64:: MAX << 4 ;
3437
38+ enum ValueSlot < T > {
39+ InMemory ( T ) ,
40+ Spilled {
41+ n_bytes : usize ,
42+ ctx_stats : Arc < SpillContextStatistics > ,
43+ } ,
44+ Dropped ,
45+ }
46+
3547struct SpillTokenInner < T : Spillable > {
3648 // May be read if holding LOCK_BIT or a pin, may be written while holding
3749 // LOCK_BIT and no pins exist.
38- value : UnsafeCell < Option < T > > ,
50+ value_slot : UnsafeCell < ValueSlot < T > > ,
3951
4052 // May be read+written while holding LOCK_BIT (irrespective of pins).
41- spilled : UnsafeCell < Option < T :: Spilled > > ,
53+ spilled_value : UnsafeCell < Option < T :: Spilled > > ,
4254
4355 // Lock should not be held for long, only used to register/wake waiters.
4456 waiters : Mutex < UnitVec < Waker > > ,
@@ -192,9 +204,15 @@ impl<T: Spillable> SpillTokenInner<T> {
192204 slf. wake_waiters ( slf. state . fetch_and ( !LOCK_BIT , Ordering :: AcqRel ) ) ;
193205 } ) ;
194206 // TODO: re-register unspilled frame?
195- let spilled = ( * self . spilled . get ( ) ) . as_ref ( ) . unwrap ( ) ;
207+ let unspill_start = Instant :: now ( ) ;
208+ let spilled = ( * self . spilled_value . get ( ) ) . as_ref ( ) . unwrap ( ) ;
196209 let value = T :: unspill ( spilled) . await ;
197- self . value . get ( ) . write ( Some ( value) ) ;
210+ let ValueSlot :: Spilled { n_bytes, ctx_stats } =
211+ self . value_slot . get ( ) . replace ( ValueSlot :: InMemory ( value) )
212+ else {
213+ unreachable ! ( )
214+ } ;
215+ ctx_stats. add_unspill ( n_bytes, unspill_start) ;
198216
199217 WithDrop :: dismiss ( lock_guard) ;
200218 self . wake_waiters (
@@ -222,16 +240,19 @@ impl<T: Spillable> SpillTokenInner<T> {
222240 slf. wake_waiters ( slf. state . fetch_and ( !LOCK_BIT , Ordering :: AcqRel ) ) ;
223241 } ) ;
224242
225- let value = & mut * self . value . get ( ) ;
226- if value . is_none ( ) {
243+ let value_slot = & mut * self . value_slot . get ( ) ;
244+ if let ValueSlot :: Spilled { n_bytes , ctx_stats } = value_slot {
227245 debug_assert ! ( self . state. load( Ordering :: Relaxed ) & SPILLED_BIT == SPILLED_BIT ) ;
228246 // TODO: re-register unspilled frame?
229- let spilled = ( * self . spilled . get ( ) ) . take ( ) . unwrap ( ) ;
230- * value = Some ( T :: unspill ( & spilled) . await ) ;
247+ let unspill_start = Instant :: now ( ) ;
248+ let spilled = ( * self . spilled_value . get ( ) ) . take ( ) . unwrap ( ) ;
249+ let value = T :: unspill ( & spilled) . await ;
250+ ctx_stats. add_unspill ( * n_bytes, unspill_start) ;
251+ * value_slot = ValueSlot :: InMemory ( value) ;
231252 self . state . fetch_sub ( SPILLED_BIT , Ordering :: Relaxed ) ;
232253 } else {
233254 // We are about to mutate, making our current spill invalid.
234- * self . spilled . get ( ) = None ;
255+ * self . spilled_value . get ( ) = None ;
235256 }
236257
237258 WithDrop :: dismiss ( lock_guard) ;
@@ -264,8 +285,8 @@ impl<T: Spillable> SpillTokenInner<T> {
264285 // allowing us to clean up here.
265286 let old_state = self . state . fetch_or ( DROPPED_BIT , Ordering :: Acquire ) ;
266287 if old_state & ( LOCK_BIT | RO_PIN_MASK ) == 0 {
267- self . value . get ( ) . replace ( None ) ;
268- self . spilled . get ( ) . replace ( None ) ;
288+ self . value_slot . get ( ) . replace ( ValueSlot :: Dropped ) ;
289+ self . spilled_value . get ( ) . replace ( None ) ;
269290 }
270291 self . registration_id . fetch_add ( 1 , Ordering :: Relaxed ) ;
271292 }
@@ -294,7 +315,10 @@ trait DynSpillToken: Send + Sync + 'static {
294315 /// May return false if the token is already spilled, or is currently pinned.
295316 /// It may also return None in those cases (to avoid the allocation of the future).
296317 #[ expect( unused) ]
297- fn try_spill ( & self ) -> Option < Pin < Box < dyn Future < Output = bool > + Send + ' _ > > > ;
318+ fn try_spill (
319+ & self ,
320+ stats : & Arc < SpillContextStatistics > ,
321+ ) -> Option < Pin < Box < dyn Future < Output = bool > + Send + ' _ > > > ;
298322}
299323
300324impl < T : Spillable > DynSpillToken for SpillTokenInner < T > {
@@ -315,7 +339,10 @@ impl<T: Spillable> DynSpillToken for SpillTokenInner<T> {
315339 self . try_pin ( ) . map ( |p| p. estimate_byte_size ( ) )
316340 }
317341
318- fn try_spill ( & self ) -> Option < Pin < Box < dyn Future < Output = bool > + Send + ' _ > > > {
342+ fn try_spill (
343+ & self ,
344+ stats : & Arc < SpillContextStatistics > ,
345+ ) -> Option < Pin < Box < dyn Future < Output = bool > + Send + ' _ > > > {
319346 // First, we try setting the lock bit. If anyone else has a pin we don't bother.
320347 let pin_update = self
321348 . state
@@ -329,8 +356,10 @@ impl<T: Spillable> DynSpillToken for SpillTokenInner<T> {
329356 return None ;
330357 }
331358
332- Some ( Box :: pin ( async {
333- let needs_spill = unsafe { ( * self . spilled . get ( ) ) . is_none ( ) } ;
359+ let stats = Arc :: clone ( stats) ;
360+ Some ( Box :: pin ( async move {
361+ let spill_start = Instant :: now ( ) ;
362+ let needs_spill = unsafe { ( * self . spilled_value . get ( ) ) . is_none ( ) } ;
334363 let is_exclusive = if needs_spill {
335364 // Relax the lock to a pin while spilling such that new pins can
336365 // come in. After all, we still have it in memory right now.
@@ -347,7 +376,7 @@ impl<T: Spillable> DynSpillToken for SpillTokenInner<T> {
347376 . state
348377 . fetch_add ( LOCK_BIT . wrapping_sub ( RO_PIN_COUNT_UNIT ) , Ordering :: Acquire ) ;
349378 unsafe {
350- self . spilled . get ( ) . write ( Some ( spilled) ) ;
379+ self . spilled_value . get ( ) . write ( Some ( spilled) ) ;
351380 }
352381 old_state & RO_PIN_MASK == RO_PIN_COUNT_UNIT
353382 } else {
@@ -357,10 +386,21 @@ impl<T: Spillable> DynSpillToken for SpillTokenInner<T> {
357386 let state = if is_exclusive {
358387 // We are the only pin and hold the lock meaning no one else can
359388 // access value or create new pins.
360- unsafe { self . value . get ( ) . replace ( None ) } ;
389+ let n_bytes = match unsafe { & * self . value_slot . get ( ) } {
390+ ValueSlot :: InMemory ( val) => val. estimate_byte_size ( ) ,
391+ _ => unreachable ! ( ) ,
392+ } ;
393+ unsafe {
394+ self . value_slot . get ( ) . replace ( ValueSlot :: Spilled {
395+ n_bytes,
396+ ctx_stats : stats. clone ( ) ,
397+ } )
398+ } ;
399+ stats. add_successful_spill ( n_bytes, spill_start) ;
361400 self . state
362401 . fetch_add ( SPILLED_BIT . wrapping_sub ( LOCK_BIT ) , Ordering :: AcqRel )
363402 } else {
403+ stats. add_failed_spill ( spill_start) ;
364404 self . state . fetch_sub ( LOCK_BIT , Ordering :: AcqRel )
365405 } ;
366406 self . wake_waiters ( state) ;
@@ -379,8 +419,8 @@ impl<T: Spillable> SpillToken<T> {
379419 /// Creates a new SpillToken containing the given value.
380420 pub fn new ( value : T ) -> Self {
381421 let inner = Arc :: new ( SpillTokenInner {
382- value : UnsafeCell :: new ( Some ( value) ) ,
383- spilled : UnsafeCell :: new ( None ) ,
422+ value_slot : UnsafeCell :: new ( ValueSlot :: InMemory ( value) ) ,
423+ spilled_value : UnsafeCell :: new ( None ) ,
384424 registration_id : AtomicU64 :: new ( 0 ) ,
385425 state : AtomicU64 :: new ( 0 ) ,
386426 waiters : Mutex :: default ( ) ,
@@ -416,13 +456,21 @@ impl<T: Spillable> SpillToken<T> {
416456 /// Consumes this SpillToken, unspilling it if it were spilled.
417457 pub async fn into_inner ( mut self ) -> T {
418458 let pin = self . get_mut ( ) . await ;
419- unsafe { & mut * pin. inner . value . get ( ) } . take ( ) . unwrap ( )
459+ let slot = unsafe { pin. inner . value_slot . get ( ) . replace ( ValueSlot :: Dropped ) } ;
460+ let ValueSlot :: InMemory ( value) = slot else {
461+ unreachable ! ( )
462+ } ;
463+ value
420464 }
421465
422466 /// Blocking version of into_inner.
423467 pub fn into_inner_blocking ( mut self ) -> T {
424468 let pin = self . get_mut_blocking ( ) ;
425- unsafe { & mut * pin. inner . value . get ( ) } . take ( ) . unwrap ( )
469+ let slot = unsafe { pin. inner . value_slot . get ( ) . replace ( ValueSlot :: Dropped ) } ;
470+ let ValueSlot :: InMemory ( value) = slot else {
471+ unreachable ! ( )
472+ } ;
473+ value
426474 }
427475}
428476
@@ -440,7 +488,11 @@ impl<'a, T: Spillable> Deref for PinnedRef<'a, T> {
440488 type Target = T ;
441489
442490 fn deref ( & self ) -> & Self :: Target {
443- unsafe { & * self . inner . value . get ( ) } . as_ref ( ) . unwrap ( )
491+ let slot = unsafe { & * self . inner . value_slot . get ( ) } ;
492+ let ValueSlot :: InMemory ( value) = slot else {
493+ unreachable ! ( )
494+ } ;
495+ value
444496 }
445497}
446498
@@ -458,13 +510,21 @@ impl<'a, T: Spillable> Deref for PinnedMut<'a, T> {
458510 type Target = T ;
459511
460512 fn deref ( & self ) -> & Self :: Target {
461- unsafe { & * self . inner . value . get ( ) } . as_ref ( ) . unwrap ( )
513+ let slot = unsafe { & * self . inner . value_slot . get ( ) } ;
514+ let ValueSlot :: InMemory ( value) = slot else {
515+ unreachable ! ( )
516+ } ;
517+ value
462518 }
463519}
464520
465521impl < ' a , T : Spillable > DerefMut for PinnedMut < ' a , T > {
466522 fn deref_mut ( & mut self ) -> & mut Self :: Target {
467- unsafe { & mut * self . inner . value . get ( ) } . as_mut ( ) . unwrap ( )
523+ let slot = unsafe { & mut * self . inner . value_slot . get ( ) } ;
524+ let ValueSlot :: InMemory ( value) = slot else {
525+ unreachable ! ( )
526+ } ;
527+ value
468528 }
469529}
470530
0 commit comments