@@ -188,6 +188,47 @@ fn partially_mapped() {
188188 assert ! ( result. is_err( ) ) ;
189189}
190190
191+ /// Ensure that `map_async` calls its callback even when the buffer is invalid.
192+ ///
193+ /// Regression test: when `buffer_map_async` failed because the buffer id referred to
194+ /// an invalid buffer (one whose creation had failed), it dropped `op` via `?` without
195+ /// calling its callback, violating the documented guarantee that the callback is always
196+ /// called.
197+ #[ test]
198+ fn map_async_on_invalid_buffer_calls_callback ( ) {
199+ let ( device, _queue) = wgpu:: Device :: noop ( & wgpu:: DeviceDescriptor :: default ( ) ) ;
200+
201+ // MAP_READ | MAP_WRITE is an invalid usage combination, so create_buffer
202+ // will fail and the returned buffer will be invalid. Capture the error so
203+ // the default (panic) handler is not reached.
204+ let _creation_error_scope = device. push_error_scope ( wgpu:: ErrorFilter :: Validation ) ;
205+ let buffer = device. create_buffer ( & wgpu:: BufferDescriptor {
206+ label : Some ( "invalid" ) ,
207+ size : 4 ,
208+ usage : wgpu:: BufferUsages :: MAP_READ | wgpu:: BufferUsages :: MAP_WRITE ,
209+ mapped_at_creation : false ,
210+ } ) ;
211+ drop ( _creation_error_scope) ;
212+
213+ // `map_async` on an invalid buffer should fire the callback with an error.
214+ // Also capture the Err that wgpu-core returns to wgpu's map_async layer, which
215+ // wgpu forwards to the error sink regardless of whether it called the callback.
216+ let callback_called = std:: sync:: Arc :: new ( std:: sync:: atomic:: AtomicBool :: new ( false ) ) ;
217+ let callback_called2 = callback_called. clone ( ) ;
218+ let _map_error_scope = device. push_error_scope ( wgpu:: ErrorFilter :: Validation ) ;
219+ buffer. map_async ( wgpu:: MapMode :: Read , .., move |result| {
220+ assert ! ( result. is_err( ) , "expected an error for an invalid buffer" ) ;
221+ callback_called2. store ( true , std:: sync:: atomic:: Ordering :: SeqCst ) ;
222+ } ) ;
223+ device. poll ( wgpu:: PollType :: wait_indefinitely ( ) ) . unwrap ( ) ;
224+ drop ( _map_error_scope) ;
225+
226+ assert ! (
227+ callback_called. load( std:: sync:: atomic:: Ordering :: SeqCst ) ,
228+ "map_async callback was not called for an invalid buffer"
229+ ) ;
230+ }
231+
191232/// Ensure that you cannot unmap a buffer while there are still accessible mapped views.
192233#[ test]
193234#[ should_panic( expected = "You cannot unmap a buffer that still has accessible mapped views" ) ]
0 commit comments