1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
//! # Errors
//! Errors that the crate can return
use std::{error::Error, fmt, io};
/// A collection of all the errors
/// that can occur
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum RuntimeError {
/// A `libc` call failed, carrying its `errno` if there was one
CheckError(Option<i32>),
/// The kernel refused a wait on an address
AddressLock,
/// Runtime has already been previously initialised
AlreadyInit,
/// The runtime hasn't been initialised, so nothing can run
///
/// A task spawned before `Runtime::init` settles with this
/// rather than waiting for something that isn't there
NotInitialised,
/// The output was already moved out by `take()`
///
/// On a repeat, the next run may still publish another.
/// `Finished` is the one that means no more are coming
AlreadyTaken,
/// A bounded repeat reached its end, and its last output has
/// been taken
///
/// Nothing more is coming. Only repeats return this, a one
/// shot says `AlreadyTaken`
Finished,
/// The task was cancelled by one of its listeners
Cancelled,
/// There is no task behind this handle
///
/// The spawn found no slot to put it in, or the handle came
/// from a `join_first` over nothing
NoSuchTask,
/// Nothing is going to produce an output for this task
///
/// The task panicked, the thread running it died, nothing was
/// left to run it, or a repeat couldn't be rescheduled
TaskFailed,
/// The task hasn't settled yet
///
/// Also what a timed read returns when its timeout runs out
NotReady,
/// The table is too close to the number of tasks alive in it
/// to give any of it back
StillInUse,
/// A path contained a zero byte, so it couldn't be handed to
/// the kernel
///
/// For a Unix socket, also a path that is empty or longer than
/// the 103 bytes the kernel has room for
BadPath,
/// An argument couldn't be handed to the kernel as written
///
/// A program argument with a zero byte in it, or a process id
/// that names a group rather than one process
BadArgument,
/// An environment variable couldn't be handed to the kernel
///
/// Its name was empty or contained `=`, or either half
/// contained a zero byte. An `=` in the value is fine
BadVariable,
/// A working directory was relative or contained a zero byte
///
/// One that doesn't exist is reported as `ENOENT` instead
BadDirectory,
/// A run went past the timeout its task was spawned with
///
/// A receive that times out puts back what it had read, so the
/// connection can still be read from
TimedOut,
/// An address didn't parse, or a name lookup found nothing for
/// it
BadAddress,
/// The other side closed the connection before a receive had
/// everything it was waiting for
Closed,
/// A `recv_until` read as much as it was allowed without
/// finding its delimiter
TooLong,
/// A certificate was refused
///
/// The other side's didn't check out: an untrusted issuer, the
/// wrong name, or out of date. Or this side's own certificate
/// or key file didn't parse, or the two didn't match
///
/// Only TLS tasks return this, which need the `tls` feature
BadCertificate,
/// TLS failed for a reason other than a certificate
///
/// The handshake broke down, a record didn't decrypt, or the
/// other side sent an alert
///
/// Only TLS tasks return this, which need the `tls` feature
TlsFailed,
/// The signal isn't one the kernel will take
///
/// A number that isn't a signal at all, or `SIGKILL` or
/// `SIGSTOP` where one could be caught: those two can be sent,
/// but never taken over
BadSignal,
}
impl fmt::Display for RuntimeError {
/// One line saying what went wrong, with the kernel's own
/// message for a `CheckError`
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CheckError(Some(errno)) => {
write!(
formatter,
"system call failed: {}",
io::Error::from_raw_os_error(*errno)
)
}
Self::CheckError(None) => write!(formatter, "system call failed"),
Self::AddressLock => write!(formatter, "the kernel refused a wait on an address"),
Self::AlreadyInit => write!(formatter, "the runtime is already initialised"),
Self::NotInitialised => write!(formatter, "the runtime has not been initialised"),
Self::AlreadyTaken => write!(formatter, "the output was already taken"),
Self::Finished => write!(formatter, "the series ran out and its output was taken"),
Self::Cancelled => write!(formatter, "the task was cancelled"),
Self::NoSuchTask => write!(formatter, "there is no task behind this handle"),
Self::TaskFailed => write!(formatter, "the task will never produce an output"),
Self::NotReady => write!(formatter, "the task has not settled yet"),
Self::StillInUse => write!(formatter, "too much of the task table is in use to trim"),
Self::BadPath => write!(formatter, "the path can't be handed to the kernel"),
Self::BadArgument => {
write!(
formatter,
"an argument cannot be handed to the kernel as written"
)
}
Self::BadVariable => {
write!(
formatter,
"an environment variable cannot be passed on as written"
)
}
Self::BadDirectory => {
write!(
formatter,
"the working directory is not an absolute path without zero bytes"
)
}
Self::TimedOut => write!(formatter, "the task ran out of time"),
Self::BadAddress => write!(formatter, "the address could not be parsed or found"),
Self::Closed => write!(formatter, "the other side closed the connection"),
Self::TooLong => write!(formatter, "the delimiter was not found within the limit"),
Self::BadCertificate => write!(formatter, "a certificate was refused"),
Self::TlsFailed => write!(formatter, "the TLS session failed"),
Self::BadSignal => write!(formatter, "the signal cannot be used that way"),
}
}
}
impl RuntimeError {
/// The `errno` a failed system call left, if there was one
pub fn raw_os_error(&self) -> Option<i32> {
match self {
Self::CheckError(errno) => *errno,
_ => None,
}
}
}
impl Error for RuntimeError {}
/// The kernel's own error for a `CheckError`, and `Other` carrying
/// the runtime's for everything else
impl From<RuntimeError> for io::Error {
fn from(error: RuntimeError) -> Self {
match error.raw_os_error() {
Some(errno) => io::Error::from_raw_os_error(errno),
None => io::Error::other(error),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
/// Only a failed system call carries an `errno`
#[test]
fn only_a_check_error_has_an_errno() {
assert_eq!(
RuntimeError::CheckError(Some(libc::ENOENT)).raw_os_error(),
Some(libc::ENOENT)
);
assert_eq!(RuntimeError::CheckError(None).raw_os_error(), None);
assert_eq!(RuntimeError::TimedOut.raw_os_error(), None);
}
/// An `errno` becomes the kernel's own error, and anything else
/// keeps the runtime's
#[test]
fn converts_into_an_io_error() {
let os = io::Error::from(RuntimeError::CheckError(Some(libc::ENOENT)));
assert_eq!(os.kind(), io::ErrorKind::NotFound);
assert_eq!(os.raw_os_error(), Some(libc::ENOENT));
let other = io::Error::from(RuntimeError::Closed);
assert_eq!(other.kind(), io::ErrorKind::Other);
assert_eq!(
other
.get_ref()
.and_then(|inner| inner.downcast_ref::<RuntimeError>()),
Some(&RuntimeError::Closed)
);
}
}