Skip to content

Commit 67534d3

Browse files
wan9chiGPT-5.6
andcommitted
fix(fspy): use memfd for Linux shared memory
Co-authored-by: GPT-5.6 <gpt-5.6@openai.com>
1 parent ae72983 commit 67534d3

11 files changed

Lines changed: 725 additions & 34 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
- **Fixed** Windows automatic input tracking now records executable image reads when process creation performs the lookup inside `NtCreateUserProcess` ([#518](https://github.com/voidzero-dev/vite-task/pull/518)).
44
- **Fixed** Failures while waiting for a started task process to exit no longer incorrectly say the process failed to spawn ([#515](https://github.com/voidzero-dev/vite-task/pull/515)).
55
- **Fixed** Missing env vars requested through `@voidzero-dev/vite-task-client` now return `undefined` instead of `null`, preserving Vite production `NODE_ENV` semantics when builds run through `vp run` ([#508](https://github.com/voidzero-dev/vite-task/pull/508)).
6+
- **Fixed** Linux file-access tracking no longer consumes the `/dev/shm` mount used by containers and Kubernetes runners ([#353](https://github.com/voidzero-dev/vite-task/issues/353)).
67
- **Fixed** Windows builds no longer hang on CI when a `node_modules/.bin` `.cmd` shim is routed through PowerShell: the npm/pnpm/yarn `.ps1` wrappers read stdin and block forever on a non-TTY pipe, so the PowerShell rewrite is now skipped when stdin is not an interactive terminal, falling back to the `.cmd` (which never reads stdin) ([#491](https://github.com/voidzero-dev/vite-task/pull/491)).
78
- **Added** First-party support for caching `vite build` with zero cache config, giving Vite projects correct cache hits out of the box ([vitejs/vite#22453](https://github.com/vitejs/vite/pull/22453)).
89
- **Added** Support for specifying tasks from dependency packages in `dependsOn`, such as `dependsOn: [{ "task": "build", "from": "dependencies" }]` ([#479](https://github.com/voidzero-dev/vite-task/pull/479)).

Cargo.lock

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ ref-cast = "1.0.24"
116116
regex = "1.11.3"
117117
rusqlite = "0.39.0"
118118
rustc-hash = "2.1.1"
119+
rustix = "1.1"
119120
# SeccompAction::UserNotif (SECCOMP_RET_USER_NOTIF) was added after the latest published release (v0.5.0)
120121
seccompiler = { git = "https://github.com/rust-vmm/seccompiler", rev = "08587106340b8e3cb361c7561411510039436857" }
121122
serde = "1.0.219"

crates/fspy_shared/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ ctor = { workspace = true }
2828
rustc-hash = { workspace = true }
2929
subprocess_test = { workspace = true }
3030

31+
[target.'cfg(target_os = "linux")'.dev-dependencies]
32+
tokio = { workspace = true, features = ["net", "rt-multi-thread", "time"] }
33+
3134
[lints]
3235
workspace = true
3336

crates/fspy_shared/src/ipc/channel/mod.rs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,12 @@ impl Deref for Sender {
139139
}
140140
}
141141

142-
#[expect(
143-
clippy::non_send_fields_in_send_ty,
144-
reason = "`Sender` holds a shared file lock that ensures there's no reader, so `shm` can be safely written to"
142+
#[cfg_attr(
143+
not(target_os = "linux"),
144+
expect(
145+
clippy::non_send_fields_in_send_ty,
146+
reason = "`Sender` holds a shared file lock that ensures there's no reader, so `shm` can be safely written to"
147+
)
145148
)]
146149
/// SAFETY: `Sender` holds a shared file lock that ensures there's no reader, so `shm` can be safely written to.
147150
unsafe impl Send for Sender {}
@@ -157,9 +160,12 @@ pub struct Receiver {
157160
shm: Shm,
158161
}
159162

160-
#[expect(
161-
clippy::non_send_fields_in_send_ty,
162-
reason = "Receiver doesn't read or write `shm`. It only pass it to `ReceiverLockGuard` under the lock"
163+
#[cfg_attr(
164+
not(target_os = "linux"),
165+
expect(
166+
clippy::non_send_fields_in_send_ty,
167+
reason = "Receiver doesn't read or write `shm`. It only passes it to `ReceiverLockGuard` under the lock"
168+
)
163169
)]
164170
/// SAFETY: `Receiver` doesn't read or write `shm`. It only passes it to `ReceiverLockGuard` under the lock.
165171
unsafe impl Send for Receiver {}
@@ -229,6 +235,8 @@ mod tests {
229235
#[test]
230236
fn smoke() {
231237
let created = channel(100).unwrap();
238+
#[cfg(target_os = "linux")]
239+
let (broker_runtime, broker_handle) = start_test_broker(created.broker);
232240
let conf = created.conf;
233241
let receiver = created.receiver;
234242
let cmd = command_for_fn!(conf, |conf: ChannelConf| {
@@ -239,6 +247,9 @@ mod tests {
239247
});
240248
assert!(std::process::Command::from(cmd).status().unwrap().success());
241249

250+
#[cfg(target_os = "linux")]
251+
stop_test_broker(&broker_runtime, broker_handle);
252+
242253
let lock = receiver.lock().unwrap();
243254
let mut frames = lock.iter_frames();
244255

@@ -252,6 +263,8 @@ mod tests {
252263
#[expect(clippy::print_stdout, reason = "test diagnostics")]
253264
fn forbid_new_senders_after_locked() {
254265
let created = channel(42).unwrap();
266+
#[cfg(target_os = "linux")]
267+
let (broker_runtime, broker_handle) = start_test_broker(created.broker);
255268
let conf = created.conf;
256269
let receiver = created.receiver;
257270
let _lock = receiver.lock().unwrap();
@@ -261,12 +274,16 @@ mod tests {
261274
});
262275
let output = std::process::Command::from(cmd).output().unwrap();
263276
assert_eq!(B(&output.stdout), B("false"));
277+
#[cfg(target_os = "linux")]
278+
stop_test_broker(&broker_runtime, broker_handle);
264279
}
265280

266281
#[test]
267282
#[expect(clippy::print_stdout, reason = "test diagnostics")]
268283
fn forbid_new_senders_after_receiver_dropped() {
269284
let created = channel(42).unwrap();
285+
#[cfg(target_os = "linux")]
286+
let (broker_runtime, broker_handle) = start_test_broker(created.broker);
270287
let conf = created.conf;
271288
let receiver = created.receiver;
272289
drop(receiver);
@@ -276,11 +293,15 @@ mod tests {
276293
});
277294
let output = std::process::Command::from(cmd).output().unwrap();
278295
assert_eq!(B(&output.stdout), B("false"));
296+
#[cfg(target_os = "linux")]
297+
stop_test_broker(&broker_runtime, broker_handle);
279298
}
280299

281300
#[test]
282301
fn concurrent_senders() {
283302
let created = channel(8192).unwrap();
303+
#[cfg(target_os = "linux")]
304+
let (broker_runtime, broker_handle) = start_test_broker(created.broker);
284305
let conf = created.conf;
285306
let receiver = created.receiver;
286307
for i in 0u16..200 {
@@ -300,6 +321,8 @@ mod tests {
300321
B(&output.stderr)
301322
);
302323
}
324+
#[cfg(target_os = "linux")]
325+
stop_test_broker(&broker_runtime, broker_handle);
303326
let lock = receiver.lock().unwrap();
304327
let mut received_values: Vec<u16> = lock
305328
.iter_frames()
@@ -308,4 +331,23 @@ mod tests {
308331
received_values.sort_unstable();
309332
assert_eq!(received_values, (0u16..200).collect::<Vec<u16>>());
310333
}
334+
335+
#[cfg(target_os = "linux")]
336+
fn start_test_broker(
337+
broker: ShmBroker,
338+
) -> (tokio::runtime::Runtime, tokio::task::JoinHandle<std::io::Result<()>>) {
339+
let runtime =
340+
tokio::runtime::Builder::new_multi_thread().enable_io().enable_time().build().unwrap();
341+
let handle = runtime.spawn(broker);
342+
(runtime, handle)
343+
}
344+
345+
#[cfg(target_os = "linux")]
346+
fn stop_test_broker(
347+
runtime: &tokio::runtime::Runtime,
348+
broker: tokio::task::JoinHandle<std::io::Result<()>>,
349+
) {
350+
broker.abort();
351+
assert!(runtime.block_on(broker).unwrap_err().is_cancelled());
352+
}
311353
}

crates/fspy_shared/src/ipc/channel/shm_io.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,10 @@ mod tests {
674674

675675
let created = fspy_shm::create(SHM_SIZE).unwrap();
676676
#[cfg(target_os = "linux")]
677-
let _broker = created.broker;
677+
let broker_runtime =
678+
tokio::runtime::Builder::new_multi_thread().enable_io().enable_time().build().unwrap();
679+
#[cfg(target_os = "linux")]
680+
let broker_handle = broker_runtime.spawn(created.broker);
678681
let shm = created.shm;
679682
let shm_name = shm.id().to_owned();
680683

@@ -703,6 +706,12 @@ mod tests {
703706
assert!(status.success());
704707
}
705708

709+
#[cfg(target_os = "linux")]
710+
{
711+
broker_handle.abort();
712+
assert!(broker_runtime.block_on(broker_handle).unwrap_err().is_cancelled());
713+
}
714+
706715
// SAFETY: All child processes have exited (waited above), so no concurrent writers exist.
707716
// The shared memory is valid and fully written.
708717
let shm = unsafe { shm.as_slice() };

crates/fspy_shm/Cargo.toml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,23 @@ license.workspace = true
77
publish = false
88
rust-version.workspace = true
99

10-
[dependencies]
10+
[target.'cfg(not(target_os = "linux"))'.dependencies]
1111
shared_memory = { workspace = true, features = ["logging"] }
1212

13+
[target.'cfg(target_os = "linux")'.dependencies]
14+
base64 = { workspace = true }
15+
memmap2 = { workspace = true }
16+
rustix = { workspace = true, features = ["fs", "net"] }
17+
tokio = { workspace = true, features = ["macros", "net", "rt", "time"] }
18+
uuid = { workspace = true, features = ["v4"] }
19+
1320
[dev-dependencies]
1421
ctor = { workspace = true }
1522
subprocess_test = { workspace = true }
1623

24+
[target.'cfg(target_os = "linux")'.dev-dependencies]
25+
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
26+
1727
[lints]
1828
workspace = true
1929

0 commit comments

Comments
 (0)