@@ -8,7 +8,6 @@ pub use sandbox::*;
8
8
9
9
use crate :: native;
10
10
use crate :: workspace:: Workspace ;
11
- use failure:: { Error , Fail } ;
12
11
use futures_util:: {
13
12
future:: { self , FutureExt } ,
14
13
stream:: { self , TryStreamExt } ,
@@ -58,20 +57,79 @@ pub(crate) mod container_dirs {
58
57
}
59
58
60
59
/// Error happened while executing a command.
61
- #[ derive( Debug , Fail ) ]
60
+ #[ derive( Debug , thiserror :: Error ) ]
62
61
#[ non_exhaustive]
63
62
pub enum CommandError {
64
63
/// The command didn't output anything to stdout or stderr for more than the timeout, and it
65
64
/// was killed. The timeout's value (in seconds) is the first value.
66
- #[ fail ( display = "no output for {} seconds" , _0 ) ]
65
+ #[ error ( "no output for {0 } seconds" ) ]
67
66
NoOutputFor ( u64 ) ,
67
+
68
68
/// The command took more time than the timeout to end, and it was killed. The timeout's value
69
69
/// (in seconds) is the first value.
70
- #[ fail ( display = "command timed out after {} seconds" , _0 ) ]
70
+ #[ error ( "command timed out after {0 } seconds" ) ]
71
71
Timeout ( u64 ) ,
72
+
73
+ /// The command failed to execute.
74
+ #[ error( "command failed: {0}" ) ]
75
+ ExecutionFailed ( ExitStatus ) ,
76
+
77
+ /// Killing the underlying process after the timeout failed.
78
+ #[ error( "{0}" ) ]
79
+ KillAfterTimeoutFailed ( #[ source] KillFailedError ) ,
80
+
72
81
/// The sandbox ran out of memory and was killed.
73
- #[ fail ( display = "container ran out of memory" ) ]
82
+ #[ error ( "container ran out of memory" ) ]
74
83
SandboxOOM ,
84
+
85
+ /// Pulling a sandbox image from the registry failed
86
+ #[ error( "failed to pull the sandbox image from the registry: {0}" ) ]
87
+ SandboxImagePullFailed ( #[ source] Box < CommandError > ) ,
88
+
89
+ /// The sandbox image is missing from the local system.
90
+ #[ error( "sandbox image missing from the local system: {0}" ) ]
91
+ SandboxImageMissing ( #[ source] Box < CommandError > ) ,
92
+
93
+ /// Running rustwide inside a Docker container requires the workspace directory to be mounted
94
+ /// from the host system. This error happens if that's not true, for example if the workspace
95
+ /// lives in a directory inside the container.
96
+ #[ error( "the workspace is not mounted from outside the container" ) ]
97
+ WorkspaceNotMountedCorrectly ,
98
+
99
+ /// The data received from the `docker inspect` command is not valid.
100
+ #[ error( "invalid output of `docker inspect`: {0}" ) ]
101
+ InvalidDockerInspectOutput ( #[ source] serde_json:: Error ) ,
102
+
103
+ /// An I/O error occured while executing the command.
104
+ #[ error( transparent) ]
105
+ IO ( #[ from] std:: io:: Error ) ,
106
+ }
107
+
108
+ /// Error happened while trying to kill a process.
109
+ #[ derive( Debug , thiserror:: Error ) ]
110
+ #[ cfg_attr( unix, error(
111
+ "failed to kill the process with PID {pid}{}" ,
112
+ . errno. map( |e| format!( ": {}" , e. desc( ) ) ) . unwrap_or_else( String :: new)
113
+ ) ) ]
114
+ #[ cfg_attr( not( unix) , error( "failed to kill the process with PID {pid}" ) ) ]
115
+ pub struct KillFailedError {
116
+ pub ( crate ) pid : u32 ,
117
+ #[ cfg( unix) ]
118
+ pub ( crate ) errno : Option < nix:: errno:: Errno > ,
119
+ }
120
+
121
+ impl KillFailedError {
122
+ /// Return the PID of the process that couldn't be killed.
123
+ pub fn pid ( & self ) -> u32 {
124
+ self . pid
125
+ }
126
+
127
+ /// Return the underlying error number provided by the operative system.
128
+ #[ cfg( any( unix, doc) ) ]
129
+ #[ cfg_attr( docs_rs, doc( cfg( unix) ) ) ]
130
+ pub fn errno ( & self ) -> Option < i32 > {
131
+ self . errno . map ( |errno| errno as i32 )
132
+ }
75
133
}
76
134
77
135
/// Name and kind of a binary executed by [`Command`](struct.Command.html).
@@ -285,7 +343,7 @@ impl<'w, 'pl> Command<'w, 'pl> {
285
343
286
344
/// Run the prepared command and return an error if it fails (for example with a non-zero exit
287
345
/// code or a timeout).
288
- pub fn run ( self ) -> Result < ( ) , Error > {
346
+ pub fn run ( self ) -> Result < ( ) , CommandError > {
289
347
self . run_inner ( false ) ?;
290
348
Ok ( ( ) )
291
349
}
@@ -296,11 +354,11 @@ impl<'w, 'pl> Command<'w, 'pl> {
296
354
/// Even though the output will be captured and returned, if output logging is enabled (as it
297
355
/// is by default) the output will be also logged. You can disable this behavior by calling the
298
356
/// [`log_output`](struct.Command.html#method.log_output) method.
299
- pub fn run_capture ( self ) -> Result < ProcessOutput , Error > {
357
+ pub fn run_capture ( self ) -> Result < ProcessOutput , CommandError > {
300
358
Ok ( self . run_inner ( true ) ?)
301
359
}
302
360
303
- fn run_inner ( self , capture : bool ) -> Result < ProcessOutput , Error > {
361
+ fn run_inner ( self , capture : bool ) -> Result < ProcessOutput , CommandError > {
304
362
if let Some ( mut builder) = self . sandbox {
305
363
let workspace = self
306
364
. workspace
@@ -438,7 +496,7 @@ impl<'w, 'pl> Command<'w, 'pl> {
438
496
if out. status . success ( ) {
439
497
Ok ( out. into ( ) )
440
498
} else {
441
- failure :: bail! ( "command `{}` failed" , cmdstr ) ;
499
+ Err ( CommandError :: ExecutionFailed ( out . status ) )
442
500
}
443
501
}
444
502
}
@@ -499,7 +557,7 @@ async fn log_command(
499
557
timeout : Option < Duration > ,
500
558
no_output_timeout : Option < Duration > ,
501
559
log_output : bool ,
502
- ) -> Result < InnerProcessOutput , Error > {
560
+ ) -> Result < InnerProcessOutput , CommandError > {
503
561
let timeout = if let Some ( t) = timeout {
504
562
t
505
563
} else {
@@ -532,12 +590,12 @@ async fn log_command(
532
590
. map ( move |result| match result {
533
591
// If the timeout elapses, kill the process
534
592
Err ( _timeout) => Err ( match native:: kill_process ( child_id) {
535
- Ok ( ( ) ) => Error :: from ( CommandError :: NoOutputFor ( no_output_timeout. as_secs ( ) ) ) ,
536
- Err ( err) => err,
593
+ Ok ( ( ) ) => CommandError :: NoOutputFor ( no_output_timeout. as_secs ( ) ) ,
594
+ Err ( err) => CommandError :: KillAfterTimeoutFailed ( err) ,
537
595
} ) ,
538
596
539
597
// If an error occurred reading the line, flatten the error
540
- Ok ( ( _, Err ( read_err) ) ) => Err ( Error :: from ( read_err) ) ,
598
+ Ok ( ( _, Err ( read_err) ) ) => Err ( read_err. into ( ) ) ,
541
599
542
600
// If the read was successful, return the `OutputKind` and the read line
543
601
Ok ( ( out_kind, Ok ( line) ) ) => Ok ( ( out_kind, line) ) ,
@@ -546,7 +604,7 @@ async fn log_command(
546
604
// If the process is in a tight output loop the timeout on the process might fail to
547
605
// be executed, so this extra check prevents the process to run without limits.
548
606
if start. elapsed ( ) > timeout {
549
- return future:: err ( Error :: from ( CommandError :: Timeout ( timeout. as_secs ( ) ) ) ) ;
607
+ return future:: err ( CommandError :: Timeout ( timeout. as_secs ( ) ) ) ;
550
608
}
551
609
552
610
if let Some ( f) = & mut process_lines {
@@ -587,12 +645,12 @@ async fn log_command(
587
645
match result {
588
646
// If the timeout elapses, kill the process
589
647
Err ( _timeout) => Err ( match native:: kill_process ( child_id) {
590
- Ok ( ( ) ) => Error :: from ( CommandError :: Timeout ( timeout. as_secs ( ) ) ) ,
591
- Err ( err) => err,
648
+ Ok ( ( ) ) => CommandError :: Timeout ( timeout. as_secs ( ) ) ,
649
+ Err ( err) => CommandError :: KillAfterTimeoutFailed ( err) ,
592
650
} ) ,
593
651
594
652
// If an error occurred with the child
595
- Ok ( Err ( err) ) => Err ( Error :: from ( err) ) ,
653
+ Ok ( Err ( err) ) => Err ( err. into ( ) ) ,
596
654
597
655
// If the read was successful, return the process's exit status
598
656
Ok ( Ok ( exit_status) ) => Ok ( exit_status) ,
0 commit comments