@@ -9,23 +9,34 @@ pub use sandbox::*;
9
9
use crate :: native;
10
10
use crate :: workspace:: Workspace ;
11
11
use failure:: { Error , Fail } ;
12
- use futures:: { future, Future , Stream } ;
12
+ use futures_util:: {
13
+ future:: { self , FutureExt } ,
14
+ stream:: { self , TryStreamExt } ,
15
+ } ;
13
16
use log:: { error, info} ;
14
17
use process_lines_actions:: InnerState ;
15
18
use std:: convert:: AsRef ;
16
19
use std:: env:: consts:: EXE_SUFFIX ;
17
20
use std:: ffi:: { OsStr , OsString } ;
18
- use std:: io:: BufReader ;
19
21
use std:: path:: { Path , PathBuf } ;
20
- use std:: process:: { Command as StdCommand , ExitStatus , Stdio } ;
22
+ use std:: process:: { ExitStatus , Stdio } ;
21
23
use std:: time:: { Duration , Instant } ;
22
- use tokio:: { io:: lines, runtime:: current_thread:: block_on_all, util:: * } ;
23
- use tokio_process:: CommandExt ;
24
+ use tokio:: {
25
+ io:: { AsyncBufReadExt , BufReader } ,
26
+ process:: Command as AsyncCommand ,
27
+ runtime:: Runtime ,
28
+ stream:: StreamExt ,
29
+ time,
30
+ } ;
31
+
32
+ lazy_static:: lazy_static! {
33
+ // TODO: Migrate to asynchronous code and remove runtime
34
+ pub ( super ) static ref RUNTIME : Runtime = Runtime :: new( ) . expect( "Failed to construct tokio runtime" ) ;
35
+ }
24
36
25
37
pub ( crate ) mod container_dirs {
26
- use std:: path:: { Path , PathBuf } ;
27
-
28
38
use lazy_static:: lazy_static;
39
+ use std:: path:: { Path , PathBuf } ;
29
40
30
41
#[ cfg( windows) ]
31
42
lazy_static ! {
@@ -371,7 +382,7 @@ impl<'w, 'pl> Command<'w, 'pl> {
371
382
) ,
372
383
Binary :: __NonExaustive => panic ! ( "do not create __NonExaustive variants manually" ) ,
373
384
} ;
374
- let mut cmd = StdCommand :: new ( crate :: utils:: normalize_path ( & binary) ) ;
385
+ let mut cmd = AsyncCommand :: new ( crate :: utils:: normalize_path ( & binary) ) ;
375
386
376
387
cmd. args ( & self . args ) ;
377
388
@@ -411,18 +422,21 @@ impl<'w, 'pl> Command<'w, 'pl> {
411
422
if self . log_command {
412
423
info ! ( "running `{}`" , cmdstr) ;
413
424
}
414
- let out = log_command (
415
- cmd,
416
- self . process_lines ,
417
- capture,
418
- self . timeout ,
419
- self . no_output_timeout ,
420
- self . log_output ,
421
- )
422
- . map_err ( |e| {
423
- error ! ( "error running command: {}" , e) ;
424
- e
425
- } ) ?;
425
+
426
+ let out = RUNTIME
427
+ . handle ( )
428
+ . block_on ( log_command (
429
+ cmd,
430
+ self . process_lines ,
431
+ capture,
432
+ self . timeout ,
433
+ self . no_output_timeout ,
434
+ self . log_output ,
435
+ ) )
436
+ . map_err ( |e| {
437
+ error ! ( "error running command: {}" , e) ;
438
+ e
439
+ } ) ?;
426
440
427
441
if out. status . success ( ) {
428
442
Ok ( out. into ( ) )
@@ -481,8 +495,8 @@ impl OutputKind {
481
495
}
482
496
}
483
497
484
- fn log_command (
485
- mut cmd : StdCommand ,
498
+ async fn log_command (
499
+ mut cmd : AsyncCommand ,
486
500
mut process_lines : Option < & mut dyn FnMut ( & str , & mut ProcessLinesActions ) > ,
487
501
capture : bool ,
488
502
timeout : Option < Duration > ,
@@ -503,34 +517,35 @@ fn log_command(
503
517
timeout
504
518
} ;
505
519
506
- let mut child = cmd
507
- . stdout ( Stdio :: piped ( ) )
508
- . stderr ( Stdio :: piped ( ) )
509
- . spawn_async ( ) ?;
520
+ let mut child = cmd. stdout ( Stdio :: piped ( ) ) . stderr ( Stdio :: piped ( ) ) . spawn ( ) ?;
510
521
let child_id = child. id ( ) ;
511
522
512
- let stdout = lines ( BufReader :: new ( child. stdout ( ) . take ( ) . unwrap ( ) ) )
523
+ let stdout = BufReader :: new ( child. stdout . take ( ) . unwrap ( ) )
524
+ . lines ( )
513
525
. map ( |line| ( OutputKind :: Stdout , line) ) ;
514
- let stderr = lines ( BufReader :: new ( child. stderr ( ) . take ( ) . unwrap ( ) ) )
526
+ let stderr = BufReader :: new ( child. stderr . take ( ) . unwrap ( ) )
527
+ . lines ( )
515
528
. map ( |line| ( OutputKind :: Stderr , line) ) ;
516
529
517
530
let start = Instant :: now ( ) ;
518
531
let mut actions = ProcessLinesActions :: new ( ) ;
519
532
520
- let output = stdout
521
- . select ( stderr)
533
+ let output = stream:: select ( stdout, stderr)
522
534
. timeout ( no_output_timeout)
523
- . map_err ( move |err| {
524
- if err. is_elapsed ( ) {
525
- match native:: kill_process ( child_id) {
526
- Ok ( ( ) ) => Error :: from ( CommandError :: NoOutputFor ( no_output_timeout. as_secs ( ) ) ) ,
527
- Err ( err) => err,
528
- }
529
- } else {
530
- Error :: from ( err)
531
- }
535
+ . map ( move |result| match result {
536
+ // If the timeout elapses, kill the process
537
+ Err ( _timeout) => Err ( match native:: kill_process ( child_id) {
538
+ Ok ( ( ) ) => Error :: from ( CommandError :: NoOutputFor ( no_output_timeout. as_secs ( ) ) ) ,
539
+ Err ( err) => err,
540
+ } ) ,
541
+
542
+ // If an error occurred reading the line, flatten the error
543
+ Ok ( ( _, Err ( read_err) ) ) => Err ( Error :: from ( read_err) ) ,
544
+
545
+ // If the read was successful, return the `OutputKind` and the read line
546
+ Ok ( ( out_kind, Ok ( line) ) ) => Ok ( ( out_kind, line) ) ,
532
547
} )
533
- . and_then ( move |( kind, line) | {
548
+ . and_then ( move |( kind, line) : ( OutputKind , String ) | {
534
549
// If the process is in a tight output loop the timeout on the process might fail to
535
550
// be executed, so this extra check prevents the process to run without limits.
536
551
if start. elapsed ( ) > timeout {
@@ -555,31 +570,44 @@ fn log_command(
555
570
556
571
future:: ok ( ( kind, lines) )
557
572
} )
558
- . fold (
559
- ( Vec :: new ( ) , Vec :: new ( ) ) ,
560
- move |mut res, ( kind, mut lines) | -> Result < _ , Error > {
573
+ . try_fold (
574
+ ( Vec :: < String > :: new ( ) , Vec :: < String > :: new ( ) ) ,
575
+ move |( mut stdout, mut stderr) , ( kind, mut lines) | async move {
576
+ // If stdio/stdout is supposed to be captured, append it to
577
+ // the accumulated stdio/stdout
561
578
if capture {
562
579
match kind {
563
- OutputKind :: Stdout => res . 0 . append ( & mut lines) ,
564
- OutputKind :: Stderr => res . 1 . append ( & mut lines) ,
580
+ OutputKind :: Stdout => stdout . append ( & mut lines) ,
581
+ OutputKind :: Stderr => stderr . append ( & mut lines) ,
565
582
}
566
583
}
567
- Ok ( res)
584
+
585
+ Ok ( ( stdout, stderr) )
568
586
} ,
569
587
) ;
570
588
571
- let child = child. timeout ( timeout) . map_err ( move |err| {
572
- if err. is_elapsed ( ) {
573
- match native:: kill_process ( child_id) {
589
+ let child = time:: timeout ( timeout, child) . map ( move |result| {
590
+ match result {
591
+ // If the timeout elapses, kill the process
592
+ Err ( _timeout) => Err ( match native:: kill_process ( child_id) {
574
593
Ok ( ( ) ) => Error :: from ( CommandError :: Timeout ( timeout. as_secs ( ) ) ) ,
575
594
Err ( err) => err,
576
- }
577
- } else {
578
- Error :: from ( err)
595
+ } ) ,
596
+
597
+ // If an error occurred with the child
598
+ Ok ( Err ( err) ) => Err ( Error :: from ( err) ) ,
599
+
600
+ // If the read was successful, return the process's exit status
601
+ Ok ( Ok ( exit_status) ) => Ok ( exit_status) ,
579
602
}
580
603
} ) ;
581
604
582
- let ( ( stdout, stderr) , status) = block_on_all ( output. join ( child) ) ?;
605
+ let ( ( stdout, stderr) , status) = {
606
+ let ( output, child) = future:: join ( output, child) . await ;
607
+ let ( stdout, stderr) = output?;
608
+
609
+ ( ( stdout, stderr) , child?)
610
+ } ;
583
611
584
612
Ok ( InnerProcessOutput {
585
613
status,
0 commit comments