Skip to content

Commit a9900f3

Browse files
authored
Merge pull request #100 from NobodyXu/fix
Fix `Client::configure*` on unix
2 parents c0c2898 + 0119e16 commit a9900f3

File tree

2 files changed

+132
-94
lines changed

2 files changed

+132
-94
lines changed

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ mod test {
644644
client.try_acquire().unwrap().unwrap();
645645
}
646646

647-
#[cfg(not(unix))]
647+
#[cfg(windows)]
648648
#[test]
649649
fn test_try_acquire() {
650650
let client = Client::new(0).unwrap();

src/unix.rs

+131-93
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::io::{self, Read, Write};
66
use std::mem;
77
use std::mem::MaybeUninit;
88
use std::os::unix::prelude::*;
9-
use std::path::{Path, PathBuf};
9+
use std::path::Path;
1010
use std::process::Command;
1111
use std::ptr;
1212
use std::sync::{
@@ -17,17 +17,27 @@ use std::thread::{self, Builder, JoinHandle};
1717
use 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

Comments
 (0)