@@ -16,16 +16,20 @@ use std::sync::{
16
16
use std:: thread:: { self , Builder , JoinHandle } ;
17
17
use std:: time:: Duration ;
18
18
19
+ #[ derive( Debug ) ]
20
+ enum ClientCreationArg {
21
+ Fds { read : c_int , write : c_int } ,
22
+ Fifo ( Box < Path > ) ,
23
+ }
24
+
19
25
#[ derive( Debug ) ]
20
26
pub struct Client {
21
27
read : File ,
22
- write : Option < File > ,
23
- /// If path is not None, then a fifo jobserver is created.
24
- path : Option < Box < Path > > ,
25
- supports_non_blocking : bool ,
26
- /// it can only go from false -> true but not the other way around, since that
28
+ write : File ,
29
+ creation_arg : ClientCreationArg ,
30
+ /// it can only go from Some(false) -> Some(true) but not the other way around, since that
27
31
/// could cause a race condition.
28
- is_non_blocking : AtomicBool ,
32
+ is_non_blocking : Option < AtomicBool > ,
29
33
}
30
34
31
35
#[ derive( Debug ) ]
@@ -41,7 +45,7 @@ impl Client {
41
45
// wrong!
42
46
const BUFFER : [ u8 ; 128 ] = [ b'|' ; 128 ] ;
43
47
44
- let mut write = client. write ( ) ;
48
+ let mut write = & client. write ;
45
49
46
50
set_nonblocking ( write. as_raw_fd ( ) , true ) ?;
47
51
@@ -109,18 +113,20 @@ impl Client {
109
113
FromEnvErrorInner :: CannotParse ( "expected a path after `fifo:`" . to_string ( ) )
110
114
} ) ?;
111
115
let path = Path :: new ( path_str) ;
116
+
112
117
let file = OpenOptions :: new ( )
113
118
. read ( true )
114
119
. write ( true )
115
120
. open ( path)
116
121
. map_err ( |err| FromEnvErrorInner :: CannotOpenPath ( path_str. to_string ( ) , err) ) ?;
117
122
118
123
Ok ( Some ( Client {
119
- read : file,
120
- write : None ,
121
- path : Some ( path. into ( ) ) ,
122
- supports_non_blocking : true ,
123
- is_non_blocking : AtomicBool :: new ( false ) ,
124
+ read : file
125
+ . try_clone ( )
126
+ . map_err ( |err| FromEnvErrorInner :: CannotClone ( file. as_raw_fd ( ) , err) ) ?,
127
+ write : file,
128
+ creation_arg : ClientCreationArg :: Fifo ( path. into ( ) ) ,
129
+ is_non_blocking : Some ( AtomicBool :: new ( false ) ) ,
124
130
} ) )
125
131
}
126
132
@@ -148,6 +154,8 @@ impl Client {
148
154
return Err ( FromEnvErrorInner :: NegativeFd ( write) ) ;
149
155
}
150
156
157
+ let creation_arg = ClientCreationArg :: Fds { read, write } ;
158
+
151
159
// Ok so we've got two integers that look like file descriptors, but
152
160
// for extra sanity checking let's see if they actually look like
153
161
// valid files and instances of a pipe if feature enabled before we
@@ -174,44 +182,39 @@ impl Client {
174
182
//
175
183
// I tested this on macOS 14 and Linux 6.5.13
176
184
#[ cfg( target_os = "linux" ) ]
177
- if let Ok ( Some ( mut jobserver) ) =
178
- Self :: from_fifo ( & format ! ( "fifo:/dev/fd/{}" , read. as_raw_fd( ) ) )
179
- {
180
- jobserver. path . take ( ) ;
181
- return Ok ( Some ( jobserver) ) ;
185
+ if let ( Ok ( read) , Ok ( write) ) = (
186
+ File :: open ( format ! ( "/dev/fd/{}" , read) ) ,
187
+ OpenOptions :: new ( )
188
+ . write ( true )
189
+ . open ( format ! ( "/dev/fd/{}" , write) ) ,
190
+ ) {
191
+ return Ok ( Some ( Client {
192
+ read,
193
+ write,
194
+ creation_arg,
195
+ is_non_blocking : Some ( AtomicBool :: new ( false ) ) ,
196
+ } ) ) ;
182
197
}
183
198
}
184
199
}
185
200
186
201
Ok ( Some ( Client {
187
202
read : clone_fd_and_set_cloexec ( read) ?,
188
- write : Some ( clone_fd_and_set_cloexec ( write) ?) ,
189
- path : None ,
190
- supports_non_blocking : false ,
191
- is_non_blocking : AtomicBool :: new ( false ) ,
203
+ write : clone_fd_and_set_cloexec ( write) ?,
204
+ creation_arg,
205
+ is_non_blocking : None ,
192
206
} ) )
193
207
}
194
208
195
209
unsafe fn from_fds ( read : c_int , write : c_int ) -> Client {
196
210
Client {
197
211
read : File :: from_raw_fd ( read) ,
198
- write : Some ( File :: from_raw_fd ( write) ) ,
199
- path : None ,
200
- supports_non_blocking : false ,
201
- is_non_blocking : AtomicBool :: new ( false ) ,
212
+ write : File :: from_raw_fd ( write) ,
213
+ creation_arg : ClientCreationArg :: Fds { read, write } ,
214
+ is_non_blocking : None ,
202
215
}
203
216
}
204
217
205
- /// Gets the read end of our jobserver client.
206
- fn read ( & self ) -> & File {
207
- & self . read
208
- }
209
-
210
- /// Gets the write end of our jobserver client.
211
- fn write ( & self ) -> & File {
212
- self . write . as_ref ( ) . unwrap_or ( & self . read )
213
- }
214
-
215
218
pub fn acquire ( & self ) -> io:: Result < Acquired > {
216
219
// Ignore interrupts and keep trying if that happens
217
220
loop {
@@ -246,7 +249,7 @@ impl Client {
246
249
// to shut us down, so we otherwise punt all errors upwards.
247
250
unsafe {
248
251
let mut fd: libc:: pollfd = mem:: zeroed ( ) ;
249
- let mut read = self . read ( ) ;
252
+ let mut read = & self . read ;
250
253
fd. fd = read. as_raw_fd ( ) ;
251
254
fd. events = libc:: POLLIN ;
252
255
loop {
@@ -285,18 +288,17 @@ impl Client {
285
288
286
289
pub fn try_acquire ( & self ) -> io:: Result < Option < Acquired > > {
287
290
let mut buf = [ 0 ] ;
291
+ let mut fifo = & self . read ;
288
292
289
- if !self . supports_non_blocking {
293
+ if let Some ( is_non_blocking) = self . is_non_blocking . as_ref ( ) {
294
+ if !is_non_blocking. load ( Ordering :: Relaxed ) {
295
+ set_nonblocking ( fifo. as_raw_fd ( ) , true ) ?;
296
+ is_non_blocking. store ( true , Ordering :: Relaxed ) ;
297
+ }
298
+ } else {
290
299
return Err ( io:: ErrorKind :: Unsupported . into ( ) ) ;
291
300
}
292
301
293
- let mut fifo = self . read ( ) ;
294
-
295
- if !self . is_non_blocking . load ( Ordering :: Relaxed ) {
296
- set_nonblocking ( fifo. as_raw_fd ( ) , true ) ?;
297
- self . is_non_blocking . store ( true , Ordering :: Relaxed ) ;
298
- }
299
-
300
302
loop {
301
303
match fifo. read ( & mut buf) {
302
304
Ok ( 1 ) => break Ok ( Some ( Acquired { byte : buf[ 0 ] } ) ) ,
@@ -321,7 +323,7 @@ impl Client {
321
323
// always quickly release a token). If that turns out to not be the
322
324
// case we'll get an error anyway!
323
325
let byte = data. map ( |d| d. byte ) . unwrap_or ( b'+' ) ;
324
- match self . write ( ) . write ( & [ byte] ) ? {
326
+ match ( & self . write ) . write ( & [ byte] ) ? {
325
327
1 => Ok ( ( ) ) ,
326
328
_ => Err ( io:: Error :: new (
327
329
io:: ErrorKind :: Other ,
@@ -331,20 +333,20 @@ impl Client {
331
333
}
332
334
333
335
pub fn string_arg ( & self ) -> String {
334
- self . path
335
- . as_deref ( )
336
- . map ( |path| format ! ( "fifo:{} " , path . display ( ) ) )
337
- . unwrap_or_else ( || format ! ( "{},{}" , self . read ( ) . as_raw_fd ( ) , self . write ( ) . as_raw_fd ( ) ) )
336
+ match & self . creation_arg {
337
+ ClientCreationArg :: Fifo ( path ) => format ! ( "fifo:{}" , path . display ( ) ) ,
338
+ ClientCreationArg :: Fds { read , write } => format ! ( "{},{} " , read , write ) ,
339
+ }
338
340
}
339
341
340
342
pub fn available ( & self ) -> io:: Result < usize > {
341
343
let mut len = MaybeUninit :: < c_int > :: uninit ( ) ;
342
- cvt ( unsafe { libc:: ioctl ( self . read ( ) . as_raw_fd ( ) , libc:: FIONREAD , len. as_mut_ptr ( ) ) } ) ?;
344
+ cvt ( unsafe { libc:: ioctl ( self . read . as_raw_fd ( ) , libc:: FIONREAD , len. as_mut_ptr ( ) ) } ) ?;
343
345
Ok ( unsafe { len. assume_init ( ) } as usize )
344
346
}
345
347
346
348
pub fn configure ( & self , cmd : & mut Command ) {
347
- if self . path . is_some ( ) {
349
+ if matches ! ( self . creation_arg , ClientCreationArg :: Fifo { .. } ) {
348
350
// We `File::open`ed it when inheriting from environment,
349
351
// so no need to set cloexec for fifo.
350
352
return ;
@@ -353,8 +355,8 @@ impl Client {
353
355
// we'll configure the read/write file descriptors to *not* be
354
356
// cloexec, so they're inherited across the exec and specified as
355
357
// integers through `string_arg` above.
356
- let read = self . read ( ) . as_raw_fd ( ) ;
357
- let write = self . write ( ) . as_raw_fd ( ) ;
358
+ let read = self . read . as_raw_fd ( ) ;
359
+ let write = self . write . as_raw_fd ( ) ;
358
360
unsafe {
359
361
cmd. pre_exec ( move || {
360
362
set_cloexec ( read, false ) ?;
0 commit comments