Skip to content

Commit c83f5ee

Browse files
authored
Fix Build::compile_objects deadlock on parallel (#829)
1 parent d275796 commit c83f5ee

File tree

1 file changed

+29
-19
lines changed

1 file changed

+29
-19
lines changed

src/lib.rs

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,15 +1258,16 @@ impl Build {
12581258

12591259
#[cfg(feature = "parallel")]
12601260
fn compile_objects(&self, objs: &[Object], print: &PrintThread) -> Result<(), Error> {
1261-
use std::sync::Once;
1261+
use std::sync::{mpsc::channel, Once};
12621262

12631263
// Limit our parallelism globally with a jobserver. Start off by
12641264
// releasing our own token for this process so we can have a bit of an
12651265
// easier to write loop below. If this fails, though, then we're likely
12661266
// on Windows with the main implicit token, so we just have a bit extra
12671267
// parallelism for a bit and don't reacquire later.
12681268
let server = jobserver();
1269-
let reacquire = server.release_raw().is_ok();
1269+
// Reacquire our process's token on drop
1270+
let _reacquire = server.release_raw().ok().map(|_| JobserverToken(server));
12701271

12711272
// When compiling objects in parallel we do a few dirty tricks to speed
12721273
// things up:
@@ -1287,29 +1288,31 @@ impl Build {
12871288
// acquire the appropriate tokens, Once all objects have been compiled
12881289
// we wait on all the processes and propagate the results of compilation.
12891290

1290-
let children = objs
1291-
.iter()
1292-
.map(|obj| {
1293-
let (mut cmd, program) = self.create_compile_object_cmd(obj)?;
1294-
let token = server.acquire()?;
1291+
let (tx, rx) = channel::<(_, String, KillOnDrop, _)>();
12951292

1296-
let child = spawn(&mut cmd, &program, print.pipe_writer_cloned()?.unwrap())?;
1293+
// Since jobserver::Client::acquire can block, waiting
1294+
// must be done in parallel so that acquire won't block forever.
1295+
let wait_thread = thread::Builder::new().spawn(move || {
1296+
for (cmd, program, mut child, _token) in rx {
1297+
wait_on_child(&cmd, &program, &mut child.0)?;
1298+
}
12971299

1298-
Ok((cmd, program, KillOnDrop(child), token))
1299-
})
1300-
.collect::<Result<Vec<_>, Error>>()?;
1300+
Ok(())
1301+
})?;
13011302

1302-
for (cmd, program, mut child, _token) in children {
1303-
wait_on_child(&cmd, &program, &mut child.0)?;
1304-
}
1303+
for obj in objs {
1304+
let (mut cmd, program) = self.create_compile_object_cmd(obj)?;
1305+
let token = server.acquire()?;
13051306

1306-
// Reacquire our process's token before we proceed, which we released
1307-
// before entering the loop above.
1308-
if reacquire {
1309-
server.acquire_raw()?;
1307+
let child = spawn(&mut cmd, &program, print.pipe_writer_cloned()?.unwrap())?;
1308+
1309+
if tx.send((cmd, program, KillOnDrop(child), token)).is_err() {
1310+
break;
1311+
}
13101312
}
1313+
drop(tx);
13111314

1312-
return Ok(());
1315+
return wait_thread.join().expect("wait_thread panics");
13131316

13141317
/// Returns a suitable `jobserver::Client` used to coordinate
13151318
/// parallelism between build scripts.
@@ -1365,6 +1368,13 @@ impl Build {
13651368
child.kill().ok();
13661369
}
13671370
}
1371+
1372+
struct JobserverToken(&'static jobserver::Client);
1373+
impl Drop for JobserverToken {
1374+
fn drop(&mut self) {
1375+
let _ = self.0.acquire_raw();
1376+
}
1377+
}
13681378
}
13691379

13701380
#[cfg(not(feature = "parallel"))]

0 commit comments

Comments
 (0)