From e5f090729f5f285be5be923e732b6a7ab6ea387e Mon Sep 17 00:00:00 2001 From: Sergio Gasquez Date: Tue, 16 Sep 2025 10:46:25 +0200 Subject: [PATCH 1/2] fix: Monitor hang for macos --- espflash/src/cli/monitor/mod.rs | 70 +++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/espflash/src/cli/monitor/mod.rs b/espflash/src/cli/monitor/mod.rs index 158f4589..0ef15040 100644 --- a/espflash/src/cli/monitor/mod.rs +++ b/espflash/src/cli/monitor/mod.rs @@ -177,6 +177,8 @@ impl InputHandler { self.flush_deadline = None; #[cfg(target_os = "linux")] let _timer = linux::arm_timeout_workaround(Duration::from_millis(100)); + #[cfg(target_os = "macos")] + let _timer = macos::arm_timeout_workaround(Duration::from_millis(100)); serial.flush().ignore_timeout().into_diagnostic()?; } @@ -292,6 +294,74 @@ mod linux { } } +#[cfg(target_os = "macos")] +mod macos { + use std::time::Duration; + + use libc::{self, ITIMER_REAL, SIGALRM, c_int, itimerval, sigaction, sigemptyset, timeval}; + + pub struct Workaround { + previous_action: sigaction, + previous_timer: itimerval, + } + + impl Drop for Workaround { + fn drop(&mut self) { + unsafe { + // Restore previous signal action + libc::sigaction(SIGALRM, &self.previous_action, std::ptr::null_mut()); + + // Restore previous timer (or cancel if none was set) + libc::setitimer(ITIMER_REAL, &self.previous_timer, std::ptr::null_mut()); + } + } + } + + /// Sets a one-shot interval timer that will deliver SIGALRM after + /// `timeout`. The timer and signal handler are restored when the + /// returned object is dropped. + pub fn arm_timeout_workaround(timeout: Duration) -> Workaround { + unsafe extern "C" fn handle_signal(_signal: c_int) {} + + unsafe { + // Install a simple handler for SIGALRM and capture the previous one + let mut new_action: sigaction = std::mem::zeroed(); + sigemptyset(&mut new_action.sa_mask); + new_action.sa_flags = 0; + // On macOS, `sa_sigaction` is a function pointer stored as usize + new_action.sa_sigaction = handle_signal as usize; + + let mut old_action: sigaction = std::mem::zeroed(); + libc::sigaction(SIGALRM, &new_action, &mut old_action); + + // Arm a one-shot real-time interval timer (ITIMER_REAL → SIGALRM) + let timeout_tv = duration_to_timeval(timeout); + let new_timer = itimerval { + it_interval: timeval { + tv_sec: 0, + tv_usec: 0, + }, + it_value: timeout_tv, + }; + + let mut old_timer: itimerval = std::mem::zeroed(); + libc::setitimer(ITIMER_REAL, &new_timer, &mut old_timer); + + Workaround { + previous_action: old_action, + previous_timer: old_timer, + } + } + } + + fn duration_to_timeval(d: Duration) -> timeval { + timeval { + tv_sec: d.as_secs() as libc::time_t, + tv_usec: d.subsec_micros() as libc::suseconds_t, + } + } +} + trait ErrorExt { fn ignore_timeout(self) -> Self; } From f2d84e612b89a183c26de790058e22dd0924ca44 Mon Sep 17 00:00:00 2001 From: Sergio Gasquez Date: Tue, 16 Sep 2025 10:48:59 +0200 Subject: [PATCH 2/2] docs: Update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d25ea117..4423945c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - [Windows] Fixed a crash in monitor when espflash is connected via USB Serial/JTAG, and the user is typing into the monitor but the device is not reading serial input. (#943) -- [Linux] Fixed espflash hanging when espflash is connected via USB Serial/JTAG, and the user is typing into the monitor but the device is not reading serial input. (#944) +- [Linux/MacOS] Fixed espflash hanging when espflash is connected via USB Serial/JTAG, and the user is typing into the monitor but the device is not reading serial input. (#944, #945) + ### Removed