Skip to content

Commit 5def561

Browse files
authored
Merge pull request #255 from dhardy/failure
ErrorKind: rename Transient → Unexpected and revise uses of Unavailable
2 parents 56fb29f + 4f7b402 commit 5def561

File tree

5 files changed

+124
-104
lines changed

5 files changed

+124
-104
lines changed

src/error.rs

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,33 @@ use std::error::Error as stdError;
1818
/// Error kind which can be matched over.
1919
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
2020
pub enum ErrorKind {
21-
/// Permanent failure: likely not recoverable without user action.
21+
/// Feature is not available; not recoverable.
22+
///
23+
/// This is the most permanent failure type and implies the error cannot be
24+
/// resolved simply by retrying (e.g. the feature may not exist in this
25+
/// build of the application or on the current platform).
2226
Unavailable,
23-
/// Temporary failure: recommended to retry a few times, but may also be
24-
/// irrecoverable.
27+
/// General failure; there may be a chance of recovery on retry.
28+
///
29+
/// This is the catch-all kind for errors from known and unknown sources
30+
/// which do not have a more specific kind / handling method.
31+
///
32+
/// It is suggested to retry a couple of times or retry later when
33+
/// handling; some error sources may be able to resolve themselves,
34+
/// although this is not likely.
35+
Unexpected,
36+
/// A transient failure which likely can be resolved or worked around.
37+
///
38+
/// This error kind exists for a few specific cases where it is known that
39+
/// the error likely can be resolved internally, but is reported anyway.
2540
Transient,
2641
/// Not ready yet: recommended to try again a little later.
42+
///
43+
/// This error kind implies the generator needs more time or needs some
44+
/// other part of the application to do something else first before it is
45+
/// ready for use; for example this may be used by external generators
46+
/// which require time for initialization.
2747
NotReady,
28-
/// Uncategorised error
29-
Other,
3048
#[doc(hidden)]
3149
__Nonexhaustive,
3250
}
@@ -36,10 +54,7 @@ impl ErrorKind {
3654
///
3755
/// See also `should_wait()`.
3856
pub fn should_retry(self) -> bool {
39-
match self {
40-
ErrorKind::Transient | ErrorKind::NotReady => true,
41-
_ => false,
42-
}
57+
self != ErrorKind::Unavailable
4358
}
4459

4560
/// True if we should retry but wait before retrying
@@ -52,24 +67,30 @@ impl ErrorKind {
5267
/// A description of this error kind
5368
pub fn description(self) -> &'static str {
5469
match self {
55-
ErrorKind::Unavailable => "permanent failure",
70+
ErrorKind::Unavailable => "permanently unavailable",
71+
ErrorKind::Unexpected => "unexpected failure",
5672
ErrorKind::Transient => "transient failure",
5773
ErrorKind::NotReady => "not ready yet",
58-
ErrorKind::Other => "uncategorised",
5974
ErrorKind::__Nonexhaustive => unreachable!(),
6075
}
6176
}
6277
}
6378

79+
6480
/// Error type of random number generators
6581
///
6682
/// This is a relatively simple error type, designed for compatibility with and
6783
/// without the Rust `std` library. It embeds a "kind" code, a message (static
68-
/// string only), and an optional chained cause (`std` only).
84+
/// string only), and an optional chained cause (`std` only). The `kind` and
85+
/// `msg` fields can be accessed directly; cause can be accessed via
86+
/// `std::error::Error::cause` or `Error::take_cause`. Construction can only be
87+
/// done via `Error::new` or `Error::with_cause`.
6988
#[derive(Debug)]
7089
pub struct Error {
71-
kind: ErrorKind,
72-
msg: &'static str,
90+
/// The error kind
91+
pub kind: ErrorKind,
92+
/// The error message
93+
pub msg: &'static str,
7394
#[cfg(feature="std")]
7495
cause: Option<Box<stdError + Send + Sync>>,
7596
}
@@ -110,14 +131,11 @@ impl Error {
110131
Error { kind: kind, msg: msg }
111132
}
112133

113-
/// Get the error kind
114-
pub fn kind(&self) -> ErrorKind {
115-
self.kind
116-
}
117-
118-
/// Get the error message
119-
pub fn msg(&self) -> &'static str {
120-
self.msg
134+
/// Take the cause, if any. This allows the embedded cause to be extracted.
135+
/// This uses `Option::take`, leaving `self` with no cause.
136+
#[cfg(feature="std")]
137+
pub fn take_cause(&mut self) -> Option<Box<stdError + Send + Sync>> {
138+
self.cause.take()
121139
}
122140
}
123141

@@ -126,12 +144,10 @@ impl fmt::Display for Error {
126144
#[cfg(feature="std")] {
127145
if let Some(ref cause) = self.cause {
128146
return write!(f, "{} ({}); cause: {}",
129-
self.msg(),
130-
self.kind.description(),
131-
cause);
147+
self.msg, self.kind.description(), cause);
132148
}
133149
}
134-
write!(f, "{} ({})", self.msg(), self.kind.description())
150+
write!(f, "{} ({})", self.msg, self.kind.description())
135151
}
136152
}
137153

