@@ -66,8 +66,9 @@ use std::process::{Child, Command, Stdio};
66
66
use std:: sync:: { Arc , Mutex } ;
67
67
use std:: thread:: { self , JoinHandle } ;
68
68
69
+ #[ cfg( feature = "parallel" ) ]
70
+ mod job_token;
69
71
mod os_pipe;
70
-
71
72
// These modules are all glue to support reading the MSVC version from
72
73
// the registry and from COM interfaces
73
74
#[ cfg( windows) ]
@@ -1294,7 +1295,7 @@ impl Build {
1294
1295
1295
1296
#[ cfg( feature = "parallel" ) ]
1296
1297
fn compile_objects ( & self , objs : & [ Object ] , print : & PrintThread ) -> Result < ( ) , Error > {
1297
- use std:: sync:: { mpsc, Once } ;
1298
+ use std:: sync:: mpsc;
1298
1299
1299
1300
if objs. len ( ) <= 1 {
1300
1301
for obj in objs {
@@ -1305,14 +1306,8 @@ impl Build {
1305
1306
return Ok ( ( ) ) ;
1306
1307
}
1307
1308
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 ( ) ;
1316
1311
1317
1312
// When compiling objects in parallel we do a few dirty tricks to speed
1318
1313
// things up:
@@ -1333,7 +1328,7 @@ impl Build {
1333
1328
// acquire the appropriate tokens, Once all objects have been compiled
1334
1329
// we wait on all the processes and propagate the results of compilation.
1335
1330
1336
- let ( tx, rx) = mpsc:: channel :: < ( _ , String , KillOnDrop , _ ) > ( ) ;
1331
+ let ( tx, rx) = mpsc:: channel :: < ( _ , String , KillOnDrop , crate :: job_token :: JobToken ) > ( ) ;
1337
1332
1338
1333
// Since jobserver::Client::acquire can block, waiting
1339
1334
// must be done in parallel so that acquire won't block forever.
@@ -1346,7 +1341,13 @@ impl Build {
1346
1341
1347
1342
loop {
1348
1343
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 ;
1350
1351
// Reading new pending tasks
1351
1352
loop {
1352
1353
match rx. try_recv ( ) {
@@ -1362,23 +1363,32 @@ impl Build {
1362
1363
Ok ( ( ) )
1363
1364
} ;
1364
1365
}
1366
+ Err ( mpsc:: TryRecvError :: Disconnected ) => {
1367
+ is_disconnected = true ;
1368
+ break ;
1369
+ }
1365
1370
_ => break ,
1366
1371
}
1367
1372
}
1368
1373
1369
1374
// Try waiting on them.
1370
- pendings. retain_mut ( |( cmd, program, child, _ ) | {
1375
+ pendings. retain_mut ( |( cmd, program, child, token ) | {
1371
1376
match try_wait_on_child ( cmd, program, & mut child. 0 , & mut stdout) {
1372
1377
Ok ( Some ( ( ) ) ) => {
1373
1378
// Task done, remove the entry
1379
+ if is_disconnected {
1380
+ token. forget ( ) ;
1381
+ }
1374
1382
has_made_progress = true ;
1375
1383
false
1376
1384
}
1377
1385
Ok ( None ) => true , // Task still not finished, keep the entry
1378
1386
Err ( err) => {
1379
1387
// Task fail, remove the entry.
1380
1388
has_made_progress = true ;
1381
-
1389
+ if is_disconnected {
1390
+ token. forget ( ) ;
1391
+ }
1382
1392
// Since we can only return one error, log the error to make
1383
1393
// sure users always see all the compilation failures.
1384
1394
let _ = writeln ! ( stdout, "cargo:warning={}" , err) ;
@@ -1416,11 +1426,9 @@ impl Build {
1416
1426
} ;
1417
1427
}
1418
1428
} ) ?;
1419
-
1420
1429
for obj in objs {
1421
1430
let ( mut cmd, program) = self . create_compile_object_cmd ( obj) ?;
1422
- let token = server. acquire ( ) ?;
1423
-
1431
+ let token = tokens. acquire ( ) ?;
1424
1432
let child = spawn ( & mut cmd, & program, print. pipe_writer_cloned ( ) ?. unwrap ( ) ) ?;
1425
1433
1426
1434
tx. send ( ( cmd, program, KillOnDrop ( child) , token) )
@@ -1431,51 +1439,6 @@ impl Build {
1431
1439
1432
1440
return wait_thread. join ( ) . expect ( "wait_thread panics" ) ;
1433
1441
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
-
1479
1442
struct KillOnDrop ( Child ) ;
1480
1443
1481
1444
impl Drop for KillOnDrop {
@@ -1485,13 +1448,6 @@ impl Build {
1485
1448
child. kill ( ) . ok ( ) ;
1486
1449
}
1487
1450
}
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
- }
1495
1451
}
1496
1452
1497
1453
#[ cfg( not( feature = "parallel" ) ) ]
0 commit comments