@@ -6,7 +6,7 @@ use std::io::{self, Read, Write};
6
6
use std:: mem;
7
7
use std:: mem:: MaybeUninit ;
8
8
use std:: os:: unix:: prelude:: * ;
9
- use std:: path:: { Path , PathBuf } ;
9
+ use std:: path:: Path ;
10
10
use std:: process:: Command ;
11
11
use std:: ptr;
12
12
use std:: sync:: {
@@ -17,17 +17,27 @@ use std::thread::{self, Builder, JoinHandle};
17
17
use std:: time:: Duration ;
18
18
19
19
#[ derive( Debug ) ]
20
- pub enum Client {
21
- /// `--jobserver-auth=R,W`
22
- Pipe { read : File , write : File } ,
23
- /// `--jobserver-auth=fifo:PATH`
24
- Fifo {
25
- file : File ,
26
- path : PathBuf ,
27
- /// it can only go from false -> true but not the other way around, since that
28
- /// could cause a race condition.
29
- is_non_blocking : AtomicBool ,
30
- } ,
20
+ /// This preserves the `--jobserver-auth` type at creation time,
21
+ /// so auth type will be passed down to and inherit from sub-Make processes correctly.
22
+ ///
23
+ /// See <https://github.com/rust-lang/jobserver-rs/issues/99> for details.
24
+ enum ClientCreationArg {
25
+ Fds { read : c_int , write : c_int } ,
26
+ Fifo ( Box < Path > ) ,
27
+ }
28
+
29
+ #[ derive( Debug ) ]
30
+ pub struct Client {
31
+ read : File ,
32
+ write : File ,
33
+ creation_arg : ClientCreationArg ,
34
+ /// It is set to `None` if the pipe is shared with other processes, so it
35
+ /// cannot support non-blocking mode.
36
+ ///
37
+ /// If it is set to `Some`, then it can only go from
38
+ /// `Some(false)` -> `Some(true)` but not the other way around,
39
+ /// since that could cause a race condition.
40
+ is_non_blocking : Option < AtomicBool > ,
31
41
}
32
42
33
43
#[ derive( Debug ) ]
@@ -43,7 +53,7 @@ impl Client {
43
53
// wrong!
44
54
const BUFFER : [ u8 ; 128 ] = [ b'|' ; 128 ] ;
45
55
46
- let mut write = client. write ( ) ;
56
+ let mut write = & client. write ;
47
57
48
58
set_nonblocking ( write. as_raw_fd ( ) , true ) ?;
49
59
@@ -111,16 +121,24 @@ impl Client {
111
121
FromEnvErrorInner :: CannotParse ( "expected a path after `fifo:`" . to_string ( ) )
112
122
} ) ?;
113
123
let path = Path :: new ( path_str) ;
114
- let file = OpenOptions :: new ( )
115
- . read ( true )
116
- . write ( true )
117
- . open ( path)
118
- . map_err ( |err| FromEnvErrorInner :: CannotOpenPath ( path_str. to_string ( ) , err) ) ?;
119
-
120
- Ok ( Some ( Client :: Fifo {
121
- file,
122
- path : path. into ( ) ,
123
- is_non_blocking : AtomicBool :: new ( false ) ,
124
+
125
+ let open_file = || {
126
+ // Opening with read write is necessary, since opening with
127
+ // read-only or write-only could block the thread until another
128
+ // thread opens it with write-only or read-only (or RDWR)
129
+ // correspondingly.
130
+ OpenOptions :: new ( )
131
+ . read ( true )
132
+ . write ( true )
133
+ . open ( path)
134
+ . map_err ( |err| FromEnvErrorInner :: CannotOpenPath ( path_str. to_string ( ) , err) )
135
+ } ;
136
+
137
+ Ok ( Some ( Client {
138
+ read : open_file ( ) ?,
139
+ write : open_file ( ) ?,
140
+ creation_arg : ClientCreationArg :: Fifo ( path. into ( ) ) ,
141
+ is_non_blocking : Some ( AtomicBool :: new ( false ) ) ,
124
142
} ) )
125
143
}
126
144
@@ -148,6 +166,8 @@ impl Client {
148
166
return Err ( FromEnvErrorInner :: NegativeFd ( write) ) ;
149
167
}
150
168
169
+ let creation_arg = ClientCreationArg :: Fds { read, write } ;
170
+
151
171
// Ok so we've got two integers that look like file descriptors, but
152
172
// for extra sanity checking let's see if they actually look like
153
173
// valid files and instances of a pipe if feature enabled before we
@@ -174,40 +194,36 @@ impl Client {
174
194
//
175
195
// I tested this on macOS 14 and Linux 6.5.13
176
196
#[ cfg( target_os = "linux" ) ]
177
- if let Ok ( Some ( jobserver) ) =
178
- Self :: from_fifo ( & format ! ( "fifo:/dev/fd/{}" , read. as_raw_fd( ) ) )
179
- {
180
- return Ok ( Some ( jobserver) ) ;
197
+ if let ( Ok ( read) , Ok ( write) ) = (
198
+ File :: open ( format ! ( "/dev/fd/{}" , read) ) ,
199
+ OpenOptions :: new ( )
200
+ . write ( true )
201
+ . open ( format ! ( "/dev/fd/{}" , write) ) ,
202
+ ) {
203
+ return Ok ( Some ( Client {
204
+ read,
205
+ write,
206
+ creation_arg,
207
+ is_non_blocking : Some ( AtomicBool :: new ( false ) ) ,
208
+ } ) ) ;
181
209
}
182
210
}
183
211
}
184
212
185
- Ok ( Some ( Client :: Pipe {
213
+ Ok ( Some ( Client {
186
214
read : clone_fd_and_set_cloexec ( read) ?,
187
215
write : clone_fd_and_set_cloexec ( write) ?,
216
+ creation_arg,
217
+ is_non_blocking : None ,
188
218
} ) )
189
219
}
190
220
191
221
unsafe fn from_fds ( read : c_int , write : c_int ) -> Client {
192
- Client :: Pipe {
222
+ Client {
193
223
read : File :: from_raw_fd ( read) ,
194
224
write : File :: from_raw_fd ( write) ,
195
- }
196
- }
197
-
198
- /// Gets the read end of our jobserver client.
199
- fn read ( & self ) -> & File {
200
- match self {
201
- Client :: Pipe { read, .. } => read,
202
- Client :: Fifo { file, .. } => file,
203
- }
204
- }
205
-
206
- /// Gets the write end of our jobserver client.
207
- fn write ( & self ) -> & File {
208
- match self {
209
- Client :: Pipe { write, .. } => write,
210
- Client :: Fifo { file, .. } => file,
225
+ creation_arg : ClientCreationArg :: Fds { read, write } ,
226
+ is_non_blocking : None ,
211
227
}
212
228
}
213
229
@@ -245,7 +261,7 @@ impl Client {
245
261
// to shut us down, so we otherwise punt all errors upwards.
246
262
unsafe {
247
263
let mut fd: libc:: pollfd = mem:: zeroed ( ) ;
248
- let mut read = self . read ( ) ;
264
+ let mut read = & self . read ;
249
265
fd. fd = read. as_raw_fd ( ) ;
250
266
fd. events = libc:: POLLIN ;
251
267
loop {
@@ -284,19 +300,15 @@ impl Client {
284
300
285
301
pub fn try_acquire ( & self ) -> io:: Result < Option < Acquired > > {
286
302
let mut buf = [ 0 ] ;
303
+ let mut fifo = & self . read ;
287
304
288
- let ( mut fifo, is_non_blocking) = match self {
289
- Self :: Fifo {
290
- file,
291
- is_non_blocking,
292
- ..
293
- } => ( file, is_non_blocking) ,
294
- _ => return Err ( io:: ErrorKind :: Unsupported . into ( ) ) ,
295
- } ;
296
-
297
- if !is_non_blocking. load ( Ordering :: Relaxed ) {
298
- set_nonblocking ( fifo. as_raw_fd ( ) , true ) ?;
299
- is_non_blocking. store ( true , Ordering :: Relaxed ) ;
305
+ if let Some ( is_non_blocking) = self . is_non_blocking . as_ref ( ) {
306
+ if !is_non_blocking. load ( Ordering :: Relaxed ) {
307
+ set_nonblocking ( fifo. as_raw_fd ( ) , true ) ?;
308
+ is_non_blocking. store ( true , Ordering :: Relaxed ) ;
309
+ }
310
+ } else {
311
+ return Err ( io:: ErrorKind :: Unsupported . into ( ) ) ;
300
312
}
301
313
302
314
loop {
@@ -323,7 +335,7 @@ impl Client {
323
335
// always quickly release a token). If that turns out to not be the
324
336
// case we'll get an error anyway!
325
337
let byte = data. map ( |d| d. byte ) . unwrap_or ( b'+' ) ;
326
- match self . write ( ) . write ( & [ byte] ) ? {
338
+ match ( & self . write ) . write ( & [ byte] ) ? {
327
339
1 => Ok ( ( ) ) ,
328
340
_ => Err ( io:: Error :: new (
329
341
io:: ErrorKind :: Other ,
@@ -333,31 +345,30 @@ impl Client {
333
345
}
334
346
335
347
pub fn string_arg ( & self ) -> String {
336
- match self {
337
- Client :: Pipe { read , write } => format ! ( "{},{} " , read . as_raw_fd ( ) , write . as_raw_fd ( ) ) ,
338
- Client :: Fifo { path , .. } => format ! ( "fifo:{} " , path . to_str ( ) . unwrap ( ) ) ,
348
+ match & self . creation_arg {
349
+ ClientCreationArg :: Fifo ( path ) => format ! ( "fifo:{} " , path . display ( ) ) ,
350
+ ClientCreationArg :: Fds { read , write } => format ! ( "{},{} " , read , write ) ,
339
351
}
340
352
}
341
353
342
354
pub fn available ( & self ) -> io:: Result < usize > {
343
355
let mut len = MaybeUninit :: < c_int > :: uninit ( ) ;
344
- cvt ( unsafe { libc:: ioctl ( self . read ( ) . as_raw_fd ( ) , libc:: FIONREAD , len. as_mut_ptr ( ) ) } ) ?;
356
+ cvt ( unsafe { libc:: ioctl ( self . read . as_raw_fd ( ) , libc:: FIONREAD , len. as_mut_ptr ( ) ) } ) ?;
345
357
Ok ( unsafe { len. assume_init ( ) } as usize )
346
358
}
347
359
348
360
pub fn configure ( & self , cmd : & mut Command ) {
349
- match self {
361
+ if matches ! ( self . creation_arg , ClientCreationArg :: Fifo { .. } ) {
350
362
// We `File::open`ed it when inheriting from environment,
351
363
// so no need to set cloexec for fifo.
352
- Client :: Fifo { .. } => return ,
353
- Client :: Pipe { .. } => { }
354
- } ;
364
+ return ;
365
+ }
355
366
// Here we basically just want to say that in the child process
356
367
// we'll configure the read/write file descriptors to *not* be
357
368
// cloexec, so they're inherited across the exec and specified as
358
369
// integers through `string_arg` above.
359
- let read = self . read ( ) . as_raw_fd ( ) ;
360
- let write = self . write ( ) . as_raw_fd ( ) ;
370
+ let read = self . read . as_raw_fd ( ) ;
371
+ let write = self . write . as_raw_fd ( ) ;
361
372
unsafe {
362
373
cmd. pre_exec ( move || {
363
374
set_cloexec ( read, false ) ?;
@@ -559,55 +570,82 @@ mod test {
559
570
560
571
use crate :: { test:: run_named_fifo_try_acquire_tests, Client } ;
561
572
562
- use std:: sync:: Arc ;
573
+ use std:: {
574
+ fs:: File ,
575
+ io:: { self , Write } ,
576
+ os:: unix:: io:: AsRawFd ,
577
+ sync:: Arc ,
578
+ } ;
563
579
564
580
fn from_imp_client ( imp : ClientImp ) -> Client {
565
581
Client {
566
582
inner : Arc :: new ( imp) ,
567
583
}
568
584
}
569
585
570
- #[ test]
571
- fn test_try_acquire_named_fifo ( ) {
586
+ fn new_client_from_fifo ( ) -> ( Client , String ) {
572
587
let file = tempfile:: NamedTempFile :: new ( ) . unwrap ( ) ;
573
588
let fifo_path = file. path ( ) . to_owned ( ) ;
574
589
file. close ( ) . unwrap ( ) ; // Remove the NamedTempFile to create fifo
575
590
576
591
nix:: unistd:: mkfifo ( & fifo_path, nix:: sys:: stat:: Mode :: S_IRWXU ) . unwrap ( ) ;
577
592
578
- let client = ClientImp :: from_fifo ( & format ! ( "fifo:{}" , fifo_path. to_str( ) . unwrap( ) ) )
579
- . unwrap ( )
580
- . map ( from_imp_client)
581
- . unwrap ( ) ;
593
+ let arg = format ! ( "fifo:{}" , fifo_path. to_str( ) . unwrap( ) ) ;
582
594
583
- run_named_fifo_try_acquire_tests ( & client) ;
595
+ (
596
+ ClientImp :: from_fifo ( & arg)
597
+ . unwrap ( )
598
+ . map ( from_imp_client)
599
+ . unwrap ( ) ,
600
+ arg,
601
+ )
584
602
}
585
603
586
- #[ cfg( not( target_os = "linux" ) ) ]
587
- #[ test]
588
- fn test_try_acquire_annoymous_pipe_linux_specific_optimization ( ) {
589
- use std:: {
590
- fs:: File ,
591
- io:: { self , Write } ,
592
- os:: unix:: io:: AsRawFd ,
593
- } ;
594
-
604
+ fn new_client_from_pipe ( ) -> ( Client , String ) {
595
605
let ( read, write) = nix:: unistd:: pipe ( ) . unwrap ( ) ;
596
606
let read = File :: from ( read) ;
597
607
let mut write = File :: from ( write) ;
598
608
599
609
write. write_all ( b"1" ) . unwrap ( ) ;
600
610
601
- let client = unsafe {
602
- ClientImp :: from_pipe ( & format ! ( "{},{}" , read. as_raw_fd( ) , write. as_raw_fd( ) ) , true )
603
- }
604
- . unwrap ( )
605
- . map ( from_imp_client)
606
- . unwrap ( ) ;
611
+ let arg = format ! ( "{},{}" , read. as_raw_fd( ) , write. as_raw_fd( ) ) ;
612
+
613
+ (
614
+ unsafe { ClientImp :: from_pipe ( & arg, true ) }
615
+ . unwrap ( )
616
+ . map ( from_imp_client)
617
+ . unwrap ( ) ,
618
+ arg,
619
+ )
620
+ }
607
621
622
+ #[ test]
623
+ fn test_try_acquire_named_fifo ( ) {
624
+ run_named_fifo_try_acquire_tests ( & new_client_from_fifo ( ) . 0 ) ;
625
+ }
626
+
627
+ #[ test]
628
+ fn test_try_acquire_annoymous_pipe_linux_specific_optimization ( ) {
629
+ #[ cfg( not( target_os = "linux" ) ) ]
608
630
assert_eq ! (
609
- client . try_acquire( ) . unwrap_err( ) . kind( ) ,
631
+ new_client_from_pipe ( ) . 0 . try_acquire( ) . unwrap_err( ) . kind( ) ,
610
632
io:: ErrorKind :: Unsupported
611
633
) ;
634
+
635
+ #[ cfg( target_os = "linux" ) ]
636
+ {
637
+ let client = new_client_from_pipe ( ) . 0 ;
638
+ client. acquire ( ) . unwrap ( ) . drop_without_releasing ( ) ;
639
+ run_named_fifo_try_acquire_tests ( & client) ;
640
+ }
641
+ }
642
+
643
+ #[ test]
644
+ fn test_string_arg ( ) {
645
+ let ( client, arg) = new_client_from_fifo ( ) ;
646
+ assert_eq ! ( client. inner. string_arg( ) , arg) ;
647
+
648
+ let ( client, arg) = new_client_from_pipe ( ) ;
649
+ assert_eq ! ( client. inner. string_arg( ) , arg) ;
612
650
}
613
651
}
0 commit comments