@@ -66,8 +66,9 @@ use std::process::{Child, Command, Stdio};
6666use std:: sync:: { Arc , Mutex } ;
6767use std:: thread:: { self , JoinHandle } ;
6868
69+ #[ cfg( feature = "parallel" ) ]
70+ mod job_token;
6971mod os_pipe;
70-
7172// These modules are all glue to support reading the MSVC version from
7273// the registry and from COM interfaces
7374#[ cfg( windows) ]
@@ -1294,7 +1295,7 @@ impl Build {
12941295
12951296 #[ cfg( feature = "parallel" ) ]
12961297 fn compile_objects ( & self , objs : & [ Object ] , print : & PrintThread ) -> Result < ( ) , Error > {
1297- use std:: sync:: { mpsc, Once } ;
1298+ use std:: sync:: mpsc;
12981299
12991300 if objs. len ( ) <= 1 {
13001301 for obj in objs {
@@ -1305,14 +1306,8 @@ impl Build {
13051306 return Ok ( ( ) ) ;
13061307 }
13071308
1308- // Limit our parallelism globally with a jobserver. Start off by
1309- // releasing our own token for this process so we can have a bit of an
1310- // easier to write loop below. If this fails, though, then we're likely
1311- // on Windows with the main implicit token, so we just have a bit extra
1312- // parallelism for a bit and don't reacquire later.
1313- let server = jobserver ( ) ;
1314- // Reacquire our process's token on drop
1315- let _reacquire = server. release_raw ( ) . ok ( ) . map ( |_| JobserverToken ( server) ) ;
1309+ // Limit our parallelism globally with a jobserver.
1310+ let tokens = crate :: job_token:: JobTokenServer :: new ( ) ;
13161311
13171312 // When compiling objects in parallel we do a few dirty tricks to speed
13181313 // things up:
@@ -1333,7 +1328,7 @@ impl Build {
13331328 // acquire the appropriate tokens, Once all objects have been compiled
13341329 // we wait on all the processes and propagate the results of compilation.
13351330
1336- let ( tx, rx) = mpsc:: channel :: < ( _ , String , KillOnDrop , _ ) > ( ) ;
1331+ let ( tx, rx) = mpsc:: channel :: < ( _ , String , KillOnDrop , crate :: job_token :: JobToken ) > ( ) ;
13371332
13381333 // Since jobserver::Client::acquire can block, waiting
13391334 // must be done in parallel so that acquire won't block forever.
@@ -1346,7 +1341,13 @@ impl Build {
13461341
13471342 loop {
13481343 let mut has_made_progress = false ;
1349-
1344+ // If the other end of the pipe is already disconnected, then we're not gonna get any new jobs,
1345+ // so it doesn't make sense to reuse the tokens; in fact,
1346+ // releasing them as soon as possible (once we know that the other end is disconnected) is beneficial.
1347+ // Imagine that the last file built takes an hour to finish; in this scenario,
1348+ // by not releasing the tokens before that last file is done we would effectively block other processes from
1349+ // starting sooner - even though we only need one token for that last file, not N others that were acquired.
1350+ let mut is_disconnected = false ;
13501351 // Reading new pending tasks
13511352 loop {
13521353 match rx. try_recv ( ) {
@@ -1362,23 +1363,32 @@ impl Build {
13621363 Ok ( ( ) )
13631364 } ;
13641365 }
1366+ Err ( mpsc:: TryRecvError :: Disconnected ) => {
1367+ is_disconnected = true ;
1368+ break ;
1369+ }
13651370 _ => break ,
13661371 }
13671372 }
13681373
13691374 // Try waiting on them.
1370- pendings. retain_mut ( |( cmd, program, child, _ ) | {
1375+ pendings. retain_mut ( |( cmd, program, child, token ) | {
13711376 match try_wait_on_child ( cmd, program, & mut child. 0 , & mut stdout) {
13721377 Ok ( Some ( ( ) ) ) => {
13731378 // Task done, remove the entry
1379+ if is_disconnected {
1380+ token. forget ( ) ;
1381+ }
13741382 has_made_progress = true ;
13751383 false
13761384 }
13771385 Ok ( None ) => true , // Task still not finished, keep the entry
13781386 Err ( err) => {
13791387 // Task fail, remove the entry.
13801388 has_made_progress = true ;
1381-
1389+ if is_disconnected {
1390+ token. forget ( ) ;
1391+ }
13821392 // Since we can only return one error, log the error to make
13831393 // sure users always see all the compilation failures.
13841394 let _ = writeln ! ( stdout, "cargo:warning={}" , err) ;
@@ -1416,11 +1426,9 @@ impl Build {
14161426 } ;
14171427 }
14181428 } ) ?;
1419-
14201429 for obj in objs {
14211430 let ( mut cmd, program) = self . create_compile_object_cmd ( obj) ?;
1422- let token = server. acquire ( ) ?;
1423-
1431+ let token = tokens. acquire ( ) ?;
14241432 let child = spawn ( & mut cmd, & program, print. pipe_writer_cloned ( ) ?. unwrap ( ) ) ?;
14251433
14261434 tx. send ( ( cmd, program, KillOnDrop ( child) , token) )
@@ -1431,51 +1439,6 @@ impl Build {
14311439
14321440 return wait_thread. join ( ) . expect ( "wait_thread panics" ) ;
14331441
1434- /// Returns a suitable `jobserver::Client` used to coordinate
1435- /// parallelism between build scripts.
1436- fn jobserver ( ) -> & ' static jobserver:: Client {
1437- static INIT : Once = Once :: new ( ) ;
1438- static mut JOBSERVER : Option < jobserver:: Client > = None ;
1439-
1440- fn _assert_sync < T : Sync > ( ) { }
1441- _assert_sync :: < jobserver:: Client > ( ) ;
1442-
1443- unsafe {
1444- INIT . call_once ( || {
1445- let server = default_jobserver ( ) ;
1446- JOBSERVER = Some ( server) ;
1447- } ) ;
1448- JOBSERVER . as_ref ( ) . unwrap ( )
1449- }
1450- }
1451-
1452- unsafe fn default_jobserver ( ) -> jobserver:: Client {
1453- // Try to use the environmental jobserver which Cargo typically
1454- // initializes for us...
1455- if let Some ( client) = jobserver:: Client :: from_env ( ) {
1456- return client;
1457- }
1458-
1459- // ... but if that fails for whatever reason select something
1460- // reasonable and crate a new jobserver. Use `NUM_JOBS` if set (it's
1461- // configured by Cargo) and otherwise just fall back to a
1462- // semi-reasonable number. Note that we could use `num_cpus` here
1463- // but it's an extra dependency that will almost never be used, so
1464- // it's generally not too worth it.
1465- let mut parallelism = 4 ;
1466- if let Ok ( amt) = env:: var ( "NUM_JOBS" ) {
1467- if let Ok ( amt) = amt. parse ( ) {
1468- parallelism = amt;
1469- }
1470- }
1471-
1472- // If we create our own jobserver then be sure to reserve one token
1473- // for ourselves.
1474- let client = jobserver:: Client :: new ( parallelism) . expect ( "failed to create jobserver" ) ;
1475- client. acquire_raw ( ) . expect ( "failed to acquire initial" ) ;
1476- return client;
1477- }
1478-
14791442 struct KillOnDrop ( Child ) ;
14801443
14811444 impl Drop for KillOnDrop {
@@ -1485,13 +1448,6 @@ impl Build {
14851448 child. kill ( ) . ok ( ) ;
14861449 }
14871450 }
1488-
1489- struct JobserverToken ( & ' static jobserver:: Client ) ;
1490- impl Drop for JobserverToken {
1491- fn drop ( & mut self ) {
1492- let _ = self . 0 . acquire_raw ( ) ;
1493- }
1494- }
14951451 }
14961452
14971453 #[ cfg( not( feature = "parallel" ) ) ]
0 commit comments