From c39fdc512e8df2a41c5ff578fcd41b79c3ddcbec Mon Sep 17 00:00:00 2001 From: Simonas Kazlauskas Date: Fri, 20 Feb 2026 22:08:54 +0200 Subject: [PATCH] feat(spawning): make niri spawning work better with PR_SET_PDEATHSIG `PR_SET_PDEATHSIG` as commonly used by bubblewrap (`--die-with-parent`) which niri in turn uses to wrap `steam` and `heroic` appears to keep track not only of the reaper process but also the thread that spawned the command. This is extremely unfortunate, but this seems reasonably straightforward to make better on `niri` side. I quickly whipped this up and confirmed that niri with this change now can spawn `steam` or `heroic` on my system. This may also be ever-so-slightly faster compared to creating a full thread on every command invocation. --- src/cli.rs | 3 +-- src/utils/spawning.rs | 32 ++++++++++++++++++++------------ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 308b3127b7..3f44974ac9 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,3 @@ -use std::ffi::OsString; use std::path::PathBuf; use clap::{Parser, Subcommand}; @@ -28,7 +27,7 @@ pub struct Cli { pub session: bool, /// Command to run upon compositor startup. #[arg(last = true)] - pub command: Vec, + pub command: Vec, #[command(subcommand)] pub subcommand: Option, diff --git a/src/utils/spawning.rs b/src/utils/spawning.rs index f5b2cb47a0..28f5686c4f 100644 --- a/src/utils/spawning.rs +++ b/src/utils/spawning.rs @@ -3,7 +3,7 @@ use std::os::unix::process::CommandExt; use std::path::Path; use std::process::{Child, Command, Stdio}; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::RwLock; +use std::sync::{mpsc, RwLock}; use std::{io, thread}; use atomic::Atomic; @@ -63,24 +63,32 @@ pub fn restore_nofile_rlimit() { } /// Spawns the command to run independently of the compositor. -pub fn spawn + Send + 'static>(command: Vec, token: Option) { +pub fn spawn(command: Vec, token: Option) { let _span = tracy_client::span!(); if command.is_empty() { return; } - // Spawning and waiting takes some milliseconds, so do it in a thread. - let res = thread::Builder::new() - .name("Command Spawner".to_owned()) - .spawn(move || { - let (command, args) = command.split_first().unwrap(); - spawn_sync(command, args, token); + type Message = (Vec, Option); + static SPAWNING_CHANNEL: std::sync::LazyLock> = + std::sync::LazyLock::new(|| { + let (sender, receiver) = mpsc::sync_channel::(16); + thread::Builder::new() + .name("Command Spawner".to_owned()) + .stack_size(96 * 1024) + .spawn(move || { + for (command, token) in receiver.into_iter() { + let (command, args) = command.split_first().unwrap(); + spawn_sync(command, args, token); + } + }) + .unwrap(); + sender }); - - if let Err(err) = res { - warn!("error spawning a thread to spawn the command: {err:?}"); - } + if let Err(err) = SPAWNING_CHANNEL.send((command, token)) { + warn!("error sending command to the spawning thread: {err:?}"); + }; } /// Spawns the command through the shell.