Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a GNU make jobserver implementation to Cargo #4110

Merged
merged 1 commit into from
Jun 2, 2017

Conversation

alexcrichton
Copy link
Member

This commit adds a GNU make jobserver implementation to Cargo, both as a client
of existing jobservers and also a creator of new jobservers. The jobserver is
actually just an IPC semaphore which manifests itself as a pipe with N bytes
of tokens on Unix and a literal IPC semaphore on Windows. The rough protocol
is then if you want to run a job you read acquire the semaphore (read a byte on
Unix or wait on the semaphore on Windows) and then you release it when you're
done.

All the hairy details of the jobserver implementation are housed in the
jobserver crate on crates.io instead of Cargo. This should hopefully make it
much easier for the compiler to also share a jobserver implementation
eventually.

The main tricky bit here is that on Unix and Windows acquiring a jobserver token
will block the calling thread. We need to either way for a running job to exit
or to acquire a new token when we want to spawn a new job. To handle this the
current implementation spawns a helper thread that does the blocking and sends a
message back to Cargo when it receives a token. It's a little trickier with
shutting down this thread gracefully as well but more details can be found in
the jobserver crate.

Unfortunately crates are unlikely to see an immediate benefit of this once
implemented. Most crates are run with a manual make -jN and this overrides the
jobserver in the environment, creating a new jobserver in the sub-make. If the
-jN argument is removed, however, then make will share Cargo's jobserver and
properly limit parallelism.

Closes #1744

@rust-highfive
Copy link

r? @brson

(rust_highfive has picked a reviewer for you, use r? to override)

@alexcrichton
Copy link
Member Author

r? @matklad

@rust-highfive rust-highfive assigned matklad and unassigned brson May 30, 2017
@alexcrichton
Copy link
Member Author

When reviewing this, it may also be worth taking a brief look at the crate's source as well

// we're only sending "longer living" messages and we should also
// destroy all references to the channel before this function exits as
// the destructor for the `helper` object will ensure the associated
// thread i sno longer running.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/i sno/is no

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, looks like this transmute is always safe, because Sender is contravariant.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I originally thought yeah but actually I don't think so (unfortunately). I believe Drop for Sender may run destructors for items in the internal queue (not received yet), which means that if you persist a Sender beyond the lifetime of the item I think it'll access data outside of its lifetime.

