Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
29e0704
refactor(fspy): own Windows shared-memory mapping
wan9chi Jul 10, 2026
fa158a6
docs(fspy): explain Windows import linkage
wan9chi Jul 10, 2026
8b27495
refactor(fspy): simplify Windows shared memory
wan9chi Jul 10, 2026
99344ab
perf(fspy): use sparse Windows shared memory
wan9chi Jul 9, 2026
7426a27
docs(fspy): explain Windows shared memory design
wan9chi Jul 10, 2026
ded5b36
refactor(fspy): simplify sparse Windows backend
wan9chi Jul 10, 2026
ea957d6
refactor(fspy): remove redundant Windows id validation
wan9chi Jul 10, 2026
bffde97
refactor(fspy): inline Windows sparse setup
wan9chi Jul 10, 2026
528e9f3
docs: clarify Windows shared memory improvement
wan9chi Jul 10, 2026
c0667f4
refactor(fspy): remove redundant Windows open validation
wan9chi Jul 10, 2026
59ce6aa
refactor(fspy): require sparse Windows backing files
wan9chi Jul 10, 2026
cbb23bc
docs(fspy): explain Windows shared memory design
wan9chi Jul 10, 2026
90681f6
docs(fspy): fix Windows backend rustdoc
wan9chi Jul 10, 2026
0c32f10
docs(fspy): focus Windows docs on design choices
wan9chi Jul 10, 2026
3bc7988
docs(fspy): tighten Windows backend decision record
wan9chi Jul 10, 2026
ff50a42
docs(fspy): keep Windows rationale prose continuous
wan9chi Jul 10, 2026
07bc5d1
fix(fspy): retain Windows mapping handles
wan9chi Jul 10, 2026
a1c2c70
docs(fspy): use direct Windows shared memory wording
wan9chi Jul 10, 2026
03396df
docs(fspy): remove hard wraps from Windows README
wan9chi Jul 10, 2026
80dfb07
refactor(fspy): derive Windows shared memory size
wan9chi Jul 10, 2026
e476b9c
docs(fspy): omit Windows mapping mechanics
wan9chi Jul 10, 2026
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Changelog

- **Improved** Windows file-access tracking now uses sparse temporary backing files where supported, avoiding upfront allocation of the full backing file on disk ([#524](https://github.com/voidzero-dev/vite-task/pull/524)).
- **Fixed** Automatic file-access tracking on Linux now works in containers and Kubernetes runners with limited `/dev/shm` space ([#353](https://github.com/voidzero-dev/vite-task/issues/353), [#523](https://github.com/voidzero-dev/vite-task/pull/523)).
- **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)).
- **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)).
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ wax = "0.7.0"
which = "8.0.0"
widestring = "1.2.0"
winapi = "0.3.9"
windows-sys = "0.61"
winsafe = { version = "0.0.27", features = ["kernel"] }
xxhash-rust = { version = "0.8.15", features = ["const_xxh3"] }
ntest = "0.9.5"
Expand Down
1 change: 1 addition & 0 deletions crates/fspy_preload_windows/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ winapi = { workspace = true, features = [
"memoryapi",
"std",
] }
windows-sys = { workspace = true, features = ["Win32_Foundation", "Win32_UI_Shell"] }
winsafe = { workspace = true }

[target.'cfg(target_os = "windows")'.dev-dependencies]
Expand Down
19 changes: 6 additions & 13 deletions crates/fspy_preload_windows/src/windows/winapi_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use widestring::{U16CStr, U16Str};
use winapi::{
ctypes::c_long,
shared::{
minwindef::{BOOL, FALSE, HLOCAL, MAX_PATH, ULONG},
ntdef::{HANDLE, HRESULT, PCWSTR, PWSTR, UNICODE_STRING},
minwindef::{BOOL, FALSE, MAX_PATH},
ntdef::{HANDLE, PWSTR, UNICODE_STRING},
winerror::{NO_ERROR, S_OK},
},
um::{
Expand All @@ -18,6 +18,10 @@ use winapi::{
},
},
};
use windows_sys::Win32::{
Foundation::LocalFree,
UI::Shell::{PATHCCH_ALLOW_LONG_PATHS, PathAllocCombine},
};
use winsafe::{GetLastError, co};

pub fn ck(b: BOOL) -> winsafe::SysResult<()> {
Expand Down Expand Up @@ -99,16 +103,6 @@ pub const fn access_mask_to_mode(desired_access: ACCESS_MASK) -> AccessMode {
}
}

unsafe extern "system" {
fn LocalFree(hmem: HLOCAL) -> HLOCAL;
fn PathAllocCombine(
pszpathin: PCWSTR,
pszmore: PCWSTR,
dwflags: ULONG,
ppszpathout: *mut PWSTR,
) -> HRESULT;
}

