Prevent possible deadlocks when pressing Escape to cancel recordings (#408)

* PRevent possible deadlocks when pressing Escape to cancel recordings

* same for sigusr2

---------

Co-authored-by: CJ Pais <cj@cjpais.com>
This commit is contained in:
Danny Smith 2025-12-18 14:28:10 +00:00 committed by GitHub
parent 0a7a1a0aa5
commit 3f401d933a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 50 additions and 35 deletions

View file

@ -870,25 +870,32 @@ pub fn register_shortcut(app: &AppHandle, binding: ShortcutBinding) -> Result<()
action.stop(ah, &binding_id_for_closure, &shortcut_string); action.stop(ah, &binding_id_for_closure, &shortcut_string);
} }
} else { } else {
// Toggle mode: toggle on press only
if event.state == ShortcutState::Pressed { if event.state == ShortcutState::Pressed {
let toggle_state_manager = ah.state::<ManagedToggleState>(); // Determine action and update state while holding the lock,
// but RELEASE the lock before calling the action to avoid deadlocks.
// (Actions may need to acquire the lock themselves, e.g., cancel_current_operation)
let should_start: bool;
{
let toggle_state_manager = ah.state::<ManagedToggleState>();
let mut states = toggle_state_manager
.lock()
.expect("Failed to lock toggle state manager");
let mut states = toggle_state_manager.lock().expect("Failed to lock toggle state manager"); let is_currently_active = states
.active_toggles
.entry(binding_id_for_closure.clone())
.or_insert(false);
let is_currently_active = states.active_toggles should_start = !*is_currently_active;
.entry(binding_id_for_closure.clone()) *is_currently_active = should_start;
.or_insert(false); } // Lock released here
if *is_currently_active { // Now call the action without holding the lock
action.stop( if should_start {
ah,
&binding_id_for_closure,
&shortcut_string,
);
*is_currently_active = false; // Update state to inactive
} else {
action.start(ah, &binding_id_for_closure, &shortcut_string); action.start(ah, &binding_id_for_closure, &shortcut_string);
*is_currently_active = true; // Update state to active } else {
action.stop(ah, &binding_id_for_closure, &shortcut_string);
} }
} }
} }

View file

@ -25,32 +25,40 @@ pub fn setup_signal_handler(app_handle: AppHandle, mut signals: Signals) {
let shortcut_string = "SIGUSR2"; let shortcut_string = "SIGUSR2";
if let Some(action) = ACTION_MAP.get(binding_id) { if let Some(action) = ACTION_MAP.get(binding_id) {
let toggle_state_manager = // Determine action and update state while holding the lock,
app_handle_for_signal.state::<ManagedToggleState>(); // but RELEASE the lock before calling the action to avoid deadlocks.
// (Actions may need to acquire the lock themselves, e.g., cancel_current_operation)
let should_start: bool;
{
let toggle_state_manager =
app_handle_for_signal.state::<ManagedToggleState>();
let mut states = match toggle_state_manager.lock() { let mut states = match toggle_state_manager.lock() {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
warn!("Failed to lock toggle state manager: {e}"); warn!("Failed to lock toggle state manager: {e}");
continue; continue;
} }
}; };
let is_currently_active = states let is_currently_active = states
.active_toggles .active_toggles
.entry(binding_id.to_string()) .entry(binding_id.to_string())
.or_insert(false); .or_insert(false);
if *is_currently_active { should_start = !*is_currently_active;
debug!("SIGUSR2: Stopping transcription (currently active)"); *is_currently_active = should_start;
action.stop(&app_handle_for_signal, binding_id, shortcut_string); } // Lock released here
*is_currently_active = false; // Update state to inactive
debug!("SIGUSR2: Transcription stopped"); // Now call the action without holding the lock
} else { if should_start {
debug!("SIGUSR2: Starting transcription (currently inactive)"); debug!("SIGUSR2: Starting transcription (was inactive)");
action.start(&app_handle_for_signal, binding_id, shortcut_string); action.start(&app_handle_for_signal, binding_id, shortcut_string);
*is_currently_active = true; // Update state to active
info!("SIGUSR2: Transcription started"); info!("SIGUSR2: Transcription started");
} else {
debug!("SIGUSR2: Stopping transcription (was active)");
action.stop(&app_handle_for_signal, binding_id, shortcut_string);
debug!("SIGUSR2: Transcription stopped");
} }
} else { } else {
warn!("No action defined in ACTION_MAP for binding ID '{binding_id}'"); warn!("No action defined in ACTION_MAP for binding ID '{binding_id}'");