@@ -31,7 +31,7 @@ use crate::Error;
3131/// eprintln!("Error deactivating client: {err}");
3232/// };
3333/// ```
34- #[ must_use = "The jack client is shut down when the AsyncClient is dropped. You most likely want to keep this alive and manually tear down with `AsyncClient::deactivate`." ]
34+ #[ must_use = "Processing stops when the AsyncClient is dropped. You most likely want to keep this alive and manually tear down with `AsyncClient::deactivate`." ]
3535pub struct AsyncClient < N , P > {
3636 callback : Option < Box < CallbackContext < N , P > > > ,
3737}
4848 /// methods specified by the `NotificationHandler` and `ProcessHandler` objects.
4949 ///
5050 /// On failure, either `Err(Error::CallbackRegistrationError)` or
51- /// `Err(Error::ClientActivationError)` is returned. The client is closed and both handlers
52- /// are dropped.
51+ /// `Err(Error::ClientActivationError)` is returned. Callbacks are detached and both handlers
52+ /// are dropped. Ports and transport objects may keep the JACK client open.
53+ /// If callback cleanup also fails, the callback context is retained to keep JACK's pointers valid.
5354 ///
5455 /// `notification_handler` and `process_handler` are consumed, but they are returned when
5556 /// `Client::deactivate` is called.
@@ -78,13 +79,26 @@ where
7879 callback_lock : std:: sync:: Mutex :: new ( ( ) ) ,
7980 } ) ;
8081 // Declare the guard after the context so it is released before error cleanup:
81- // Client ::drop also acquires this mutex.
82+ // ClientHandle ::drop also acquires this mutex.
8283 let _m = CREATE_OR_DESTROY_CLIENT_MUTEX . lock ( ) . ok ( ) ;
83- unsafe {
84- CallbackContext :: register_callbacks ( & mut callback_context) ?;
85- }
86- if activate ( & callback_context. client ) != 0 {
87- return Err ( Error :: ClientActivationError ) ;
84+ let registration = unsafe { CallbackContext :: register_callbacks ( & mut callback_context) } ;
85+ let result = registration. and_then ( |( ) | {
86+ if activate ( & callback_context. client ) == 0 {
87+ Ok ( ( ) )
88+ } else {
89+ Err ( Error :: ClientActivationError )
90+ }
91+ } ) ;
92+ if let Err ( error) = result {
93+ if Self :: detach_callbacks ( & callback_context) . is_err ( ) {
94+ crate :: logging:: log_error (
95+ b"Failed to detach callbacks after activation error. Leaking callback context.\0 " ,
96+ ) ;
97+ // JACK may still hold pointers into the context. Retain it rather
98+ // than free memory that a callback could still access.
99+ Box :: leak ( callback_context) ;
100+ }
101+ return Err ( error) ;
88102 }
89103 Ok ( AsyncClient {
90104 callback : Some ( callback_context) ,
@@ -106,8 +120,9 @@ impl<N, P> AsyncClient<N, P> {
106120 /// The `handler` that was used for `Client::activate` is returned on success. Its state may
107121 /// have changed due to JACK calling its methods.
108122 ///
109- /// In the case of error, the `Client` is destroyed because its state is unknown, and it is
110- /// therefore unsafe to continue using.
123+ /// In the case of error, the `Client` is dropped. Ports and transport objects may keep the
124+ /// underlying JACK client open.
125+ /// If callback cleanup fails, the callback context is retained to keep JACK's pointers valid.
111126 pub fn deactivate ( self ) -> Result < ( Client , N , P ) , Error > {
112127 let mut c = self ;
113128 c. maybe_deactivate ( )
@@ -123,29 +138,40 @@ impl<N, P> AsyncClient<N, P> {
123138 return Err ( Error :: ClientIsNoLongerAlive ) ;
124139 }
125140 let cb = self . callback . take ( ) . ok_or ( Error :: ClientIsNoLongerAlive ) ?;
126- drop ( cb. callback_lock . lock ( ) ) ;
127- cb. is_valid_for_callback
128- . store ( false , std:: sync:: atomic:: Ordering :: Relaxed ) ;
129- // deactivate
130- if unsafe { j:: jack_deactivate ( cb. client . raw ( ) ) } != 0 {
141+ if let Err ( error) = Self :: detach_callbacks ( & cb) {
142+ crate :: logging:: log_error (
143+ b"Failed to detach callbacks on deactivation. Leaking callback context.\0 " ,
144+ ) ;
145+ // Keep the callback data alive if JACK could still use it.
146+ Box :: leak ( cb) ;
131147 drop ( m) ;
132- return Err ( Error :: ClientDeactivationError ) ;
148+ return Err ( error ) ;
133149 }
134150
135- // clear the callbacks
136- unsafe { clear_callbacks ( cb. client . raw ( ) ) } ?;
137151 // done, take ownership of callback
138152 if cb. has_panic . load ( std:: sync:: atomic:: Ordering :: Relaxed ) {
139153 drop ( m) ;
140154 return Err ( Error :: ClientPanicked ) ;
141155 }
142156 Ok ( cb)
143157 }
158+
159+ fn detach_callbacks ( cb : & CallbackContext < N , P > ) -> Result < ( ) , Error > {
160+ {
161+ let _guard = cb. callback_lock . lock ( ) ;
162+ cb. is_valid_for_callback
163+ . store ( false , std:: sync:: atomic:: Ordering :: Relaxed ) ;
164+ }
165+ if unsafe { j:: jack_deactivate ( cb. client . raw ( ) ) } != 0 {
166+ return Err ( Error :: ClientDeactivationError ) ;
167+ }
168+ unsafe { clear_callbacks ( cb. client . raw ( ) ) }
169+ }
144170}
145171
146- /// Closes the client.
172+ /// Stops processing and releases this reference to the client.
147173impl < N , P > Drop for AsyncClient < N , P > {
148- // Deactivate and close the client.
174+ // Deactivate and release the client.
149175 fn drop ( & mut self ) {
150176 let _ = self . maybe_deactivate ( ) ;
151177 }
@@ -195,15 +221,17 @@ mod tests {
195221
196222 impl Drop for Handler {
197223 fn drop ( & mut self ) {
198- // The client must be closed before either handler is destroyed .
199- assert_eq ! ( self . port. name( ) , Err ( Error :: ClientIsNoLongerAlive ) ) ;
224+ // Each handler's port keeps the client alive during destruction .
225+ assert ! ( self . port. name( ) . is_ok ( ) ) ;
200226 self . drops . fetch_add ( 1 , Ordering :: SeqCst ) ;
201227 }
202228 }
203229
204230 fn check_activation_failure ( start_processing : bool ) {
205231 let ( client, _) = Client :: new ( "" , ClientOptions :: default ( ) ) . unwrap ( ) ;
206232 let port = client. register_port ( "input" , AudioIn :: default ( ) ) . unwrap ( ) ;
233+ let transport = client. transport ( ) ;
234+ let life = Arc :: downgrade ( & transport. client ) ;
207235 let notification_drops = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
208236 let process_drops = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
209237 let ( send, recv) = sync_channel ( 1 ) ;
@@ -229,16 +257,49 @@ mod tests {
229257 assert_eq ! ( result. err( ) , Some ( Error :: ClientActivationError ) ) ;
230258 assert_eq ! ( notification_drops. load( Ordering :: SeqCst ) , 1 ) ;
231259 assert_eq ! ( process_drops. load( Ordering :: SeqCst ) , 1 ) ;
232- assert_eq ! ( port. name( ) , Err ( Error :: ClientIsNoLongerAlive ) ) ;
260+ assert ! ( port. name( ) . is_ok( ) ) ;
261+ // The retained client must be safe to activate after its original
262+ // callback context and handlers have been freed.
263+ assert_eq ! ( unsafe { j:: jack_activate( port. client_ptr( ) ) } , 0 ) ;
264+ assert_eq ! ( unsafe { j:: jack_deactivate( port. client_ptr( ) ) } , 0 ) ;
265+ drop ( port) ;
266+ assert ! ( life. upgrade( ) . is_some( ) ) ;
267+ drop ( transport) ;
268+ assert ! ( life. upgrade( ) . is_none( ) ) ;
233269 }
234270
235271 #[ test]
236- fn activation_failure_closes_client_and_drops_handlers ( ) {
272+ fn activation_failure_detaches_callbacks_and_drops_handlers ( ) {
237273 check_activation_failure ( false ) ;
238274 }
239275
240276 #[ test]
241- fn activation_failure_after_start_closes_client_and_drops_handlers ( ) {
277+ fn activation_failure_after_start_detaches_callbacks_and_drops_handlers ( ) {
242278 check_activation_failure ( true ) ;
243279 }
280+
281+ #[ test]
282+ fn dropping_async_client_detaches_callbacks_while_port_keeps_client_alive ( ) {
283+ let ( client, _) = Client :: new ( "" , ClientOptions :: default ( ) ) . unwrap ( ) ;
284+ let port = client. register_port ( "input" , AudioIn :: default ( ) ) . unwrap ( ) ;
285+ let transport = client. transport ( ) ;
286+ let life = Arc :: downgrade ( & transport. client ) ;
287+ let drops = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
288+ let handler = || Handler {
289+ drops : drops. clone ( ) ,
290+ port : port. clone_unowned ( ) ,
291+ processed : None ,
292+ } ;
293+ let active = client. activate_async ( handler ( ) , handler ( ) ) . unwrap ( ) ;
294+
295+ drop ( active) ;
296+ assert_eq ! ( drops. load( Ordering :: SeqCst ) , 2 ) ;
297+ assert ! ( port. name( ) . is_ok( ) ) ;
298+ assert_eq ! ( unsafe { j:: jack_activate( port. client_ptr( ) ) } , 0 ) ;
299+ assert_eq ! ( unsafe { j:: jack_deactivate( port. client_ptr( ) ) } , 0 ) ;
300+
301+ drop ( port) ;
302+ drop ( transport) ;
303+ assert ! ( life. upgrade( ) . is_none( ) ) ;
304+ }
244305}
0 commit comments