diff options
Diffstat (limited to 'vendor/jobserver/src')
-rw-r--r-- | vendor/jobserver/src/lib.rs | 54 | ||||
-rw-r--r-- | vendor/jobserver/src/unix.rs | 122 | ||||
-rw-r--r-- | vendor/jobserver/src/wasm.rs | 5 | ||||
-rw-r--r-- | vendor/jobserver/src/windows.rs | 20 |
4 files changed, 181 insertions, 20 deletions
diff --git a/vendor/jobserver/src/lib.rs b/vendor/jobserver/src/lib.rs index 72c02c120..cd0cdd749 100644 --- a/vendor/jobserver/src/lib.rs +++ b/vendor/jobserver/src/lib.rs @@ -11,7 +11,10 @@ //! The jobserver implementation can be found in [detail online][docs] but //! basically boils down to a cross-process semaphore. On Unix this is //! implemented with the `pipe` syscall and read/write ends of a pipe and on -//! Windows this is implemented literally with IPC semaphores. +//! Windows this is implemented literally with IPC semaphores. Starting from +//! GNU `make` version 4.4, named pipe becomes the default way in communication +//! on Unix. This crate also supports that feature in the sense of inheriting +//! and forwarding the correct environment. //! //! The jobserver protocol in `make` also dictates when tokens are acquired to //! run child work, and clients using this crate should take care to implement @@ -208,7 +211,7 @@ impl Client { /// with `CLOEXEC` so they're not automatically inherited by spawned /// children. /// - /// # Unsafety + /// # Safety /// /// This function is `unsafe` to call on Unix specifically as it /// transitively requires usage of the `from_raw_fd` function, which is @@ -273,6 +276,19 @@ impl Client { }) } + /// Returns amount of tokens in the read-side pipe. + /// + /// # Return value + /// + /// Number of bytes available to be read from the jobserver pipe + /// + /// # Errors + /// + /// Underlying errors from the ioctl will be passed up. + pub fn available(&self) -> io::Result<usize> { + self.inner.available() + } + /// Configures a child process to have access to this client's jobserver as /// well. /// @@ -290,13 +306,41 @@ impl Client { /// /// On platforms other than Unix and Windows this panics. pub fn configure(&self, cmd: &mut Command) { + cmd.env("CARGO_MAKEFLAGS", &self.mflags_env()); + self.inner.configure(cmd); + } + + /// Configures a child process to have access to this client's jobserver as + /// well. + /// + /// This function is required to be called to ensure that a jobserver is + /// properly inherited to a child process. If this function is *not* called + /// then this `Client` will not be accessible in the child process. In other + /// words, if not called, then `Client::from_env` will return `None` in the + /// child process (or the equivalent of `Child::from_env` that `make` uses). + /// + /// ## Platform-specific behavior + /// + /// On Unix and Windows this will clobber the `CARGO_MAKEFLAGS`, + /// `MAKEFLAGS` and `MFLAGS` environment variables for the child process, + /// and on Unix this will also allow the two file descriptors for + /// this client to be inherited to the child. + /// + /// On platforms other than Unix and Windows this panics. + pub fn configure_make(&self, cmd: &mut Command) { + let value = self.mflags_env(); + cmd.env("CARGO_MAKEFLAGS", &value); + cmd.env("MAKEFLAGS", &value); + cmd.env("MFLAGS", &value); + self.inner.configure(cmd); + } + + fn mflags_env(&self) -> String { let arg = self.inner.string_arg(); // Older implementations of make use `--jobserver-fds` and newer // implementations use `--jobserver-auth`, pass both to try to catch // both implementations. - let value = format!("-j --jobserver-fds={0} --jobserver-auth={0}", arg); - cmd.env("CARGO_MAKEFLAGS", &value); - self.inner.configure(cmd); + format!("-j --jobserver-fds={0} --jobserver-auth={0}", arg) } /// Converts this `Client` into a helper thread to deal with a blocking diff --git a/vendor/jobserver/src/unix.rs b/vendor/jobserver/src/unix.rs index d69ae88e3..e4b143505 100644 --- a/vendor/jobserver/src/unix.rs +++ b/vendor/jobserver/src/unix.rs @@ -1,8 +1,11 @@ use libc::c_int; -use std::fs::File; + +use std::fs::{File, OpenOptions}; use std::io::{self, Read, Write}; use std::mem; +use std::mem::MaybeUninit; use std::os::unix::prelude::*; +use std::path::{Path, PathBuf}; use std::process::Command; use std::ptr; use std::sync::{Arc, Once}; @@ -10,9 +13,11 @@ use std::thread::{self, Builder, JoinHandle}; use std::time::Duration; #[derive(Debug)] -pub struct Client { - read: File, - write: File, +pub enum Client { + /// `--jobserver-auth=R,W` + Pipe { read: File, write: File }, + /// `--jobserver-auth=fifo:PATH` + Fifo { file: File, path: PathBuf }, } #[derive(Debug)] @@ -21,13 +26,26 @@ pub struct Acquired { } impl Client { - pub fn new(limit: usize) -> io::Result<Client> { + pub fn new(mut limit: usize) -> io::Result<Client> { let client = unsafe { Client::mk()? }; + // I don't think the character written here matters, but I could be // wrong! - for _ in 0..limit { - (&client.write).write_all(&[b'|'])?; + const BUFFER: [u8; 128] = [b'|'; 128]; + + let mut write = client.write(); + + set_nonblocking(write.as_raw_fd(), true)?; + + while limit > 0 { + let n = limit.min(BUFFER.len()); + + write.write_all(&BUFFER[..n])?; + limit -= n; } + + set_nonblocking(write.as_raw_fd(), false)?; + Ok(client) } @@ -64,6 +82,31 @@ impl Client { } pub unsafe fn open(s: &str) -> Option<Client> { + Client::from_fifo(s).or_else(|| Client::from_pipe(s)) + } + + /// `--jobserver-auth=fifo:PATH` + fn from_fifo(s: &str) -> Option<Client> { + let mut parts = s.splitn(2, ':'); + if parts.next().unwrap() != "fifo" { + return None; + } + let path = match parts.next() { + Some(p) => Path::new(p), + None => return None, + }; + let file = match OpenOptions::new().read(true).write(true).open(path) { + Ok(f) => f, + Err(_) => return None, + }; + Some(Client::Fifo { + file, + path: path.into(), + }) + } + + /// `--jobserver-auth=R,W` + unsafe fn from_pipe(s: &str) -> Option<Client> { let mut parts = s.splitn(2, ','); let read = parts.next().unwrap(); let write = match parts.next() { @@ -97,12 +140,28 @@ impl Client { } unsafe fn from_fds(read: c_int, write: c_int) -> Client { - Client { + Client::Pipe { read: File::from_raw_fd(read), write: File::from_raw_fd(write), } } + /// Gets the read end of our jobserver client. + fn read(&self) -> &File { + match self { + Client::Pipe { read, .. } => read, + Client::Fifo { file, .. } => file, + } + } + + /// Gets the write end of our jobserver client. + fn write(&self) -> &File { + match self { + Client::Pipe { write, .. } => write, + Client::Fifo { file, .. } => file, + } + } + pub fn acquire(&self) -> io::Result<Acquired> { // Ignore interrupts and keep trying if that happens loop { @@ -137,11 +196,12 @@ impl Client { // to shut us down, so we otherwise punt all errors upwards. unsafe { let mut fd: libc::pollfd = mem::zeroed(); - fd.fd = self.read.as_raw_fd(); + let mut read = self.read(); + fd.fd = read.as_raw_fd(); fd.events = libc::POLLIN; loop { let mut buf = [0]; - match (&self.read).read(&mut buf) { + match read.read(&mut buf) { Ok(1) => return Ok(Some(Acquired { byte: buf[0] })), Ok(_) => { return Err(io::Error::new( @@ -179,7 +239,7 @@ impl Client { // always quickly release a token). If that turns out to not be the // case we'll get an error anyway! let byte = data.map(|d| d.byte).unwrap_or(b'+'); - match (&self.write).write(&[byte])? { + match self.write().write(&[byte])? { 1 => Ok(()), _ => Err(io::Error::new( io::ErrorKind::Other, @@ -189,16 +249,31 @@ impl Client { } pub fn string_arg(&self) -> String { - format!("{},{}", self.read.as_raw_fd(), self.write.as_raw_fd()) + match self { + Client::Pipe { read, write } => format!("{},{}", read.as_raw_fd(), write.as_raw_fd()), + Client::Fifo { path, .. } => format!("fifo:{}", path.to_str().unwrap()), + } + } + + pub fn available(&self) -> io::Result<usize> { + let mut len = MaybeUninit::<c_int>::uninit(); + cvt(unsafe { libc::ioctl(self.read().as_raw_fd(), libc::FIONREAD, len.as_mut_ptr()) })?; + Ok(unsafe { len.assume_init() } as usize) } pub fn configure(&self, cmd: &mut Command) { + match self { + // We `File::open`ed it when inheriting from environment, + // so no need to set cloexec for fifo. + Client::Fifo { .. } => return, + Client::Pipe { .. } => {} + }; // Here we basically just want to say that in the child process // we'll configure the read/write file descriptors to *not* be // cloexec, so they're inherited across the exec and specified as // integers through `string_arg` above. - let read = self.read.as_raw_fd(); - let write = self.write.as_raw_fd(); + let read = self.read().as_raw_fd(); + let write = self.write().as_raw_fd(); unsafe { cmd.pre_exec(move || { set_cloexec(read, false)?; @@ -224,7 +299,14 @@ pub(crate) fn spawn_helper( let mut err = None; USR1_INIT.call_once(|| unsafe { let mut new: libc::sigaction = mem::zeroed(); - new.sa_sigaction = sigusr1_handler as usize; + #[cfg(target_os = "aix")] + { + new.sa_union.__su_sigaction = sigusr1_handler; + } + #[cfg(not(target_os = "aix"))] + { + new.sa_sigaction = sigusr1_handler as usize; + } new.sa_flags = libc::SA_SIGINFO as _; if libc::sigaction(libc::SIGUSR1, &new, ptr::null_mut()) != 0 { err = Some(io::Error::last_os_error()); @@ -322,6 +404,16 @@ fn set_cloexec(fd: c_int, set: bool) -> io::Result<()> { } } +fn set_nonblocking(fd: c_int, set: bool) -> io::Result<()> { + let status_flag = if set { libc::O_NONBLOCK } else { 0 }; + + unsafe { + cvt(libc::fcntl(fd, libc::F_SETFL, status_flag))?; + } + + Ok(()) +} + fn cvt(t: c_int) -> io::Result<c_int> { if t == -1 { Err(io::Error::last_os_error()) diff --git a/vendor/jobserver/src/wasm.rs b/vendor/jobserver/src/wasm.rs index b88a9d952..3793bd67c 100644 --- a/vendor/jobserver/src/wasm.rs +++ b/vendor/jobserver/src/wasm.rs @@ -59,6 +59,11 @@ impl Client { ); } + pub fn available(&self) -> io::Result<usize> { + let lock = self.inner.count.lock().unwrap_or_else(|e| e.into_inner()); + Ok(*lock) + } + pub fn configure(&self, _cmd: &mut Command) { unreachable!(); } diff --git a/vendor/jobserver/src/windows.rs b/vendor/jobserver/src/windows.rs index d795c1cee..6791efea4 100644 --- a/vendor/jobserver/src/windows.rs +++ b/vendor/jobserver/src/windows.rs @@ -170,6 +170,26 @@ impl Client { self.name.clone() } + pub fn available(&self) -> io::Result<usize> { + // Can't read value of a semaphore on Windows, so + // try to acquire without sleeping, since we can find out the + // old value on release. If acquisiton fails, then available is 0. + unsafe { + let r = WaitForSingleObject(self.sem.0, 0); + if r != WAIT_OBJECT_0 { + Ok(0) + } else { + let mut prev: LONG = 0; + let r = ReleaseSemaphore(self.sem.0, 1, &mut prev); + if r != 0 { + Ok(prev as usize + 1) + } else { + Err(io::Error::last_os_error()) + } + } + } + } + pub fn configure(&self, _cmd: &mut Command) { // nothing to do here, we gave the name of our semaphore to the // child above |