You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
refactor(fspy-shm): move path ownership to fspy_shared
fspy_shm no longer generates backing paths or owns their lifetime: create
takes the path, remove is public, and ShmKeeper/id() are gone. The fspy
channel now generates the absolute uniquely-named path (converting long
paths to verbatim form up front via omnipath, so open and remove never
convert again), and holds its own keeper that removes the path on drop.
This stages the next step: with paths supplied by the caller, fspy_shm
can drop std entirely and take C-string paths.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`fspy_shm` is the private shared-memory layer used by fspy IPC channels. It gives the channel one API for creating a mapping, passing its identifier to another process, and opening additional views of the same bytes.
3
+
`fspy_shm` is the private shared-memory layer used by fspy IPC channels. It gives the channel one API for creating a mapping at a caller-chosen path, opening additional views of the same bytes from any process that knows the path, and removing the backing file.
4
4
5
-
`fspy_shm` exposes only the operations used by fspy. Treat an identifier as an opaque `OsStr`; do not depend on how it is built.
5
+
`fspy_shm` exposes only the operations used by fspy. The caller owns the path: it decides where the backing file lives, passes the same path to every process that opens the shared memory, and removes it when the shared memory is no longer needed. The fspy channel generates an absolute uniquely-named path in the system temporary directory and holds it in a keeper that removes it on drop.
6
6
7
7
## API
8
8
9
9
The public API is defined in [`src/lib.rs`](src/lib.rs).
|`create(size)`| Creates a zero-initialized backing file and returns its `ShmKeeper` and an `ShmHandle`. |
14
-
|`open(id)`| Opens an `ShmHandle` on the shared memory identified by `id`. |
15
-
|`ShmKeeper::id()`| Returns the identifier another process passes to `open`. |
13
+
|`create(path, size)`| Creates a zero-initialized backing file at `path` and returns an opened `ShmHandle`.|
14
+
|`open(path)`| Opens an `ShmHandle` on the shared memory backed by the file at `path`.|
15
+
|`remove(path)`| Removes the backing file. Later opens fail; existing handles and mappings keep working.|
16
16
|`ShmHandle::map()`| Maps the shared bytes. Callable more than once. |
17
17
|`Mapping::len()`| Returns the mapped size. |
18
18
|`Mapping::as_ptr()`| Returns a mutable raw pointer to the first byte. |
19
19
|`Mapping::as_slice()`| Returns the bytes as a shared slice. The caller must prevent mutation for its lifetime. |
20
20
21
-
`ShmKeeper` is the name: while it lives, `open` succeeds, and dropping it removes the backing file. `ShmHandle` is the opened file: `create` returns one so the creator never looks its own file up by name, and `open` returns one to everybody else. `Mapping` is the bytes: it keeps them alive until dropped and can do nothing else. None of the three synchronizes memory access. The fspy channel adds that on top with atomic frame headers and a lock file: senders hold a shared file lock while writing, and the receiver takes the exclusive lock before reading, which waits for existing senders and rejects new ones.
21
+
`ShmHandle` is the opened file: `create` returns one so the creator never looks its own file up by path, and `open` returns one to everybody else. `Mapping` is the bytes: it keeps them alive until dropped and can do nothing else. Neither synchronizes memory access. The fspy channel adds that on top with atomic frame headers and a lock file: senders hold a shared file lock while writing, and the receiver takes the exclusive lock before reading, which waits for existing senders and rejects new ones.
22
22
23
23
Every byte in a mapping returned by `create` is initially zero. `open` exposes the mapping's current contents and does not reinitialize them.
24
24
25
25
## Implementation
26
26
27
-
One implementation serves every platform: a sparse file named `vite-task-fspy-<uuid>.shm` directly in the system temporary directory. The identifier is the file's absolute path, so another process opens the mapping by opening that path. There is no broker, no global object name, and no asynchronous runtime. The files sit in the temporary directory itself rather than a shared subdirectory: a subdirectory would belong to whichever user created it first and block everyone else, while uniquely named `0o600` files in a sticky-bit directory work for all users.
27
+
One implementation serves every platform: a sparse file at the caller's path. Another process opens the mapping by opening that path. There is no broker, no global object name, and no asynchronous runtime.
28
28
29
29
Only written pages ever occupy memory or disk. The multi-gigabyte capacity fspy asks for therefore costs about as much as the data a run actually records.
30
30
31
-
Mapping goes through `memmap2` on every platform. The remaining platform-specific parts are three short passages:
31
+
Every operation goes through [`fspy_nostd`](../fspy_nostd) wrappers or direct Win32 calls. The platform-specific parts are three short passages:
| Same-user access |`mode(0o600)` on the backing file | the per-user `%TEMP%` ACL of the caller's chosen directory |
36
+
| Sparseness | file holes, produced by setting a length |`FSCTL_SET_SPARSE` before setting a length, or NTFS allocates every cluster |
37
+
| Removal | unlink the path | POSIX delete via `FileDispositionInfoEx`; see below |
39
38
40
39
`FILE_ATTRIBUTE_TEMPORARY` asks Windows to keep the data in memory when it can. Creation fails on a volume without sparse-file support.
41
40
42
-
The keeper removes the name with `remove_file`on every platform. Modern Windows deletes with POSIX semantics: the name goes away at once, while [existing handles keep working](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-_file_disposition_information_ex)and [mapped views keep the data alive](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingw) until the last one goes away. The first page also reserves the right to fail the delete while a mapped view exists, and Windows versions without POSIX delete do fail it. The keeper then falls back to reopening the file with `FILE_FLAG_DELETE_ON_CLOSE` and closing it, which deletes the file once every handle to it is closed.
41
+
`remove` unlinks the path on Unix. On Windows it relies on [POSIX delete semantics](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-_file_disposition_information_ex), which requires NTFS on Windows 10 1607 or newer: the name goes away at once, while existing handles keep working and [mapped views keep the data alive](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingw) until the last one goes away.
43
42
44
43
## Options considered
45
44
@@ -61,12 +60,10 @@ Earlier revisions rejected temporary files because dirty pages can reach disk. O
61
60
62
61
## Lifetime semantics
63
62
64
-
`create` returns the only keeper. `open` returns `ShmHandle`s.
63
+
- While the backing file exists, a process that knows the path can open the shared memory.
64
+
-`remove` deletes the backing file's name, so later opens fail. This is cleanup, not a stop signal: processes that already opened the shared memory keep reading and writing. The fspy channel stops writers with the close gate it stores in the shared bytes.
65
+
- An `ShmHandle` and its `Mapping`s stay usable after the backing file is removed. They keep the bytes alive and cannot restore the path.
65
66
66
-
- While the keeper is alive, a process that knows the identifier can open the shared memory.
67
-
- Dropping the keeper removes the backing file's name, so later opens fail. This is cleanup, not a stop signal: processes that already opened the shared memory keep reading and writing. The fspy channel stops writers with the close gate it stores in the shared bytes.
68
-
- An `ShmHandle` and its `Mapping`s stay usable after the keeper is gone. They keep the bytes alive and cannot extend the identifier's validity.
67
+
The channel guards the same window from its own side: [`ChannelConf::sender`](../fspy_shared/src/ipc/channel/mod.rs) opens and locks the receiver's exact lock-file path before it calls `fspy_shm::open`, and the receiver removes that path before removing the backing file, so a sender that starts later fails before opening shared memory.
69
68
70
-
The channel guards the same window from its own side: [`ChannelConf::sender`](../fspy_shared/src/ipc/channel/mod.rs) opens and locks the receiver's exact lock-file path before it calls `fspy_shm::open`, and the receiver removes that path before dropping the keeper, so a sender that starts later fails before opening shared memory.
71
-
72
-
If the keeper's process is killed, its `Drop` never runs and the file stays behind: on Unix for the system's temporary-file reaper, on Windows until a cleanup tool runs. The file costs about as much disk as the run wrote into it.
69
+
If the process that owns the path is killed before it calls `remove`, the file stays behind: on Unix for the system's temporary-file reaper, on Windows until a cleanup tool runs. The file costs about as much disk as the run wrote into it.
0 commit comments