nextest_runner/
double_spawn.rs1use std::path::Path;
31
32#[derive(Clone, Debug)]
37pub struct DoubleSpawnInfo {
38 inner: imp::DoubleSpawnInfo,
39}
40
41impl DoubleSpawnInfo {
42 pub const SUBCOMMAND_NAME: &'static str = "__double-spawn";
44
45 pub fn try_enable() -> Self {
49 Self {
50 inner: imp::DoubleSpawnInfo::try_enable(),
51 }
52 }
53
54 pub fn disabled() -> Self {
56 Self {
57 inner: imp::DoubleSpawnInfo::disabled(),
58 }
59 }
60
61 pub fn current_exe(&self) -> Option<&Path> {
65 self.inner.current_exe()
66 }
67
68 pub fn spawn_context(&self) -> Option<DoubleSpawnContext> {
70 self.current_exe().map(|_| DoubleSpawnContext::new())
71 }
72}
73
74#[derive(Debug)]
78pub struct DoubleSpawnContext {
79 #[expect(dead_code)]
81 inner: imp::DoubleSpawnContext,
82}
83
84impl DoubleSpawnContext {
85 #[inline]
86 fn new() -> Self {
87 Self {
88 inner: imp::DoubleSpawnContext::new(),
89 }
90 }
91
92 pub fn finish(self) {}
94}
95
96pub fn double_spawn_child_init() {
98 imp::double_spawn_child_init()
99}
100
101#[cfg(unix)]
102mod imp {
103 use super::*;
104 use nix::sys::signal::{SigSet, Signal};
105 use std::path::PathBuf;
106 use tracing::warn;
107
108 #[derive(Clone, Debug)]
109 pub(super) struct DoubleSpawnInfo {
110 current_exe: Option<PathBuf>,
111 }
112
113 impl DoubleSpawnInfo {
114 #[inline]
115 pub(super) fn try_enable() -> Self {
116 let current_exe = get_current_exe().map_or_else(
120 |error| {
121 warn!(
122 "unable to determine current exe, will not use double-spawning \
123 for better isolation: {error}"
124 );
125 None
126 },
127 Some,
128 );
129 Self { current_exe }
130 }
131
132 #[inline]
133 pub(super) fn disabled() -> Self {
134 Self { current_exe: None }
135 }
136
137 #[inline]
138 pub(super) fn current_exe(&self) -> Option<&Path> {
139 self.current_exe.as_deref()
140 }
141 }
142
143 #[cfg(target_os = "linux")]
144 fn get_current_exe() -> std::io::Result<PathBuf> {
145 static PROC_SELF_EXE: &str = "/proc/self/exe";
146
147 let path = Path::new(PROC_SELF_EXE);
150 match path.symlink_metadata() {
151 Ok(_) => Ok(path.to_owned()),
152 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err(std::io::Error::new(
153 std::io::ErrorKind::Other,
154 "no /proc/self/exe available. Is /proc mounted?",
155 )),
156 Err(e) => Err(e),
157 }
158 }
159
160 #[cfg(not(target_os = "linux"))]
163 #[inline]
164 fn get_current_exe() -> std::io::Result<PathBuf> {
165 std::env::current_exe()
166 }
167
168 #[derive(Debug)]
169 pub(super) struct DoubleSpawnContext {
170 to_unblock: Option<SigSet>,
171 }
172
173 impl DoubleSpawnContext {
174 #[inline]
175 pub(super) fn new() -> Self {
176 let mut sigset = SigSet::empty();
179 sigset.add(Signal::SIGTSTP);
180 let to_unblock = sigset.thread_block().ok().map(|()| sigset);
181 Self { to_unblock }
182 }
183 }
184
185 impl Drop for DoubleSpawnContext {
186 fn drop(&mut self) {
187 if let Some(sigset) = &self.to_unblock {
188 _ = sigset.thread_unblock();
189 }
190 }
191 }
192
193 #[inline]
194 pub(super) fn double_spawn_child_init() {
195 let mut sigset = SigSet::empty();
196 sigset.add(Signal::SIGTSTP);
197 if sigset.thread_unblock().is_err() {
198 warn!("[double-spawn] unable to unblock SIGTSTP in child");
199 }
200 }
201}
202
203#[cfg(not(unix))]
204mod imp {
205 use super::*;
206
207 #[derive(Clone, Debug)]
208 pub(super) struct DoubleSpawnInfo {}
209
210 impl DoubleSpawnInfo {
211 #[inline]
212 pub(super) fn try_enable() -> Self {
213 Self {}
214 }
215
216 #[inline]
217 pub(super) fn disabled() -> Self {
218 Self {}
219 }
220
221 #[inline]
222 pub(super) fn current_exe(&self) -> Option<&Path> {
223 None
224 }
225 }
226
227 #[derive(Debug)]
228 pub(super) struct DoubleSpawnContext {}
229
230 impl DoubleSpawnContext {
231 #[inline]
232 pub(super) fn new() -> Self {
233 Self {}
234 }
235 }
236
237 #[inline]
238 pub(super) fn double_spawn_child_init() {}
239}