Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/compiler/cicc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ pub fn generate_compile_commands(
arguments,
env_vars: env_vars.to_owned(),
cwd: cwd.to_owned(),
share_jobserver: false,
};

#[cfg(not(feature = "dist-client"))]
Expand Down
12 changes: 12 additions & 0 deletions src/compiler/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,14 @@ pub struct SingleCompileCommand {
pub arguments: Vec<OsString>,
pub env_vars: Vec<(OsString, OsString)>,
pub cwd: PathBuf,
/// Whether this compiler participates in the GNU make jobserver.
///
/// Deliberately a field rather than a defaulted builder method: the
/// compiler then makes every frontend answer, so a new one cannot
/// silently get this wrong. Getting it wrong in the `true` direction
/// costs a `fork` per compile; in the `false` direction it costs `rustc`
/// its parallelism limit, which is what the jobserver exists to enforce.
pub share_jobserver: bool,
}

#[async_trait]
Expand Down Expand Up @@ -196,6 +204,7 @@ impl CompileCommandImpl for SingleCompileCommand {
arguments,
env_vars,
cwd,
share_jobserver,
} = self;
// Resolve compiler avoiding ccache wrappers to prevent double-caching.
let resolved_executable = resolve_compiler_avoiding_wrapper(executable, env_vars);
Expand All @@ -204,6 +213,9 @@ impl CompileCommandImpl for SingleCompileCommand {
.env_clear()
.envs(env_vars.clone())
.current_dir(cwd);
if *share_jobserver {
cmd.share_jobserver();
}
run_input_output(cmd, None).await
}
}
Expand Down
1 change: 1 addition & 0 deletions src/compiler/cudafe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ pub fn generate_compile_commands(
arguments,
env_vars: env_vars.to_owned(),
cwd: cwd.to_owned(),
share_jobserver: false,
};

#[cfg(not(feature = "dist-client"))]
Expand Down
1 change: 1 addition & 0 deletions src/compiler/diab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ pub fn generate_compile_commands(
arguments,
env_vars: env_vars.to_owned(),
cwd: cwd.to_owned(),
share_jobserver: false,
};

Ok((command, None, Cacheable::Yes))
Expand Down
1 change: 1 addition & 0 deletions src/compiler/gcc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,7 @@ where
arguments,
env_vars: env_vars.to_owned(),
cwd: cwd.to_owned(),
share_jobserver: false,
};

#[cfg(not(feature = "dist-client"))]
Expand Down
1 change: 1 addition & 0 deletions src/compiler/msvc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1141,6 +1141,7 @@ fn generate_compile_commands(
arguments,
env_vars: env_vars.to_owned(),
cwd: cwd.to_owned(),
share_jobserver: false,
};

#[cfg(not(feature = "dist-client"))]
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1798,6 +1798,11 @@ impl<T: CommandCreatorSync> Compilation<T> for RustCompilation {
.collect(),
env_vars: env_vars.to_owned(),
cwd: cwd.to_owned(),
// rustc reads `CARGO_MAKEFLAGS` and runs codegen on a thread pool
// sized by the jobserver. Without one, every concurrent rustc
// spawns as many threads as there are CPUs, which is the
// oversubscription sccache's own jobserver exists to prevent.
share_jobserver: true,
};

#[cfg(not(feature = "dist-client"))]
Expand Down
1 change: 1 addition & 0 deletions src/compiler/tasking_vx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ fn generate_compile_commands(
arguments,
env_vars: env_vars.to_owned(),
cwd: cwd.to_owned(),
share_jobserver: false,
};

