@@ -6,7 +6,7 @@ use std::io::{self, Read, Write};
66use std:: mem;
77use std:: mem:: MaybeUninit ;
88use std:: os:: unix:: prelude:: * ;
9- use std:: path:: { Path , PathBuf } ;
9+ use std:: path:: Path ;
1010use std:: process:: Command ;
1111use std:: ptr;
1212use std:: sync:: {
@@ -17,17 +17,27 @@ use std::thread::{self, Builder, JoinHandle};
1717use std:: time:: Duration ;
1818
1919#[ 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 > ,
3141}
3242
3343#[ derive( Debug ) ]
@@ -43,7 +53,7 @@ impl Client {
4353 // wrong!
4454 const BUFFER : [ u8 ; 128 ] = [ b'|' ; 128 ] ;
4555
46- let mut write = client. write ( ) ;
56+ let mut write = & client. write ;
4757
4858 set_nonblocking ( write. as_raw_fd ( ) , true ) ?;
4959
@@ -111,16 +121,24 @@ impl Client {
111121 FromEnvErrorInner :: CannotParse ( "expected a path after `fifo:`" . to_string ( ) )
112122 } ) ?;
113123 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 ) ) ,
124142 } ) )
125143 }
126144
@@ -148,6 +166,8 @@ impl Client {
148166 return Err ( FromEnvErrorInner :: NegativeFd ( write) ) ;
149167 }
150168
169+ let creation_arg = ClientCreationArg :: Fds { read, write } ;
170+
151171 // Ok so we've got two integers that look like file descriptors, but
152172 // for extra sanity checking let's see if they actually look like
153173 // valid files and instances of a pipe if feature enabled before we
@@ -174,40 +194,36 @@ impl Client {
174194 //
175195 // I tested this on macOS 14 and Linux 6.5.13
176196 #[ 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+ } ) ) ;
181209 }
182210 }
183211 }
184212
185- Ok ( Some ( Client :: Pipe {
213+ Ok ( Some ( Client {
186214 read : clone_fd_and_set_cloexec ( read) ?,
187215 write : clone_fd_and_set_cloexec ( write) ?,
216+ creation_arg,
217+ is_non_blocking : None ,
188218 } ) )
189219 }
190220
191221 unsafe fn from_fds ( read : c_int , write : c_int ) -> Client {
192- Client :: Pipe {
222+ Client {
193223 read : File :: from_raw_fd ( read) ,
194224 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 ,
211227 }
212228 }
213229
@@ -245,7 +261,7 @@ impl Client {
245261 // to shut us down, so we otherwise punt all errors upwards.
246262 unsafe {
247263 let mut fd: libc:: pollfd = mem:: zeroed ( ) ;
248- let mut read = self . read ( ) ;
264+ let mut read = & self . read ;
249265 fd. fd = read. as_raw_fd ( ) ;
250266 fd. events = libc:: POLLIN ;
251267 loop {
@@ -284,19 +300,15 @@ impl Client {
284300
285301 pub fn try_acquire ( & self ) -> io:: Result < Option < Acquired > > {
286302 let mut buf = [ 0 ] ;
303+ let mut fifo = & self . read ;
287304
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 ( ) ) ;
300312 }
301313
302314 loop {
@@ -323,7 +335,7 @@ impl Client {
323335 // always quickly release a token). If that turns out to not be the
324336 // case we'll get an error anyway!
325337 let byte = data. map ( |d| d. byte ) . unwrap_or ( b'+' ) ;
326- match self . write ( ) . write ( & [ byte] ) ? {
338+ match ( & self . write ) . write ( & [ byte] ) ? {
327339 1 => Ok ( ( ) ) ,
328340 _ => Err ( io:: Error :: new (
329341 io:: ErrorKind :: Other ,
@@ -333,31 +345,30 @@ impl Client {
333345 }
334346
335347 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 ) ,
339351 }
340352 }
341353
342354 pub fn available ( & self ) -> io:: Result < usize > {
343355 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 ( ) ) } ) ?;
345357 Ok ( unsafe { len. assume_init ( ) } as usize )
346358 }
347359
348360 pub fn configure ( & self , cmd : & mut Command ) {
349- match self {
361+ if matches ! ( self . creation_arg , ClientCreationArg :: Fifo { .. } ) {
350362 // We `File::open`ed it when inheriting from environment,
351363 // so no need to set cloexec for fifo.
352- Client :: Fifo { .. } => return ,
353- Client :: Pipe { .. } => { }
354- } ;
364+ return ;
365+ }
355366 // Here we basically just want to say that in the child process
356367 // we'll configure the read/write file descriptors to *not* be
357368 // cloexec, so they're inherited across the exec and specified as
358369 // 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 ( ) ;
361372 unsafe {
362373 cmd. pre_exec ( move || {
363374 set_cloexec ( read, false ) ?;
@@ -559,55 +570,82 @@ mod test {
559570
560571 use crate :: { test:: run_named_fifo_try_acquire_tests, Client } ;
561572
562- use std:: sync:: Arc ;
573+ use std:: {
574+ fs:: File ,
575+ io:: { self , Write } ,
576+ os:: unix:: io:: AsRawFd ,
577+ sync:: Arc ,
578+ } ;
563579
564580 fn from_imp_client ( imp : ClientImp ) -> Client {
565581 Client {
566582 inner : Arc :: new ( imp) ,
567583 }
568584 }
569585
570- #[ test]
571- fn test_try_acquire_named_fifo ( ) {
586+ fn new_client_from_fifo ( ) -> ( Client , String ) {
572587 let file = tempfile:: NamedTempFile :: new ( ) . unwrap ( ) ;
573588 let fifo_path = file. path ( ) . to_owned ( ) ;
574589 file. close ( ) . unwrap ( ) ; // Remove the NamedTempFile to create fifo
575590
576591 nix:: unistd:: mkfifo ( & fifo_path, nix:: sys:: stat:: Mode :: S_IRWXU ) . unwrap ( ) ;
577592
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( ) ) ;
582594
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+ )
584602 }
585603
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 ) {
595605 let ( read, write) = nix:: unistd:: pipe ( ) . unwrap ( ) ;
596606 let read = File :: from ( read) ;
597607 let mut write = File :: from ( write) ;
598608
599609 write. write_all ( b"1" ) . unwrap ( ) ;
600610
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+ }
607621
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" ) ) ]
608630 assert_eq ! (
609- client . try_acquire( ) . unwrap_err( ) . kind( ) ,
631+ new_client_from_pipe ( ) . 0 . try_acquire( ) . unwrap_err( ) . kind( ) ,
610632 io:: ErrorKind :: Unsupported
611633 ) ;
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) ;
612650 }
613651}
0 commit comments