From 93c02533a53ec4a2bb906ca67b225124dda2b60d Mon Sep 17 00:00:00 2001 From: Will Medrano Date: Mon, 9 Sep 2024 05:51:50 -0700 Subject: [PATCH 1/3] Catch panics in callbacks. --- src/client/async_client.rs | 2 + src/client/callbacks.rs | 261 +++++++++++++++++++++++++++++-------- src/client/client_impl.rs | 21 ++- src/properties.rs | 26 ++-- 4 files changed, 242 insertions(+), 68 deletions(-) diff --git a/src/client/async_client.rs b/src/client/async_client.rs index c019a7505..ed57aad29 100644 --- a/src/client/async_client.rs +++ b/src/client/async_client.rs @@ -2,6 +2,7 @@ use jack_sys as j; use std::fmt; use std::fmt::Debug; use std::mem; +use std::sync::atomic::AtomicBool; use super::callbacks::clear_callbacks; use super::callbacks::{CallbackContext, NotificationHandler, ProcessHandler}; @@ -58,6 +59,7 @@ where client, notification: notification_handler, process: process_handler, + is_valid: AtomicBool::new(true), }); CallbackContext::register_callbacks(&mut callback_context)?; sleep_on_test(); diff --git a/src/client/callbacks.rs b/src/client/callbacks.rs index 04c25ce52..94d60c037 100644 --- a/src/client/callbacks.rs +++ b/src/client/callbacks.rs @@ -1,5 +1,9 @@ use jack_sys as j; -use std::ffi; +use std::{ + ffi, + panic::catch_unwind, + sync::atomic::{AtomicBool, Ordering}, +}; use crate::{Client, ClientStatus, Control, Error, Frames, PortId, ProcessScope}; @@ -123,8 +127,17 @@ where N: 'static + Send + Sync + NotificationHandler, P: 'static + Send + ProcessHandler, { - let ctx = CallbackContext::::from_raw(data); - ctx.notification.thread_init(&ctx.client) + let res = catch_unwind(|| { + let Some(ctx) = CallbackContext::::from_raw(data) else { + return; + }; + ctx.notification.thread_init(&ctx.client); + }); + if let Err(err) = res { + CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + eprintln!("{err:?}"); + std::mem::forget(err); + } } unsafe extern "C" fn shutdown( @@ -135,13 +148,22 @@ unsafe extern "C" fn shutdown( N: 'static + Send + Sync + NotificationHandler, P: 'static + Send + ProcessHandler, { - let ctx = CallbackContext::::from_raw(data); - let cstr = ffi::CStr::from_ptr(reason); - let reason_str = cstr.to_str().unwrap_or("Failed to interpret error."); - ctx.notification.shutdown( - ClientStatus::from_bits(code).unwrap_or_else(ClientStatus::empty), - reason_str, - ) + let res = catch_unwind(|| { + let Some(ctx) = CallbackContext::::from_raw(data) else { + return; + }; + let cstr = ffi::CStr::from_ptr(reason); + let reason_str = cstr.to_str().unwrap_or("Failed to interpret error."); + ctx.notification.shutdown( + ClientStatus::from_bits(code).unwrap_or_else(ClientStatus::empty), + reason_str, + ); + }); + if let Err(err) = res { + CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + eprintln!("{err:?}"); + std::mem::forget(err); + } } unsafe extern "C" fn process(n_frames: Frames, data: *mut libc::c_void) -> libc::c_int @@ -149,15 +171,19 @@ where N: 'static + Send + Sync + NotificationHandler, P: 'static + Send + ProcessHandler, { - let res = std::panic::catch_unwind(|| { - let ctx = CallbackContext::::from_raw(data); + let res = catch_unwind(|| { + let Some(ctx) = CallbackContext::::from_raw(data) else { + return Control::Quit; + }; let scope = ProcessScope::from_raw(n_frames, ctx.client.raw()); ctx.process.process(&ctx.client, &scope) }); match res { Ok(res) => res.to_ffi(), Err(err) => { + CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); eprintln!("{err:?}"); + std::mem::forget(err); Control::Quit.to_ffi() } } @@ -172,14 +198,25 @@ where N: 'static + Send + Sync + NotificationHandler, P: 'static + Send + ProcessHandler, { - let ctx = CallbackContext::::from_raw(data); - match ctx.process.sync( - &ctx.client, - crate::Transport::state_from_ffi(state), - &*(pos as *mut crate::TransportPosition), - ) { - true => 1, - false => 0, + let res = catch_unwind(|| { + let Some(ctx) = CallbackContext::::from_raw(data) else { + return false; + }; + ctx.process.sync( + &ctx.client, + crate::Transport::state_from_ffi(state), + &*(pos as *mut crate::TransportPosition), + ) + }); + match res { + Ok(true) => 1, + Ok(false) => 0, + Err(err) => { + CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + eprintln!("{err:?}"); + std::mem::forget(err); + 0 + } } } @@ -188,9 +225,18 @@ where N: 'static + Send + Sync + NotificationHandler, P: 'static + Send + ProcessHandler, { - let ctx = CallbackContext::::from_raw(data); - let is_starting = !matches!(starting, 0); - ctx.notification.freewheel(&ctx.client, is_starting) + let res = catch_unwind(|| { + let Some(ctx) = CallbackContext::::from_raw(data) else { + return; + }; + let is_starting = !matches!(starting, 0); + ctx.notification.freewheel(&ctx.client, is_starting) + }); + if let Err(err) = res { + CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + eprintln!("{err:?}"); + std::mem::forget(err); + } } unsafe extern "C" fn buffer_size(n_frames: Frames, data: *mut libc::c_void) -> libc::c_int @@ -198,8 +244,21 @@ where N: 'static + Send + Sync + NotificationHandler, P: 'static + Send + ProcessHandler, { - let ctx = CallbackContext::::from_raw(data); - ctx.process.buffer_size(&ctx.client, n_frames).to_ffi() + let res = catch_unwind(|| { + let Some(ctx) = CallbackContext::::from_raw(data) else { + return Control::Quit; + }; + ctx.process.buffer_size(&ctx.client, n_frames) + }); + match res { + Ok(c) => c.to_ffi(), + Err(err) => { + CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + eprintln!("{err:?}"); + std::mem::forget(err); + Control::Quit.to_ffi() + } + } } unsafe extern "C" fn sample_rate(n_frames: Frames, data: *mut libc::c_void) -> libc::c_int @@ -207,8 +266,21 @@ where N: 'static + Send + Sync + NotificationHandler, P: 'static + Send + ProcessHandler, { - let ctx = CallbackContext::::from_raw(data); - ctx.notification.sample_rate(&ctx.client, n_frames).to_ffi() + let res = catch_unwind(|| { + let Some(ctx) = CallbackContext::::from_raw(data) else { + return Control::Quit; + }; + ctx.notification.sample_rate(&ctx.client, n_frames) + }); + match res { + Ok(c) => c.to_ffi(), + Err(err) => { + CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + eprintln!("{err:?}"); + std::mem::forget(err); + Control::Quit.to_ffi() + } + } } unsafe extern "C" fn client_registration( @@ -219,11 +291,20 @@ unsafe extern "C" fn client_registration( N: 'static + Send + Sync + NotificationHandler, P: 'static + Send + ProcessHandler, { - let ctx = CallbackContext::::from_raw(data); - let name = ffi::CStr::from_ptr(name).to_str().unwrap(); - let register = !matches!(register, 0); - ctx.notification - .client_registration(&ctx.client, name, register) + let res = catch_unwind(|| { + let Some(ctx) = CallbackContext::::from_raw(data) else { + return; + }; + let name = ffi::CStr::from_ptr(name).to_str().unwrap(); + let register = !matches!(register, 0); + ctx.notification + .client_registration(&ctx.client, name, register); + }); + if let Err(err) = res { + CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + eprintln!("{err:?}"); + std::mem::forget(err); + } } unsafe extern "C" fn port_registration( @@ -234,10 +315,19 @@ unsafe extern "C" fn port_registration( N: 'static + Send + Sync + NotificationHandler, P: 'static + Send + ProcessHandler, { - let ctx = CallbackContext::::from_raw(data); - let register = !matches!(register, 0); - ctx.notification - .port_registration(&ctx.client, port_id, register) + let res = catch_unwind(|| { + let Some(ctx) = CallbackContext::::from_raw(data) else { + return; + }; + let register = !matches!(register, 0); + ctx.notification + .port_registration(&ctx.client, port_id, register); + }); + if let Err(err) = res { + CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + eprintln!("{err:?}"); + std::mem::forget(err); + } } #[allow(dead_code)] // TODO: remove once it can be registered @@ -251,12 +341,24 @@ where N: 'static + Send + Sync + NotificationHandler, P: 'static + Send + ProcessHandler, { - let ctx = CallbackContext::::from_raw(data); - let old_name = ffi::CStr::from_ptr(old_name).to_str().unwrap(); - let new_name = ffi::CStr::from_ptr(new_name).to_str().unwrap(); - ctx.notification - .port_rename(&ctx.client, port_id, old_name, new_name) - .to_ffi() + let res = catch_unwind(|| { + let Some(ctx) = CallbackContext::::from_raw(data) else { + return Control::Quit; + }; + let old_name = ffi::CStr::from_ptr(old_name).to_str().unwrap(); + let new_name = ffi::CStr::from_ptr(new_name).to_str().unwrap(); + ctx.notification + .port_rename(&ctx.client, port_id, old_name, new_name) + }); + match res { + Ok(c) => c.to_ffi(), + Err(err) => { + CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + eprintln!("{err:?}"); + std::mem::forget(err); + Control::Quit.to_ffi() + } + } } unsafe extern "C" fn port_connect( @@ -268,10 +370,19 @@ unsafe extern "C" fn port_connect( N: 'static + Send + Sync + NotificationHandler, P: 'static + Send + ProcessHandler, { - let ctx = CallbackContext::::from_raw(data); - let are_connected = !matches!(connect, 0); - ctx.notification - .ports_connected(&ctx.client, port_id_a, port_id_b, are_connected) + let res = catch_unwind(|| { + let Some(ctx) = CallbackContext::::from_raw(data) else { + return; + }; + let are_connected = !matches!(connect, 0); + ctx.notification + .ports_connected(&ctx.client, port_id_a, port_id_b, are_connected) + }); + if let Err(err) = res { + CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + eprintln!("{err:?}"); + std::mem::forget(err); + } } unsafe extern "C" fn graph_order(data: *mut libc::c_void) -> libc::c_int @@ -279,8 +390,21 @@ where N: 'static + Send + Sync + NotificationHandler, P: 'static + Send + ProcessHandler, { - let ctx = CallbackContext::::from_raw(data); - ctx.notification.graph_reorder(&ctx.client).to_ffi() + let res = catch_unwind(|| { + let Some(ctx) = CallbackContext::::from_raw(data) else { + return Control::Quit; + }; + ctx.notification.graph_reorder(&ctx.client) + }); + match res { + Ok(c) => c.to_ffi(), + Err(err) => { + CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + eprintln!("{err:?}"); + std::mem::forget(err); + Control::Quit.to_ffi() + } + } } unsafe extern "C" fn xrun(data: *mut libc::c_void) -> libc::c_int @@ -288,8 +412,21 @@ where N: 'static + Send + Sync + NotificationHandler, P: 'static + Send + ProcessHandler, { - let ctx = CallbackContext::::from_raw(data); - ctx.notification.xrun(&ctx.client).to_ffi() + let res = catch_unwind(|| { + let Some(ctx) = CallbackContext::::from_raw(data) else { + return Control::Quit; + }; + ctx.notification.xrun(&ctx.client) + }); + match res { + Ok(c) => c.to_ffi(), + Err(err) => { + CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + eprintln!("{err:?}"); + std::mem::forget(err); + Control::Quit.to_ffi() + } + } } /// Unsafe ffi wrapper that clears the callbacks registered to `client`. @@ -313,10 +450,18 @@ pub unsafe fn clear_callbacks(client: *mut j::jack_client_t) -> Result<(), Error Ok(()) } +/// The information used by JACK to process data. pub struct CallbackContext { + /// The underlying JACK client. pub client: Client, + /// The handler for notifications. pub notification: N, + /// The handler for processing. pub process: P, + /// True if the callback is valid. + /// + /// This becomes false after a panic. + pub is_valid: AtomicBool, } impl CallbackContext @@ -324,10 +469,22 @@ where N: 'static + Send + Sync + NotificationHandler, P: 'static + Send + ProcessHandler, { - pub unsafe fn from_raw<'a>(ptr: *mut libc::c_void) -> &'a mut CallbackContext { + pub unsafe fn from_raw<'a>(ptr: *mut libc::c_void) -> Option<&'a mut CallbackContext> { debug_assert!(!ptr.is_null()); let obj_ptr = ptr as *mut CallbackContext; - &mut *obj_ptr + let obj_ref = &mut *obj_ptr; + if obj_ref.is_valid.load(Ordering::Relaxed) { + Some(obj_ref) + } else { + None + } + } + + /// Mark the callback context as invalid. + /// + /// This usually happens after a panic. + pub fn make_invalid(&mut self) { + self.is_valid.store(true, Ordering::Relaxed); } fn raw(b: &mut Box) -> *mut libc::c_void { diff --git a/src/client/client_impl.rs b/src/client/client_impl.rs index 387c90f41..609a3a247 100644 --- a/src/client/client_impl.rs +++ b/src/client/client_impl.rs @@ -1,5 +1,6 @@ use jack_sys as j; use std::fmt::Debug; +use std::panic::catch_unwind; use std::sync::Arc; use std::{ffi, fmt, ptr}; @@ -791,19 +792,25 @@ pub struct CycleTimes { } unsafe extern "C" fn error_handler(msg: *const libc::c_char) { - match std::ffi::CStr::from_ptr(msg).to_str() { + let res = catch_unwind(|| match std::ffi::CStr::from_ptr(msg).to_str() { Ok(msg) => log::error!("{}", msg), - Err(err) => log::error!("failed to parse JACK error: {:?}", err), + Err(err) => log::error!("failed to log to JACK error: {:?}", err), + }); + if let Err(err) = res { + eprintln!("{err:?}"); + std::mem::forget(err); } } unsafe extern "C" fn info_handler(msg: *const libc::c_char) { - match std::ffi::CStr::from_ptr(msg).to_str() { + let res = catch_unwind(|| match std::ffi::CStr::from_ptr(msg).to_str() { Ok(msg) => log::info!("{}", msg), - Err(err) => log::error!("failed to parse JACK error: {:?}", err), + Err(err) => log::error!("failed to log to JACK info: {:?}", err), + }); + if let Err(err) = res { + eprintln!("{err:?}"); + std::mem::forget(err); } } -unsafe extern "C" fn silent_handler(_msg: *const libc::c_char) { - //silent -} +unsafe extern "C" fn silent_handler(_msg: *const libc::c_char) {} diff --git a/src/properties.rs b/src/properties.rs index c341aa2b5..74687c39a 100644 --- a/src/properties.rs +++ b/src/properties.rs @@ -1,5 +1,7 @@ //! Properties, AKA [Meta Data](https://jackaudio.org/api/group__Metadata.html) //! +use std::panic::catch_unwind; + use j::jack_uuid_t as uuid; use jack_sys as j; @@ -30,15 +32,21 @@ pub(crate) unsafe extern "C" fn property_changed

( ) where P: PropertyChangeHandler, { - let h: &mut P = &mut *(arg as *mut P); - let key_c = std::ffi::CStr::from_ptr(key); - let key = key_c.to_str().expect("to convert key to valid str"); - let c = match change { - j::PropertyCreated => PropertyChange::Created { subject, key }, - j::PropertyDeleted => PropertyChange::Deleted { subject, key }, - _ => PropertyChange::Changed { subject, key }, - }; - h.property_changed(&c); + let res = catch_unwind(|| { + let h: &mut P = &mut *(arg as *mut P); + let key_c = std::ffi::CStr::from_ptr(key); + let key = key_c.to_str().expect("to convert key to valid str"); + let c = match change { + j::PropertyCreated => PropertyChange::Created { subject, key }, + j::PropertyDeleted => PropertyChange::Deleted { subject, key }, + _ => PropertyChange::Changed { subject, key }, + }; + h.property_changed(&c); + }); + if let Err(err) = res { + eprintln!("{err:?}"); + std::mem::forget(err); + } } #[cfg(feature = "metadata")] From 141ec8537540160f554b27d10337ccb16ed59539 Mon Sep 17 00:00:00 2001 From: Will Medrano Date: Mon, 9 Sep 2024 06:00:20 -0700 Subject: [PATCH 2/3] Mark callbacks as invalid if they return Quit. --- src/client/callbacks.rs | 80 ++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/src/client/callbacks.rs b/src/client/callbacks.rs index 94d60c037..f6e954345 100644 --- a/src/client/callbacks.rs +++ b/src/client/callbacks.rs @@ -134,7 +134,7 @@ where ctx.notification.thread_init(&ctx.client); }); if let Err(err) = res { - CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + CallbackContext::::from_raw(data).map(CallbackContext::mark_invalid); eprintln!("{err:?}"); std::mem::forget(err); } @@ -160,7 +160,7 @@ unsafe extern "C" fn shutdown( ); }); if let Err(err) = res { - CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + CallbackContext::::from_raw(data).map(CallbackContext::mark_invalid); eprintln!("{err:?}"); std::mem::forget(err); } @@ -176,12 +176,16 @@ where return Control::Quit; }; let scope = ProcessScope::from_raw(n_frames, ctx.client.raw()); - ctx.process.process(&ctx.client, &scope) + let c = ctx.process.process(&ctx.client, &scope); + if c == Control::Quit { + ctx.mark_invalid(); + } + c }); match res { Ok(res) => res.to_ffi(), Err(err) => { - CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + CallbackContext::::from_raw(data).map(CallbackContext::mark_invalid); eprintln!("{err:?}"); std::mem::forget(err); Control::Quit.to_ffi() @@ -202,17 +206,21 @@ where let Some(ctx) = CallbackContext::::from_raw(data) else { return false; }; - ctx.process.sync( + let is_ready = ctx.process.sync( &ctx.client, crate::Transport::state_from_ffi(state), &*(pos as *mut crate::TransportPosition), - ) + ); + if !is_ready { + ctx.mark_invalid(); + } + is_ready }); match res { Ok(true) => 1, Ok(false) => 0, Err(err) => { - CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + CallbackContext::::from_raw(data).map(CallbackContext::mark_invalid); eprintln!("{err:?}"); std::mem::forget(err); 0 @@ -230,10 +238,10 @@ where return; }; let is_starting = !matches!(starting, 0); - ctx.notification.freewheel(&ctx.client, is_starting) + ctx.notification.freewheel(&ctx.client, is_starting); }); if let Err(err) = res { - CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + CallbackContext::::from_raw(data).map(CallbackContext::mark_invalid); eprintln!("{err:?}"); std::mem::forget(err); } @@ -248,12 +256,16 @@ where let Some(ctx) = CallbackContext::::from_raw(data) else { return Control::Quit; }; - ctx.process.buffer_size(&ctx.client, n_frames) + let c = ctx.process.buffer_size(&ctx.client, n_frames); + if c == Control::Quit { + ctx.mark_invalid(); + } + c }); match res { Ok(c) => c.to_ffi(), Err(err) => { - CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + CallbackContext::::from_raw(data).map(CallbackContext::mark_invalid); eprintln!("{err:?}"); std::mem::forget(err); Control::Quit.to_ffi() @@ -270,12 +282,16 @@ where let Some(ctx) = CallbackContext::::from_raw(data) else { return Control::Quit; }; - ctx.notification.sample_rate(&ctx.client, n_frames) + let c = ctx.notification.sample_rate(&ctx.client, n_frames); + if c == Control::Quit { + ctx.mark_invalid(); + } + c }); match res { Ok(c) => c.to_ffi(), Err(err) => { - CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + CallbackContext::::from_raw(data).map(CallbackContext::mark_invalid); eprintln!("{err:?}"); std::mem::forget(err); Control::Quit.to_ffi() @@ -301,7 +317,7 @@ unsafe extern "C" fn client_registration( .client_registration(&ctx.client, name, register); }); if let Err(err) = res { - CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + CallbackContext::::from_raw(data).map(CallbackContext::mark_invalid); eprintln!("{err:?}"); std::mem::forget(err); } @@ -324,7 +340,7 @@ unsafe extern "C" fn port_registration( .port_registration(&ctx.client, port_id, register); }); if let Err(err) = res { - CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + CallbackContext::::from_raw(data).map(CallbackContext::mark_invalid); eprintln!("{err:?}"); std::mem::forget(err); } @@ -347,13 +363,18 @@ where }; let old_name = ffi::CStr::from_ptr(old_name).to_str().unwrap(); let new_name = ffi::CStr::from_ptr(new_name).to_str().unwrap(); - ctx.notification - .port_rename(&ctx.client, port_id, old_name, new_name) + let c = ctx + .notification + .port_rename(&ctx.client, port_id, old_name, new_name); + if c == Control::Quit { + ctx.mark_invalid(); + } + c }); match res { Ok(c) => c.to_ffi(), Err(err) => { - CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + CallbackContext::::from_raw(data).map(CallbackContext::mark_invalid); eprintln!("{err:?}"); std::mem::forget(err); Control::Quit.to_ffi() @@ -376,10 +397,10 @@ unsafe extern "C" fn port_connect( }; let are_connected = !matches!(connect, 0); ctx.notification - .ports_connected(&ctx.client, port_id_a, port_id_b, are_connected) + .ports_connected(&ctx.client, port_id_a, port_id_b, are_connected); }); if let Err(err) = res { - CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + CallbackContext::::from_raw(data).map(CallbackContext::mark_invalid); eprintln!("{err:?}"); std::mem::forget(err); } @@ -394,12 +415,16 @@ where let Some(ctx) = CallbackContext::::from_raw(data) else { return Control::Quit; }; - ctx.notification.graph_reorder(&ctx.client) + let c = ctx.notification.graph_reorder(&ctx.client); + if c == Control::Quit { + ctx.mark_invalid(); + } + c }); match res { Ok(c) => c.to_ffi(), Err(err) => { - CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + CallbackContext::::from_raw(data).map(CallbackContext::mark_invalid); eprintln!("{err:?}"); std::mem::forget(err); Control::Quit.to_ffi() @@ -416,12 +441,16 @@ where let Some(ctx) = CallbackContext::::from_raw(data) else { return Control::Quit; }; - ctx.notification.xrun(&ctx.client) + let c = ctx.notification.xrun(&ctx.client); + if c == Control::Quit { + ctx.mark_invalid(); + } + c }); match res { Ok(c) => c.to_ffi(), Err(err) => { - CallbackContext::::from_raw(data).map(CallbackContext::make_invalid); + CallbackContext::::from_raw(data).map(CallbackContext::mark_invalid); eprintln!("{err:?}"); std::mem::forget(err); Control::Quit.to_ffi() @@ -483,7 +512,8 @@ where /// Mark the callback context as invalid. /// /// This usually happens after a panic. - pub fn make_invalid(&mut self) { + #[cold] + pub fn mark_invalid(&mut self) { self.is_valid.store(true, Ordering::Relaxed); } From 627a2b3c33092f236680816ecb0636a5f14b4547 Mon Sep 17 00:00:00 2001 From: Will Medrano Date: Mon, 9 Sep 2024 06:05:43 -0700 Subject: [PATCH 3/3] Bump version. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 53f244509..b1e56189d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" name = "jack" readme = "README.md" repository = "https://github.com/RustAudio/rust-jack" -version = "0.12.0" +version = "0.12.1" [dependencies] bitflags = "1"