Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 8 additions & 20 deletions src-tauri/crates/berd-call/src/host_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1729,32 +1729,20 @@ impl SessionProcess {
let write_fd = fds[1];
let executable = std::env::current_exe()
.map_err(|error| format!("could not locate berd-call executable: {error}"))?;
// Only the write end reaches the child; `pipe` leaves both inheritable.
unsafe { libc::fcntl(read_fd, libc::F_SETFD, libc::FD_CLOEXEC) };
let mut command = Command::new(executable);
command
// No pre_exec, so std uses posix_spawn: forking a process with AppKit's threads can abort the child.
// The host owns Ctrl-C, so terminal signals stay out of the child's process group.
let result = command
.args(arguments)
.arg("--pcm-output-fd")
.arg(write_fd.to_string())
.process_group(0)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit());
unsafe {
command.pre_exec(move || {
// The host owns Ctrl-C: keep terminal signals away from the
// child and restore the default mask the host blocked.
libc::setpgid(0, 0);
let mut signals = std::mem::zeroed::<libc::sigset_t>();
libc::sigemptyset(&mut signals);
libc::pthread_sigmask(libc::SIG_SETMASK, &signals, std::ptr::null_mut());
libc::close(read_fd);
let flags = libc::fcntl(write_fd, libc::F_GETFD);
if flags < 0 || libc::fcntl(write_fd, libc::F_SETFD, flags & !libc::FD_CLOEXEC) < 0
{
return Err(std::io::Error::last_os_error());
}
Ok(())
});
}
let result = command.spawn();
.stderr(Stdio::inherit())
.spawn();
unsafe { libc::close(write_fd) };
let mut child = match result {
Ok(child) => child,
Expand Down
6 changes: 6 additions & 0 deletions src-tauri/crates/berd-call/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,12 @@ fn main() {
print_pretty_json(&response).unwrap_or_else(|error| operational_error("stop", error));
}
Some("session") => {
// A host blocks its stop signals, and the session inherits that mask.
unsafe {
let mut signals = std::mem::zeroed::<libc::sigset_t>();
libc::sigemptyset(&mut signals);
libc::pthread_sigmask(libc::SIG_SETMASK, &signals, std::ptr::null_mut());
}
let config = parse_or_exit(parse_args(&args), &args);
let pcm_output_fd = parse_or_exit(parse_pcm_output_fd(&args), &args);
let result = match config.mode {
Expand Down
38 changes: 38 additions & 0 deletions src-tauri/crates/berd-call/tests/session_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,44 @@ fn session_rejects_a_read_only_pcm_descriptor_before_hello() {
.contains("PCM output file descriptor is not writable"));
}

#[test]
fn a_session_stops_on_sigterm_even_when_its_host_blocks_it() {
let (mut command, _pcm, _host) = session_command();
unsafe {
command.pre_exec(|| {
// The host blocks its stop signals, and a spawned process inherits that mask.
let mut signals = std::mem::zeroed::<libc::sigset_t>();
libc::sigemptyset(&mut signals);
libc::sigaddset(&mut signals, libc::SIGINT);
libc::sigaddset(&mut signals, libc::SIGTERM);
libc::pthread_sigmask(libc::SIG_BLOCK, &signals, std::ptr::null_mut());
Ok(())
});
}
let mut child = command
.args(["--tts-backend", "openai"])
.env("OPENAI_API_KEY", "test-key-not-used")
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.unwrap();
unsafe { libc::kill(child.id() as i32, libc::SIGTERM) };
let deadline = std::time::Instant::now() + Duration::from_secs(5);
let status = loop {
if let Some(status) = child.try_wait().unwrap() {
break status;
}
if std::time::Instant::now() > deadline {
child.kill().unwrap();
panic!("the session ignored SIGTERM");
}
std::thread::sleep(Duration::from_millis(20));
};
use std::os::unix::process::ExitStatusExt;
assert_eq!(status.signal(), Some(libc::SIGTERM));
}

#[test]
fn framed_hello_reports_input_initialization_failure_before_ready() {
let missing =
Expand Down
Loading