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);
}
} else {
// Toggle mode: toggle on press only
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
.entry(binding_id_for_closure.clone())
.or_insert(false);
should_start = !*is_currently_active;
*is_currently_active = should_start;
} // Lock released here
if *is_currently_active {
action.stop(
ah,
&binding_id_for_closure,
&shortcut_string,
);
*is_currently_active = false; // Update state to inactive
} else {
// Now call the action without holding the lock
if should_start {
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";
if let Some(action) = ACTION_MAP.get(binding_id) {
let toggle_state_manager =
app_handle_for_signal.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 =
app_handle_for_signal.state::<ManagedToggleState>();
let mut states = match toggle_state_manager.lock() {
Ok(s) => s,
Err(e) => {
warn!("Failed to lock toggle state manager: {e}");
continue;
}
};
let mut states = match toggle_state_manager.lock() {
Ok(s) => s,
Err(e) => {
warn!("Failed to lock toggle state manager: {e}");
continue;
}
};
let is_currently_active = states
.active_toggles
.entry(binding_id.to_string())
.or_insert(false);
let is_currently_active = states
.active_toggles
.entry(binding_id.to_string())
.or_insert(false);
if *is_currently_active {
debug!("SIGUSR2: Stopping transcription (currently active)");
action.stop(&app_handle_for_signal, binding_id, shortcut_string);
*is_currently_active = false; // Update state to inactive
debug!("SIGUSR2: Transcription stopped");
} else {
debug!("SIGUSR2: Starting transcription (currently inactive)");
should_start = !*is_currently_active;
*is_currently_active = should_start;
} // Lock released here
// Now call the action without holding the lock
if should_start {
debug!("SIGUSR2: Starting transcription (was inactive)");
action.start(&app_handle_for_signal, binding_id, shortcut_string);
*is_currently_active = true; // Update state to active
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 {
warn!("No action defined in ACTION_MAP for binding ID '{binding_id}'");