src/jitter.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ impl ::std::error::Error for TimerError {
145145

146146
impl From<TimerError> for Error {
147147
fn from(err: TimerError) -> Error {
148+
// Timer check is already quite permissive of failures so we don't
149+
// expect false-positive failures, i.e. any error is irrecoverable.
148150
Error::with_cause(ErrorKind::Unavailable,
149151
"timer jitter failed basic quality tests", err)
150152
}

src/os.rs

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -82,31 +82,27 @@ impl RngCore for OsRng {
8282
panic!("OsRng failed too many times; last error: {}", e);
8383
}
8484

85-
match e.kind() {
86-
ErrorKind::Transient => {
87-
if !error_logged {
88-
warn!("OsRng failed; retrying up to {} times. Error: {}",
89-
TRANSIENT_RETRIES, e);
90-
error_logged = true;
91-
}
92-
err_count += (RETRY_LIMIT + TRANSIENT_RETRIES - 1)
93-
/ TRANSIENT_RETRIES; // round up
94-
continue;
95-
}
96-
ErrorKind::NotReady => {
97-
if !error_logged {
98-
warn!("OsRng failed; waiting up to {}s and retrying. Error: {}",
99-
MAX_RETRY_PERIOD, e);
100-
error_logged = true;
101-
}
102-
err_count += 1;
103-
thread::sleep(wait_dur);
104-
continue;
85+
if e.kind.should_wait() {
86+
if !error_logged {
87+
warn!("OsRng failed; waiting up to {}s and retrying. Error: {}",
88+
MAX_RETRY_PERIOD, e);
89+
error_logged = true;
10590
}
106-
_ => {
107-
error!("OsRng failed: {}", e);
108-
panic!("OsRng fatal error: {}", e);
91+
err_count += 1;
92+
thread::sleep(wait_dur);
93+
continue;
94+
} else if e.kind.should_retry() {
95+
if !error_logged {
96+
warn!("OsRng failed; retrying up to {} times. Error: {}",
97+
TRANSIENT_RETRIES, e);
98+
error_logged = true;
10999
}
100+
err_count += (RETRY_LIMIT + TRANSIENT_RETRIES - 1)
101+
/ TRANSIENT_RETRIES; // round up
102+
continue;
103+
} else {
104+
error!("OsRng failed: {}", e);
105+
panic!("OsRng fatal error: {}", e);
110106
}
111107
}
112108

@@ -147,11 +143,16 @@ impl ReadRng {
147143
let mut guard = mutex.lock().unwrap();
148144
if (*guard).is_none() {
149145
info!("OsRng: opening random device {}", path.as_ref().display());
150-
let file = File::open(path).map_err(|err| Error::with_cause(
151-
ErrorKind::Unavailable,
152-
"error opening random device",
153-
err
154-
))?;
146+
let file = File::open(path).map_err(|err| {
147+
use std::io::ErrorKind::*;
148+
match err.kind() {
149+
Interrupted => Error::new(ErrorKind::Transient, "interrupted"),
150+
WouldBlock => Error::with_cause(ErrorKind::NotReady,
151+
"opening random device would block", err),
152+
_ => Error::with_cause(ErrorKind::Unavailable,
153+
"error while opening random device", err)
154+
}
155+
})?;
155156
*guard = Some(file);
156157
}
157158

@@ -169,7 +170,13 @@ impl ReadRng {
169170
let mut file = (*guard).as_mut().unwrap();
170171
// Use `std::io::read_exact`, which retries on `ErrorKind::Interrupted`.
171172
file.read_exact(dest).map_err(|err| {
172-
Error::with_cause(ErrorKind::Unavailable, "error reading random device", err)
173+
match err.kind() {
174+
::std::io::ErrorKind::WouldBlock => Error::with_cause(
175+
ErrorKind::NotReady,
176+
"reading from random device would block", err),
177+
_ => Error::with_cause(ErrorKind::Unavailable,
178+
"error reading random device", err)
179+
}
173180
})
174181
}
175182
}

src/read.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,13 @@ impl<R: Read> RngCore for ReadRng<R> {
6262
if dest.len() == 0 { return Ok(()); }
6363
// Use `std::io::read_exact`, which retries on `ErrorKind::Interrupted`.
6464
self.reader.read_exact(dest).map_err(|err| {
65-
Error::with_cause(ErrorKind::Unavailable, "ReadRng: read error", err)
65+
match err.kind() {
66+
::std::io::ErrorKind::WouldBlock => Error::with_cause(
67+
ErrorKind::NotReady,
68+
"reading from random device would block", err),
69+
_ => Error::with_cause(ErrorKind::Unavailable,
70+
"error reading random device", err)
71+
}
6672
})
6773
}
6874
}

src/reseeding.rs

Lines changed: 37 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -83,63 +83,43 @@ impl<R: RngCore + SeedableRng, Rsdr: RngCore> ReseedingRng<R, Rsdr> {
8383
/// Reseed the internal PRNG.
8484
///
8585
/// This will try to work around errors in the RNG used for reseeding
86-
/// intelligently. If the error kind indicates retrying might help, it will
87-
/// immediately retry a couple of times. If the error kind indicates the
88-
/// seeding RNG is not ready, it will retry later, after `threshold / 256`
89-
/// generated bytes. On other errors in the source RNG, this will skip
90-
/// reseeding and continue using the internal PRNG, until another
91-
/// `threshold` bytes have been generated (at which point it will try
92-
/// reseeding again).
93-
#[inline(never)]
86+
/// intelligently through some combination of retrying and delaying
87+
/// reseeding until later. So long as the internal PRNG doesn't fail, this
88+
/// method will not fail, i.e. failures from the reseeding source are not
89+
/// fatal.
9490
pub fn reseed(&mut self) {
95-
trace!("Reseeding RNG after generating {} bytes",
96-
self.threshold - self.bytes_until_reseed);
97-
self.bytes_until_reseed = self.threshold;
98-
let mut err_count = 0;
99-
loop {
100-
if let Err(e) = R::from_rng(&mut self.reseeder)
101-
.map(|result| self.rng = result) {
102-
let kind = e.kind();
103-
if kind.should_wait() {
104-
self.bytes_until_reseed = self.threshold >> 8;
105-
warn!("Reseeding RNG delayed for {} bytes",
106-
self.bytes_until_reseed);
107-
} else if kind.should_retry() {
108-
err_count += 1;
109-
// Retry immediately for 5 times (arbitrary limit)
110-
if err_count <= 5 { continue; }
111-
}
112-
warn!("Reseeding RNG failed; continuing without reseeding. Error: {}", e);
113-
}
114-
break; // Successfully reseeded, delayed, or given up.
115-
}
91+
// Behaviour is identical to `try_reseed`; we just squelch the error.
92+
let _res = self.try_reseed();
11693
}
11794

11895
/// Reseed the internal RNG if the number of bytes that have been
11996
/// generated exceed the threshold.
12097
///
12198
/// If reseeding fails, return an error with the original cause. Note that
122-
/// if the cause has a permanent failure, we report a transient error and
123-
/// skip reseeding; this means that only two error kinds can be reported
124-
/// from this method: `ErrorKind::Transient` and `ErrorKind::NotReady`.
99+
/// in case of error we simply delay reseeding, allowing the generator to
100+
/// continue its output of random data and try reseeding again later;
101+
/// because of this we always return kind `ErrorKind::Transient`.
125102
#[inline(never)]
126103
pub fn try_reseed(&mut self) -> Result<(), Error> {
127104
trace!("Reseeding RNG after {} generated bytes",
128105
self.threshold - self.bytes_until_reseed);
129-
if let Err(err) = R::from_rng(&mut self.reseeder)
130-
.map(|result| self.rng = result) {
131-
let newkind = match err.kind() {
132-
a @ ErrorKind::NotReady => a,
133-
b @ ErrorKind::Transient => b,
134-
_ => {
135-
self.bytes_until_reseed = self.threshold; // skip reseeding
136-
ErrorKind::Transient
137-
}
106+
if let Err(mut e) = R::from_rng(&mut self.reseeder)
107+
.map(|result| self.rng = result)
108+
{
109+
let delay = match e.kind {
110+
ErrorKind::Transient => 0,
111+
kind @ _ if kind.should_retry() => self.threshold >> 8,
112+
_ => self.threshold,
138113
};
139-
return Err(Error::with_cause(newkind, "reseeding failed", err));
114+
warn!("Reseeding RNG delayed reseeding by {} bytes due to \
115+
error from source: {}", delay, e);
116+
self.bytes_until_reseed = delay;
117+
e.kind = ErrorKind::Transient;
118+
Err(e)
119+
} else {
120+
self.bytes_until_reseed = self.threshold;
121+
Ok(())
140122
}
141-
self.bytes_until_reseed = self.threshold;
142-
Ok(())
143123
}
144124
}
145125

@@ -171,12 +151,21 @@ impl<R: RngCore + SeedableRng, Rsdr: RngCore> RngCore for ReseedingRng<R, Rsdr>
171151
}
172152

173153
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
174-
self.rng.try_fill_bytes(dest)?;
154+
let res1 = self.rng.try_fill_bytes(dest);
175155
self.bytes_until_reseed -= dest.len() as i64;
176-
if self.bytes_until_reseed <= 0 {
177-
self.try_reseed()?;
156+
let res2 = if self.bytes_until_reseed <= 0 {
157+
self.try_reseed()
158+
} else { Ok(()) };
159+
160+
if let Err(e) = res1 {
161+
// In the unlikely event the internal PRNG fails, we don't know
162+
// whether this is resolvable; reseed immediately and return
163+
// original error kind.
164+
self.bytes_until_reseed = 0;
165+
Err(e)
166+
} else {
167+
res2
178168
}
179-
Ok(())
180169
}
181170
}
182171

0 commit comments

Comments
 (0)