@@ -438,7 +438,16 @@ pub type BufferAccessResult = Result<(), BufferAccessError>;
438438pub ( crate ) struct BufferPendingMapping {
439439 pub ( crate ) range : Range < wgt:: BufferAddress > ,
440440 pub ( crate ) op : BufferMapOperation ,
441- // hold the parent alive while the mapping is active
441+
442+ /// A strong reference to the parent buffer, to hold it alive
443+ /// while the mapping is pending.
444+ ///
445+ /// This creates a temporary reference cycle: [`Buffer::map_state`] owns
446+ /// this `BufferPendingMapping`, which in turn holds an `Arc<Buffer>` back
447+ /// to the same buffer. The cycle is intentional — it keeps the buffer alive
448+ /// while it sits in `LifetimeTracker::ready_to_map` with no other owner —
449+ /// and is broken by `Buffer::map` when it moves the `BufferPendingMapping`
450+ /// out of [`Buffer::map_state`].
442451 pub ( crate ) _parent_buffer : Arc < Buffer > ,
443452}
444453
@@ -716,6 +725,12 @@ impl Buffer {
716725 }
717726
718727 if let Some ( queue) = device. get_queue ( ) . as_ref ( ) {
728+ // Flush pending writes to this buffer before scheduling the map.
729+ //
730+ // Such writes get added to `queue.life_tracker.lock().active`, so
731+ // that `lock_life().map` below will find the buffer in that
732+ // submission and wait for it, rather than placing it in
733+ // `ready_to_map` prematurely.
719734 match queue. flush_writes_for_buffer ( self , snatch_guard) {
720735 Err ( err) => {
721736 let state = mem:: replace ( & mut * self . map_state . lock ( ) , BufferMapState :: Idle ) ;
@@ -725,7 +740,7 @@ impl Buffer {
725740 return Err ( ( op, err) ) ;
726741 }
727742 Ok ( ( ) ) => {
728- // Schedule the buffer map in the lifetime tracker.
743+ // Schedule the buffer map in the lifetime tracker.
729744 //
730745 // This call searches for use of the buffer by pending submissions.
731746 // If we just flushed pending writes, that search is redundant; we
0 commit comments