Skip to content

Commit ac5fc82

Browse files
[MacOS]: Fix hanging terminal (#947)
* fix: Monitor hang for macos * docs: Update changelog
1 parent 23c7419 commit ac5fc82

File tree

2 files changed

+72
-1
lines changed

2 files changed

+72
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
### Fixed
1818

1919
- [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)
20-
- [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)
20+
- [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)
21+
2122

2223
### Removed
2324

espflash/src/cli/monitor/mod.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ impl InputHandler {
177177
self.flush_deadline = None;
178178
#[cfg(target_os = "linux")]
179179
let _timer = linux::arm_timeout_workaround(Duration::from_millis(100));
180+
#[cfg(target_os = "macos")]
181+
let _timer = macos::arm_timeout_workaround(Duration::from_millis(100));
180182
serial.flush().ignore_timeout().into_diagnostic()?;
181183
}
182184

@@ -292,6 +294,74 @@ mod linux {
292294
}
293295
}
294296

297+
#[cfg(target_os = "macos")]
298+
mod macos {
299+
use std::time::Duration;
300+
301+
use libc::{self, ITIMER_REAL, SIGALRM, c_int, itimerval, sigaction, sigemptyset, timeval};
302+
303+
pub struct Workaround {
304+
previous_action: sigaction,
305+
previous_timer: itimerval,
306+
}
307+
308+
impl Drop for Workaround {
309+
fn drop(&mut self) {
310+
unsafe {
311+
// Restore previous signal action
312+
libc::sigaction(SIGALRM, &self.previous_action, std::ptr::null_mut());
313+
314+
// Restore previous timer (or cancel if none was set)
315+
libc::setitimer(ITIMER_REAL, &self.previous_timer, std::ptr::null_mut());
316+
}
317+
}
318+
}
319+
320+
/// Sets a one-shot interval timer that will deliver SIGALRM after
321+
/// `timeout`. The timer and signal handler are restored when the
322+
/// returned object is dropped.
323+
pub fn arm_timeout_workaround(timeout: Duration) -> Workaround {
324+
unsafe extern "C" fn handle_signal(_signal: c_int) {}
325+
326+
unsafe {
327+
// Install a simple handler for SIGALRM and capture the previous one
328+
let mut new_action: sigaction = std::mem::zeroed();
329+
sigemptyset(&mut new_action.sa_mask);
330+
new_action.sa_flags = 0;
331+
// On macOS, `sa_sigaction` is a function pointer stored as usize
332+
new_action.sa_sigaction = handle_signal as usize;
333+
334+
let mut old_action: sigaction = std::mem::zeroed();
335+
libc::sigaction(SIGALRM, &new_action, &mut old_action);
336+
337+
// Arm a one-shot real-time interval timer (ITIMER_REAL → SIGALRM)
338+
let timeout_tv = duration_to_timeval(timeout);
339+
let new_timer = itimerval {
340+
it_interval: timeval {
341+
tv_sec: 0,
342+
tv_usec: 0,
343+
},
344+
it_value: timeout_tv,
345+
};
346+
347+
let mut old_timer: itimerval = std::mem::zeroed();
348+
libc::setitimer(ITIMER_REAL, &new_timer, &mut old_timer);
349+
350+
Workaround {
351+
previous_action: old_action,
352+
previous_timer: old_timer,
353+
}
354+
}
355+
}
356+
357+
fn duration_to_timeval(d: Duration) -> timeval {
358+
timeval {
359+
tv_sec: d.as_secs() as libc::time_t,
360+
tv_usec: d.subsec_micros() as libc::suseconds_t,
361+
}
362+
}
363+
}
364+
295365
trait ErrorExt {
296366
fn ignore_timeout(self) -> Self;
297367
}

0 commit comments

Comments
 (0)