@@ -435,8 +435,15 @@ impl AudioContext {
435435 /// * The audio device is not available
436436 /// * For a `BackendSpecificError`
437437 pub async fn suspend ( & self ) {
438+ // Don't lock the backend manager because we can't hold is across the await point
438439 log:: debug!( "Suspend called" ) ;
439- // First, pause rendering via a control message
440+
441+ if self . state ( ) != AudioContextState :: Running {
442+ log:: debug!( "Suspend no-op - context is not running" ) ;
443+ return ;
444+ }
445+
446+ // Pause rendering via a control message
440447 let ( sender, receiver) = oneshot:: channel ( ) ;
441448 let notify = OneshotNotify :: Async ( sender) ;
442449 self . base
@@ -450,6 +457,7 @@ impl AudioContext {
450457 // Then ask the audio host to suspend the stream
451458 log:: debug!( "Suspended audio graph. Suspending audio stream.." ) ;
452459 self . backend_manager . lock ( ) . unwrap ( ) . suspend ( ) ;
460+
453461 log:: debug!( "Suspended audio stream" ) ;
454462 }
455463
@@ -462,10 +470,19 @@ impl AudioContext {
462470 ///
463471 /// * The audio device is not available
464472 /// * For a `BackendSpecificError`
473+ #[ allow( clippy:: await_holding_lock) ] // false positive due to explicit drop
465474 pub async fn resume ( & self ) {
466- log:: debug!( "Resume called" ) ;
467- // First ask the audio host to resume the stream
468- self . backend_manager . lock ( ) . unwrap ( ) . resume ( ) ;
475+ // Lock the backend manager mutex to avoid concurrent calls
476+ log:: debug!( "Resume called, locking backend manager" ) ;
477+ let backend_manager_guard = self . backend_manager . lock ( ) . unwrap ( ) ;
478+
479+ if self . state ( ) != AudioContextState :: Suspended {
480+ log:: debug!( "Resume no-op - context is not suspended" ) ;
481+ return ;
482+ }
483+
484+ // Ask the audio host to resume the stream
485+ backend_manager_guard. resume ( ) ;
469486
470487 // Then, ask to resume rendering via a control message
471488 log:: debug!( "Resumed audio stream, waking audio graph" ) ;
@@ -474,6 +491,9 @@ impl AudioContext {
474491 self . base
475492 . send_control_msg ( ControlMessage :: Resume { notify } ) ;
476493
494+ // Drop the Mutex guard so we won't hold it across an await
495+ drop ( backend_manager_guard) ;
496+
477497 // Wait for the render thread to have processed the resume message
478498 // The AudioContextState will be updated by the render thread.
479499 receiver. await . unwrap ( ) ;
@@ -489,17 +509,28 @@ impl AudioContext {
489509 ///
490510 /// Will panic when this function is called multiple times
491511 pub async fn close ( & self ) {
512+ // Don't lock the backend manager because we can't hold is across the await point
492513 log:: debug!( "Close called" ) ;
493514
494- // First, stop rendering via a control message
495- let ( sender , receiver ) = oneshot :: channel ( ) ;
496- let notify = OneshotNotify :: Async ( sender ) ;
497- self . base . send_control_msg ( ControlMessage :: Close { notify } ) ;
515+ if self . state ( ) == AudioContextState :: Closed {
516+ log :: debug! ( "Close no-op - context is already closed" ) ;
517+ return ;
518+ }
498519
499- // Wait for the render thread to have processed the suspend message.
500- // The AudioContextState will be updated by the render thread.
501- log:: debug!( "Suspending audio graph, waiting for signal.." ) ;
502- receiver. await . unwrap ( ) ;
520+ if self . state ( ) == AudioContextState :: Running {
521+ // First, stop rendering via a control message
522+ let ( sender, receiver) = oneshot:: channel ( ) ;
523+ let notify = OneshotNotify :: Async ( sender) ;
524+ self . base . send_control_msg ( ControlMessage :: Close { notify } ) ;
525+
526+ // Wait for the render thread to have processed the suspend message.
527+ // The AudioContextState will be updated by the render thread.
528+ log:: debug!( "Suspending audio graph, waiting for signal.." ) ;
529+ receiver. await . unwrap ( ) ;
530+ } else {
531+ // if the context is not running, change the state manually
532+ self . base . set_state ( AudioContextState :: Closed ) ;
533+ }
503534
504535 // Then ask the audio host to close the stream
505536 log:: debug!( "Suspended audio graph. Closing audio stream.." ) ;
@@ -526,8 +557,16 @@ impl AudioContext {
526557 /// * The audio device is not available
527558 /// * For a `BackendSpecificError`
528559 pub fn suspend_sync ( & self ) {
529- log:: debug!( "Suspend_sync called" ) ;
530- // First, pause rendering via a control message
560+ // Lock the backend manager mutex to avoid concurrent calls
561+ log:: debug!( "Suspend_sync called, locking backend manager" ) ;
562+ let backend_manager_guard = self . backend_manager . lock ( ) . unwrap ( ) ;
563+
564+ if self . state ( ) != AudioContextState :: Running {
565+ log:: debug!( "Suspend_sync no-op - context is not running" ) ;
566+ return ;
567+ }
568+
569+ // Pause rendering via a control message
531570 let ( sender, receiver) = crossbeam_channel:: bounded ( 0 ) ;
532571 let notify = OneshotNotify :: Sync ( sender) ;
533572 self . base
@@ -537,10 +576,11 @@ impl AudioContext {
537576 // The AudioContextState will be updated by the render thread.
538577 log:: debug!( "Suspending audio graph, waiting for signal.." ) ;
539578 receiver. recv ( ) . ok ( ) ;
540- log:: debug!( "Suspended audio graph. Suspending audio stream.." ) ;
541579
542580 // Then ask the audio host to suspend the stream
543- self . backend_manager . lock ( ) . unwrap ( ) . suspend ( ) ;
581+ log:: debug!( "Suspended audio graph. Suspending audio stream.." ) ;
582+ backend_manager_guard. suspend ( ) ;
583+
544584 log:: debug!( "Suspended audio stream" ) ;
545585 }
546586
@@ -557,9 +597,17 @@ impl AudioContext {
557597 /// * The audio device is not available
558598 /// * For a `BackendSpecificError`
559599 pub fn resume_sync ( & self ) {
560- log:: debug!( "Resume_sync called" ) ;
561- // First ask the audio host to resume the stream
562- self . backend_manager . lock ( ) . unwrap ( ) . resume ( ) ;
600+ // Lock the backend manager mutex to avoid concurrent calls
601+ log:: debug!( "Resume_sync called, locking backend manager" ) ;
602+ let backend_manager_guard = self . backend_manager . lock ( ) . unwrap ( ) ;
603+
604+ if self . state ( ) != AudioContextState :: Suspended {
605+ log:: debug!( "Resume no-op - context is not suspended" ) ;
606+ return ;
607+ }
608+
609+ // Ask the audio host to resume the stream
610+ backend_manager_guard. resume ( ) ;
563611
564612 // Then, ask to resume rendering via a control message
565613 log:: debug!( "Resumed audio stream, waking audio graph" ) ;
@@ -586,21 +634,33 @@ impl AudioContext {
586634 ///
587635 /// Will panic when this function is called multiple times
588636 pub fn close_sync ( & self ) {
589- log:: debug!( "Close_sync called" ) ;
637+ // Lock the backend manager mutex to avoid concurrent calls
638+ log:: debug!( "Close_sync called, locking backend manager" ) ;
639+ let backend_manager_guard = self . backend_manager . lock ( ) . unwrap ( ) ;
590640
591- // First, stop rendering via a control message
592- let ( sender , receiver ) = crossbeam_channel :: bounded ( 0 ) ;
593- let notify = OneshotNotify :: Sync ( sender ) ;
594- self . base . send_control_msg ( ControlMessage :: Close { notify } ) ;
641+ if self . state ( ) == AudioContextState :: Closed {
642+ log :: debug! ( "Close no-op - context is already closed" ) ;
643+ return ;
644+ }
595645
596- // Wait for the render thread to have processed the suspend message.
597- // The AudioContextState will be updated by the render thread.
598- log:: debug!( "Suspending audio graph, waiting for signal.." ) ;
599- receiver. recv ( ) . ok ( ) ;
646+ // First, stop rendering via a control message
647+ if self . state ( ) == AudioContextState :: Running {
648+ let ( sender, receiver) = crossbeam_channel:: bounded ( 0 ) ;
649+ let notify = OneshotNotify :: Sync ( sender) ;
650+ self . base . send_control_msg ( ControlMessage :: Close { notify } ) ;
651+
652+ // Wait for the render thread to have processed the suspend message.
653+ // The AudioContextState will be updated by the render thread.
654+ log:: debug!( "Suspending audio graph, waiting for signal.." ) ;
655+ receiver. recv ( ) . ok ( ) ;
656+ } else {
657+ // if the context is not running, change the state manually
658+ self . base . set_state ( AudioContextState :: Closed ) ;
659+ }
600660
601661 // Then ask the audio host to close the stream
602662 log:: debug!( "Suspended audio graph. Closing audio stream.." ) ;
603- self . backend_manager . lock ( ) . unwrap ( ) . close ( ) ;
663+ backend_manager_guard . close ( ) ;
604664
605665 // Stop the AudioRenderCapacity collection thread
606666 self . render_capacity . stop ( ) ;
0 commit comments