pub struct HeapPath(PWSTR);
impl HeapPath {
#[must_use]
Expand All @@ -125,7 +119,6 @@ impl Drop for HeapPath {
}

pub fn combine_paths(path1: &U16CStr, path2: &U16CStr) -> winsafe::SysResult<HeapPath> {
const PATHCCH_ALLOW_LONG_PATHS: ULONG = 0x0000_0001;
let mut out = std::ptr::null_mut();
// SAFETY: FFI call to PathAllocCombine with valid null-terminated wide string pointers
let hr = unsafe {
Expand Down
13 changes: 12 additions & 1 deletion crates/fspy_shm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ license.workspace = true
publish = false
rust-version.workspace = true

[target.'cfg(not(target_os = "linux"))'.dependencies]
[target.'cfg(not(any(target_os = "linux", target_os = "windows")))'.dependencies]
shared_memory = { workspace = true, features = ["logging"] }

[target.'cfg(target_os = "linux")'.dependencies]
Expand All @@ -19,6 +19,17 @@ tokio-util = { workspace = true }
tracing = { workspace = true }
uuid = { workspace = true, features = ["v4"] }

[target.'cfg(target_os = "windows")'.dependencies]
uuid = { workspace = true, features = ["v4"] }
windows-sys = { workspace = true, features = [
"Win32_Foundation",
"Win32_Security",
"Win32_Storage_FileSystem",
"Win32_System_IO",
"Win32_System_Ioctl",
"Win32_System_Memory",
] }

[dev-dependencies]
ctor = { workspace = true }
subprocess_test = { workspace = true }
Expand Down
3 changes: 2 additions & 1 deletion crates/fspy_shm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ The channel hides that difference with its lock file. [`ChannelConf::sender`](..
Each platform keeps its implementation rationale beside its source:

- [Linux: `memfd` with a descriptor broker](src/linux/README.md)
- [Windows: sparse file-backed named mapping](src/windows/README.md)

At this point in the stack, Windows and macOS still delegate mapping creation and opening to the [`shared_memory`](https://crates.io/crates/shared_memory) crate.
At this point in the stack, macOS still delegates mapping creation and opening to the [`shared_memory`](https://crates.io/crates/shared_memory) crate.
20 changes: 10 additions & 10 deletions crates/fspy_shm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
#![doc = include_str!("../README.md")]

#[cfg(not(target_os = "linux"))]
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
use std::io;

#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "windows")]
mod windows;

#[cfg(target_os = "linux")]
pub use linux::{Shm, create, open};
#[cfg(not(target_os = "linux"))]
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
use shared_memory::{Shmem, ShmemConf};
#[cfg(target_os = "windows")]
pub use windows::{Shm, create, open};

/// An owned shared-memory mapping.
#[cfg(not(target_os = "linux"))]
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
pub struct Shm {
inner: Shmem,
}
Expand All @@ -26,11 +30,9 @@ pub struct Shm {
/// # Errors
///
/// Returns an error if the platform cannot create or map the region.
#[cfg(not(target_os = "linux"))]
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
pub fn create(size: usize) -> io::Result<Shm> {
let conf = ShmemConf::new().size(size);
#[cfg(target_os = "windows")]
let conf = conf.allow_raw(true);

let inner = conf.create().map_err(io::Error::other)?;
Ok(Shm { inner })
Expand All @@ -45,17 +47,15 @@ pub fn create(size: usize) -> io::Result<Shm> {
/// # Errors
///
/// Returns an error if the mapping does not exist or cannot be mapped.
#[cfg(not(target_os = "linux"))]
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
pub fn open(id: &str) -> io::Result<Shm> {
let conf = ShmemConf::new().os_id(id);
#[cfg(target_os = "windows")]
let conf = conf.allow_raw(true);

let inner = conf.open().map_err(io::Error::other)?;
Ok(Shm { inner })
}

#[cfg(not(target_os = "linux"))]
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
#[expect(clippy::len_without_is_empty, reason = "shared-memory mappings are always non-empty")]
impl Shm {
/// Returns this mapping's opaque platform identifier.
Expand Down
23 changes: 23 additions & 0 deletions crates/fspy_shm/src/windows/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Windows backend

The Windows backend maps a sparse temporary file and gives the mapping a Windows section name. Another process opens the same section using only that name. Creating the mapping does not reserve its full size in system commit or disk space.

## Options considered

| Option | Decision |
| ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| Paging-file-backed section | Rejected because the section's full size is charged against system commit. |
| Reserved section with incremental `VirtualAlloc(MEM_COMMIT)` | Rejected because writers would need to coordinate when committing more pages. |
| Sparse temporary file with a named section | Selected. Disk blocks are allocated only for written ranges, and another process can open the mapping using the section name. |

`FILE_ATTRIBUTE_TEMPORARY` tells Windows to keep file data in memory when possible, but Windows may still write dirty pages to disk under memory pressure. Creation fails if the temporary volume does not support sparse files.

## Why not `shared_memory`

`shared_memory` creates and extends a regular file before fspy can mark it sparse. It therefore cannot ensure that untouched ranges use no disk blocks.

`shared_memory` also uses the file path to identify the mapping. Fspy uses the section name, so another process does not need the creator's temporary-file path.

## Lifetime semantics

Dropping the owner unmaps its view and closes the delete-on-close backing file. Existing views keep the section alive. The section name may remain openable during that period. `ChannelConf::sender` checks the receiver's lock file first to prevent new senders after the receiver has shut down.
Loading
Loading