@@ -4,7 +4,7 @@ mod shm_io;
44
55use std:: { env:: temp_dir, ffi:: OsStr , fs:: File , io, ops:: Deref , path:: PathBuf } ;
66
7- use allocator_api2:: alloc:: Global ;
7+ use allocator_api2:: alloc:: Allocator ;
88use fspy_nostd:: Fat ;
99use fspy_nostd_alloc:: OsCString ;
1010use fspy_shm:: Mapping ;
@@ -33,11 +33,17 @@ pub struct ChannelConf {
3333
3434/// Creates a mpsc IPC channel with one receiver and a `ChannelConf` that can be passed around processes and used to create multiple senders
3535#[ expect( clippy:: missing_errors_doc, reason = "non-vt crate: cannot use vt_str/vt_path types" ) ]
36- pub fn channel ( capacity : usize ) -> io:: Result < ( ChannelConf , Receiver ) > {
37- // Initialize the lock file with a unique name.
38- let lock_file_path = temp_dir ( ) . join ( format ! ( "fspy_ipc_{}.lock" , Uuid :: new_v4( ) ) ) ;
39-
40- let shm_c_path = os_c_string ( shm_backing_path ( ) ?. as_os_str ( ) ) ?;
36+ pub fn channel < A : Allocator > (
37+ capacity : usize ,
38+ allocator : A ,
39+ ) -> io:: Result < ( ChannelConf , Receiver < A > ) > {
40+ // Initialize the lock file with a unique name. `temp_dir` reflects
41+ // `TMPDIR` verbatim, which may be relative; the path travels to processes
42+ // with other working directories, so resolve it first.
43+ let lock_file_path =
44+ std:: path:: absolute ( temp_dir ( ) ) ?. join ( format ! ( "fspy_ipc_{}.lock" , Uuid :: new_v4( ) ) ) ;
45+
46+ let shm_c_path = os_c_string ( shm_backing_path ( ) ?. as_os_str ( ) , allocator) ?;
4147 let handle =
4248 fspy_shm:: create ( shm_c_path. as_c_str ( ) . as_thin ( ) , capacity) . map_err ( shm_error_to_io) ?;
4349 // The keeper exists from here on, so every error path below cleans up.
@@ -54,27 +60,27 @@ pub fn channel(capacity: usize) -> io::Result<(ChannelConf, Receiver)> {
5460}
5561
5662/// Encodes `path` as an owned NUL-terminated platform C string.
57- fn os_c_string ( path : & OsStr ) -> io:: Result < OsCString < Fat , Global > > {
58- let mut units = os_units ( path) ;
63+ fn os_c_string < A : Allocator > ( path : & OsStr , allocator : A ) -> io:: Result < OsCString < Fat , A > > {
64+ let mut units = os_units ( path, allocator ) ;
5965 units. push ( 0 ) ;
6066 OsCString :: from_vec_with_nul ( units)
6167 . ok_or_else ( || io:: Error :: new ( io:: ErrorKind :: InvalidInput , "path contains NUL" ) )
6268}
6369
6470#[ cfg( unix) ]
65- fn os_units ( path : & OsStr ) -> allocator_api2:: vec:: Vec < u8 > {
71+ fn os_units < A : Allocator > ( path : & OsStr , allocator : A ) -> allocator_api2:: vec:: Vec < u8 , A > {
6672 use std:: os:: unix:: ffi:: OsStrExt as _;
6773
68- let mut units = allocator_api2:: vec:: Vec :: with_capacity ( path. len ( ) + 1 ) ;
74+ let mut units = allocator_api2:: vec:: Vec :: with_capacity_in ( path. len ( ) + 1 , allocator ) ;
6975 units. extend_from_slice ( path. as_bytes ( ) ) ;
7076 units
7177}
7278
7379#[ cfg( windows) ]
74- fn os_units ( path : & OsStr ) -> allocator_api2:: vec:: Vec < u16 > {
80+ fn os_units < A : Allocator > ( path : & OsStr , allocator : A ) -> allocator_api2:: vec:: Vec < u16 , A > {
7581 use std:: os:: windows:: ffi:: OsStrExt as _;
7682
77- let mut units = allocator_api2:: vec:: Vec :: with_capacity ( path. len ( ) + 1 ) ;
83+ let mut units = allocator_api2:: vec:: Vec :: with_capacity_in ( path. len ( ) + 1 , allocator ) ;
7884 for unit in path. encode_wide ( ) {
7985 units. push ( unit) ;
8086 }
@@ -128,11 +134,11 @@ fn to_verbatim_if_long(path: PathBuf) -> io::Result<PathBuf> {
128134///
129135/// Removal is cleanup, not a stop signal: later opens fail, but existing
130136/// handles and mappings keep reading and writing; see [`fspy_shm::remove`].
131- struct ShmKeeper {
132- path : OsCString < Fat , Global > ,
137+ struct ShmKeeper < A : Allocator > {
138+ path : OsCString < Fat , A > ,
133139}
134140
135- impl Drop for ShmKeeper {
141+ impl < A : Allocator > Drop for ShmKeeper < A > {
136142 fn drop ( & mut self ) {
137143 let _ = fspy_shm:: remove ( self . path . as_c_str ( ) . as_thin ( ) ) ;
138144 }
@@ -146,15 +152,13 @@ impl ChannelConf {
146152 clippy:: missing_errors_doc,
147153 reason = "error conditions are self-evident from return type"
148154 ) ]
149- pub fn sender ( & self ) -> io:: Result < Sender > {
155+ pub fn sender < A : Allocator > ( & self , allocator : A ) -> io:: Result < Sender > {
150156 let lock_file = File :: open ( self . lock_file_path . to_cow_os_str ( ) ) ?;
151157 lock_file. try_lock_shared ( ) ?;
152158
153- // The arena never touches the process heap, so this stays safe in
154- // the preload contexts that create senders (pre-`main` constructors,
155- // the Windows loader lock).
156- let arena = fspy_nostd_alloc:: pooled_bump ( ) ;
157- let shm_path = self . shm_id . to_os_c_string_in ( & arena) . ok_or_else ( || {
159+ // The allocation is transient: the decoded path only has to outlive
160+ // the open call below.
161+ let shm_path = self . shm_id . to_os_c_string_in ( allocator) . ok_or_else ( || {
158162 io:: Error :: new ( io:: ErrorKind :: InvalidData , "invalid shared-memory path" )
159163 } ) ?;
160164 let mapping = fspy_shm:: open ( shm_path. as_c_str ( ) . as_thin ( ) )
@@ -200,31 +204,31 @@ unsafe impl Sync for Sender {}
200204
201205/// The unique receiver side of an IPC channel.
202206/// Owns the lock file and removes it on drop.
203- pub struct Receiver {
207+ pub struct Receiver < A : Allocator > {
204208 lock_file_path : PathBuf ,
205209 lock_file : File ,
206210 /// Keeps the shared memory's backing file alive for as long as senders
207211 /// may attach.
208- _keeper : ShmKeeper ,
212+ _keeper : ShmKeeper < A > ,
209213 mapping : Mapping ,
210214}
211215
212216/// SAFETY: `Receiver` doesn't read or write `shm`. It only passes it to `ReceiverLockGuard` under the lock.
213- unsafe impl Send for Receiver { }
217+ unsafe impl < A : Allocator + Send > Send for Receiver < A > { }
214218
215219/// SAFETY: `Receiver` doesn't read or write `shm`. It only passes it to `ReceiverLockGuard` under the lock.
216- unsafe impl Sync for Receiver { }
220+ unsafe impl < A : Allocator + Sync > Sync for Receiver < A > { }
217221
218- impl Drop for Receiver {
222+ impl < A : Allocator > Drop for Receiver < A > {
219223 fn drop ( & mut self ) {
220224 if let Err ( err) = std:: fs:: remove_file ( & self . lock_file_path ) {
221225 debug ! ( "Failed to remove IPC lock file {}: {}" , self . lock_file_path. display( ) , err) ;
222226 }
223227 }
224228}
225229
226- impl Receiver {
227- fn new ( lock_file_path : PathBuf , keeper : ShmKeeper , mapping : Mapping ) -> io:: Result < Self > {
230+ impl < A : Allocator > Receiver < A > {
231+ fn new ( lock_file_path : PathBuf , keeper : ShmKeeper < A > , mapping : Mapping ) -> io:: Result < Self > {
228232 let lock_file = File :: create ( & lock_file_path) ?;
229233 Ok ( Self { lock_file_path, lock_file, _keeper : keeper, mapping } )
230234 }
@@ -269,6 +273,7 @@ impl<'a> Deref for ReceiverLockGuard<'a> {
269273mod tests {
270274 use std:: { ffi:: OsString , fs, num:: NonZeroUsize , str:: from_utf8} ;
271275
276+ use allocator_api2:: alloc:: Global ;
272277 use bstr:: B ;
273278 use subprocess_test:: command_for_fn;
274279
@@ -279,12 +284,12 @@ mod tests {
279284 /// must still attach.
280285 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
281286 async fn sender_ignores_changed_temp_and_working_directory ( ) {
282- let ( conf, receiver) = channel ( 100 ) . unwrap ( ) ;
287+ let ( conf, receiver) = channel ( 100 , Global ) . unwrap ( ) ;
283288 let changed_cwd = temp_dir ( ) . join ( format ! ( "fspy-ipc-changed-cwd-{}" , Uuid :: new_v4( ) ) ) ;
284289 fs:: create_dir ( & changed_cwd) . unwrap ( ) ;
285290
286291 let mut command = command_for_fn ! ( conf, |conf: ChannelConf | {
287- let sender = conf. sender( ) . unwrap( ) ;
292+ let sender = conf. sender( Global ) . unwrap( ) ;
288293 let frame_size = NonZeroUsize :: new( 2 ) . unwrap( ) ;
289294 let mut frame = sender. claim_frame( frame_size) . unwrap( ) ;
290295 frame. copy_from_slice( & [ 4 , 2 ] ) ;
@@ -303,9 +308,9 @@ mod tests {
303308
304309 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
305310 async fn smoke ( ) {
306- let ( conf, receiver) = channel ( 100 ) . unwrap ( ) ;
311+ let ( conf, receiver) = channel ( 100 , Global ) . unwrap ( ) ;
307312 let cmd = command_for_fn ! ( conf, |conf: ChannelConf | {
308- let sender = conf. sender( ) . unwrap( ) ;
313+ let sender = conf. sender( Global ) . unwrap( ) ;
309314 let frame_size = NonZeroUsize :: new( 2 ) . unwrap( ) ;
310315 let mut frame = sender. claim_frame( frame_size) . unwrap( ) ;
311316 frame. copy_from_slice( & [ 4 , 2 ] ) ;
@@ -324,11 +329,11 @@ mod tests {
324329 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
325330 #[ expect( clippy:: print_stdout, reason = "test diagnostics" ) ]
326331 async fn forbid_new_senders_after_locked ( ) {
327- let ( conf, receiver) = channel ( 42 ) . unwrap ( ) ;
332+ let ( conf, receiver) = channel ( 42 , Global ) . unwrap ( ) ;
328333 let _lock = receiver. lock ( ) . unwrap ( ) ;
329334
330335 let cmd = command_for_fn ! ( conf, |conf: ChannelConf | {
331- print!( "{}" , conf. sender( ) . is_ok( ) ) ;
336+ print!( "{}" , conf. sender( Global ) . is_ok( ) ) ;
332337 } ) ;
333338 let output = std:: process:: Command :: from ( cmd) . output ( ) . unwrap ( ) ;
334339 assert_eq ! ( B ( & output. stdout) , B ( "false" ) ) ;
@@ -337,22 +342,22 @@ mod tests {
337342 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
338343 #[ expect( clippy:: print_stdout, reason = "test diagnostics" ) ]
339344 async fn forbid_new_senders_after_receiver_dropped ( ) {
340- let ( conf, receiver) = channel ( 42 ) . unwrap ( ) ;
345+ let ( conf, receiver) = channel ( 42 , Global ) . unwrap ( ) ;
341346 drop ( receiver) ;
342347
343348 let cmd = command_for_fn ! ( conf, |conf: ChannelConf | {
344- print!( "{}" , conf. sender( ) . is_ok( ) ) ;
349+ print!( "{}" , conf. sender( Global ) . is_ok( ) ) ;
345350 } ) ;
346351 let output = std:: process:: Command :: from ( cmd) . output ( ) . unwrap ( ) ;
347352 assert_eq ! ( B ( & output. stdout) , B ( "false" ) ) ;
348353 }
349354
350355 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
351356 async fn concurrent_senders ( ) {
352- let ( conf, receiver) = channel ( 8192 ) . unwrap ( ) ;
357+ let ( conf, receiver) = channel ( 8192 , Global ) . unwrap ( ) ;
353358 for i in 0u16 ..200 {
354359 let cmd = command_for_fn ! ( ( conf. clone( ) , i) , |( conf, i) : ( ChannelConf , u16 ) | {
355- let sender = conf. sender( ) . unwrap( ) ;
360+ let sender = conf. sender( Global ) . unwrap( ) ;
356361 let data_to_send = i. to_string( ) ;
357362 sender
358363 . claim_frame( NonZeroUsize :: new( data_to_send. len( ) ) . unwrap( ) )
0 commit comments