(not in this case though, the Sender should always go away with the stack frame.

@@ -51,6 +53,9 @@ impl Config {
extra_verbose: Cell::new(false),
frozen: Cell::new(false),
locked: Cell::new(false),
// This should be called early on in the process, so in theory the
// unsafety is ok here. (taken ownership of random fds)
jobserver: unsafe { jobserver::Client::from_env() },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be at most one job server, and it's not entirely obvious that we call Config::new exactly once. Perhaps, we can wrap jobserver creation in a jobserver_singleton function, which uses ONCE_INIT or lazy_static! internally?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops excellent point

@alexcrichton
Copy link
Member Author

Ok I've pushed up w/ a ONCE_INIT + an update to the jobserver crate

@bors
Copy link
Collaborator

bors commented May 31, 2017

☔ The latest upstream changes (presumably #4090) made this pull request unmergeable. Please resolve the merge conflicts.

@matklad
Copy link
Member

matklad commented Jun 1, 2017

LGTM, but perhaps we want to add a test here? I understand that there's an impressive amount of tests in jobserver-rs, but the amount of logic in Cargo is non-trivial, and we even have a transmute in here!

I am thinking of two useful test:

  1. Launch cargo itself with -j argument and print env in build.rs, to check that Cargo itself can setup a jobserver.

  2. Like 1, but launch cargo via make to check that it can be a client of jobserver (and we might want to put some logging when receiving tokens from jobserver, to check that we actually receive them).

@matklad
Copy link
Member

matklad commented Jun 1, 2017

Oh, and what should we do if Cargo is launched with jobjserver, but also with an explicit -j argument? Looks like currently we just ignore -j altogether. Should we perhaps print a warning?

@alexcrichton
Copy link
Member Author

All excellent suggestions! I've updated the PR

(thanks so much for the thorough reviews!)

@matklad
Copy link
Member

matklad commented Jun 2, 2017

r+, but looks like the CI failure on windows is legit: https://ci.appveyor.com/project/rust-lang-libs/cargo/build/1.0.1971#L271

thanks so much for the thorough reviews!

Well, I learn a lot of stuff from them :)

@alexcrichton
Copy link
Member Author

Looks like a corner case!

This commit adds a GNU make jobserver implementation to Cargo, both as a client
of existing jobservers and also a creator of new jobservers. The jobserver is
actually just an IPC semaphore which manifests itself as a pipe with N bytes
of tokens on Unix and a literal IPC semaphore on Windows. The rough protocol
is then if you want to run a job you read acquire the semaphore (read a byte on
Unix or wait on the semaphore on Windows) and then you release it when you're
done.

All the hairy details of the jobserver implementation are housed in the
`jobserver` crate on crates.io instead of Cargo. This should hopefully make it
much easier for the compiler to also share a jobserver implementation
eventually.

The main tricky bit here is that on Unix and Windows acquiring a jobserver token
will block the calling thread. We need to either way for a running job to exit
or to acquire a new token when we want to spawn a new job. To handle this the
current implementation spawns a helper thread that does the blocking and sends a
message back to Cargo when it receives a token. It's a little trickier with
shutting down this thread gracefully as well but more details can be found in
the `jobserver` crate.

Unfortunately crates are unlikely to see an immediate benefit of this once
implemented. Most crates are run with a manual `make -jN` and this overrides the
jobserver in the environment, creating a new jobserver in the sub-make. If the
`-jN` argument is removed, however, then `make` will share Cargo's jobserver and
properly limit parallelism.

Closes rust-lang#1744
@alexcrichton
Copy link
Member Author

@bor: r=matklad

@alexcrichton
Copy link
Member Author

@bors: r=matklad

@bors
Copy link
Collaborator

bors commented Jun 2, 2017

📌 Commit cbf25a9 has been approved by matklad

@bors
Copy link
Collaborator

bors commented Jun 2, 2017

⌛ Testing commit cbf25a9 with merge 8fb6e7c...

bors added a commit that referenced this pull request Jun 2, 2017
Add a GNU make jobserver implementation to Cargo

This commit adds a GNU make jobserver implementation to Cargo, both as a client
of existing jobservers and also a creator of new jobservers. The jobserver is
actually just an IPC semaphore which manifests itself as a pipe with N bytes
of tokens on Unix and a literal IPC semaphore on Windows. The rough protocol
is then if you want to run a job you read acquire the semaphore (read a byte on
Unix or wait on the semaphore on Windows) and then you release it when you're
done.

All the hairy details of the jobserver implementation are housed in the
`jobserver` crate on crates.io instead of Cargo. This should hopefully make it
much easier for the compiler to also share a jobserver implementation
eventually.

The main tricky bit here is that on Unix and Windows acquiring a jobserver token
will block the calling thread. We need to either way for a running job to exit
or to acquire a new token when we want to spawn a new job. To handle this the
current implementation spawns a helper thread that does the blocking and sends a
message back to Cargo when it receives a token. It's a little trickier with
shutting down this thread gracefully as well but more details can be found in
the `jobserver` crate.

Unfortunately crates are unlikely to see an immediate benefit of this once
implemented. Most crates are run with a manual `make -jN` and this overrides the
jobserver in the environment, creating a new jobserver in the sub-make. If the
`-jN` argument is removed, however, then `make` will share Cargo's jobserver and
properly limit parallelism.

Closes #1744
@bors
Copy link
Collaborator

bors commented Jun 2, 2017

☀️ Test successful - status-appveyor, status-travis
Approved by: matklad
Pushing 8fb6e7c to master...

alexcrichton added a commit to alexcrichton/rust that referenced this pull request Jun 21, 2017
This commit integrates the `jobserver` crate into the compiler. The crate was
previously integrated in to Cargo as part of rust-lang/cargo#4110. The purpose
here is to two-fold:

* Primarily the compiler can cooperate with Cargo on parallelism. When you run
  `cargo build -j4` then this'll make sure that the entire build process between
  Cargo/rustc won't use more than 4 cores, whereas today you'd get 4 rustc
  instances which may all try to spawn lots of threads.

* Secondarily rustc/Cargo can now integrate with a foreign GNU `make` jobserver.
  This means that if you call cargo/rustc from `make` or another
  jobserver-compatible implementation it'll use foreign parallelism settings
  instead of creating new ones locally.

As the number of parallel codegen instances in the compiler continues to grow
over time with the advent of incremental compilation it's expected that this'll
become more of a problem, so this is intended to nip concurrent concerns in the
bud by having all the tools to cooperate!

Note that while rustc has support for itself creating a jobserver it's far more
likely that rustc will always use the jobserver configured by Cargo. Cargo today
will now set a jobserver unconditionally for rustc to use.
bors added a commit to rust-lang/rust that referenced this pull request Jun 21, 2017
Integrate jobserver support to parallel codegen

This commit integrates the `jobserver` crate into the compiler. The crate was
previously integrated in to Cargo as part of rust-lang/cargo#4110. The purpose
here is to two-fold:

* Primarily the compiler can cooperate with Cargo on parallelism. When you run
  `cargo build -j4` then this'll make sure that the entire build process between
  Cargo/rustc won't use more than 4 cores, whereas today you'd get 4 rustc
  instances which may all try to spawn lots of threads.

* Secondarily rustc/Cargo can now integrate with a foreign GNU `make` jobserver.
  This means that if you call cargo/rustc from `make` or another
  jobserver-compatible implementation it'll use foreign parallelism settings
  instead of creating new ones locally.

As the number of parallel codegen instances in the compiler continues to grow
over time with the advent of incremental compilation it's expected that this'll
become more of a problem, so this is intended to nip concurrent concerns in the
bud by having all the tools to cooperate!

Note that while rustc has support for itself creating a jobserver it's far more
likely that rustc will always use the jobserver configured by Cargo. Cargo today
will now set a jobserver unconditionally for rustc to use.
bors added a commit to rust-lang/rust that referenced this pull request Jun 22, 2017
Integrate jobserver support to parallel codegen

This commit integrates the `jobserver` crate into the compiler. The crate was
previously integrated in to Cargo as part of rust-lang/cargo#4110. The purpose
here is to two-fold:

* Primarily the compiler can cooperate with Cargo on parallelism. When you run
  `cargo build -j4` then this'll make sure that the entire build process between
  Cargo/rustc won't use more than 4 cores, whereas today you'd get 4 rustc
  instances which may all try to spawn lots of threads.

* Secondarily rustc/Cargo can now integrate with a foreign GNU `make` jobserver.
  This means that if you call cargo/rustc from `make` or another
  jobserver-compatible implementation it'll use foreign parallelism settings
  instead of creating new ones locally.

As the number of parallel codegen instances in the compiler continues to grow
over time with the advent of incremental compilation it's expected that this'll
become more of a problem, so this is intended to nip concurrent concerns in the
bud by having all the tools to cooperate!

Note that while rustc has support for itself creating a jobserver it's far more
likely that rustc will always use the jobserver configured by Cargo. Cargo today
will now set a jobserver unconditionally for rustc to use.
alexcrichton added a commit to alexcrichton/cargo that referenced this pull request Apr 22, 2019
This looks like it was a bug ever present from the original
implementation of a GNU jobserver in rust-lang#4110, but we currently
unconditionally request a token is allocated for any job we pull off our
job queue. Rather we only need to request tokens for everything but the
first job because we already have an implicit token for that job.
@ehuss ehuss added this to the 1.19.0 milestone Feb 6, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for make jobservers
6 participants