Ok((command, None, Cacheable::Yes))
Expand Down
2 changes: 2 additions & 0 deletions src/dist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,8 @@ pub fn try_compile_command_to_dist(
arguments,
env_vars,
cwd,
// The jobserver is a local resource; a remote worker has its own.
share_jobserver: _,
} = command;
Some(CompileCommand {
executable: executable.into_os_string().into_string().ok()?,
Expand Down
64 changes: 63 additions & 1 deletion src/mock_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ pub trait RunCommand: fmt::Debug + Send {
fn stdout(&mut self, cfg: Stdio) -> &mut Self;
/// Set the process' stderr from `cfg`.
fn stderr(&mut self, cfg: Stdio) -> &mut Self;
/// Hand sccache's jobserver down to this child.
///
/// Only worth doing for a child that implements the protocol -- in
/// practice `rustc`, which reads `CARGO_MAKEFLAGS`. It is not free: see
/// [`AsyncCommand::spawn`] for why sharing costs a `fork` instead of a
/// `posix_spawn`.
fn share_jobserver(&mut self) -> &mut Self {
self
}
/// Execute the process and return a process object.
async fn spawn(&mut self) -> Result<Self::C>;
}
Expand Down Expand Up @@ -180,13 +189,15 @@ impl CommandChild for Child {
pub struct AsyncCommand {
inner: Option<Command>,
jobserver: Client,
share_jobserver: bool,
}

impl AsyncCommand {
pub fn new<S: AsRef<OsStr>>(program: S, jobserver: Client) -> AsyncCommand {
AsyncCommand {
inner: Some(Command::new(program)),
jobserver,
share_jobserver: false,
}
}

Expand Down Expand Up @@ -246,13 +257,28 @@ impl RunCommand for AsyncCommand {
self.inner().stderr(cfg);
self
}
fn share_jobserver(&mut self) -> &mut AsyncCommand {
self.share_jobserver = true;
self
}
async fn spawn(&mut self) -> Result<Child> {
let mut inner = self.inner.take().unwrap();
inner.env_remove("MAKEFLAGS");
inner.env_remove("MFLAGS");
inner.env_remove("CARGO_MAKEFLAGS");
self.jobserver.configure(&mut inner);
// `configure` registers a `pre_exec` closure to clear `CLOEXEC` on the
// jobserver's file descriptors, and any `pre_exec` makes `std` fall
// back from `posix_spawn` to `fork`+`exec`. Almost nothing sccache
// spawns can use a jobserver -- preprocessors, version probes and
// C/C++ compilers all ignore it -- so the default is not to share,
// and the callers that spawn `rustc` ask for it.
if self.share_jobserver {
self.jobserver.configure(&mut inner);
}

// The token is acquired either way: it rate-limits how many children
// *we* run, which is separate from whether the child can acquire
// tokens of its own.
let token = self.jobserver.acquire().await?;
let mut inner = tokio::process::Command::from(inner);
let child = inner
Expand Down Expand Up @@ -667,4 +693,40 @@ mod test {
);
assert_eq!(exit_status(0), spawn_on_thread(creator, true));
}

/// The jobserver reaches a child through `CARGO_MAKEFLAGS`, so reading it
/// back out of the child is what "did the child get the jobserver?" means.
#[cfg(unix)]
fn child_sees_cargo_makeflags(share: bool) -> bool {
let client = Client::new_num(1);
let mut creator = <ProcessCommandCreator as CommandCreator>::new(&client);
let runtime = tokio::runtime::Runtime::new().unwrap();
let mut cmd = creator.new_command("/bin/sh");
cmd.args(&["-c", "printf %s \"$CARGO_MAKEFLAGS\""]);
if share {
cmd.share_jobserver();
}
let output: std::process::Output = runtime
.block_on(async { crate::util::run_input_output(cmd, None).await })
.unwrap();
!output.stdout.is_empty()
}

#[test]
#[cfg(unix)]
fn jobserver_is_withheld_by_default() {
assert!(
!child_sees_cargo_makeflags(false),
"a child should not inherit the jobserver unless it asks"
);
}

#[test]
#[cfg(unix)]
fn share_jobserver_hands_it_over() {
assert!(
child_sees_cargo_makeflags(true),
"share_jobserver() should give the child CARGO_MAKEFLAGS"
);
}
}
Loading