-
-
Notifications
You must be signed in to change notification settings - Fork 463
ErrorKind: rename Transient → Unexpected and revise uses of Unavailable #255
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,15 +18,33 @@ use std::error::Error as stdError; | |
/// Error kind which can be matched over. | ||
#[derive(PartialEq, Eq, Debug, Copy, Clone)] | ||
pub enum ErrorKind { | ||
/// Permanent failure: likely not recoverable without user action. | ||
/// Feature is not available; not recoverable. | ||
/// | ||
/// This is the most permanent failure type and implies the error cannot be | ||
/// resolved simply by retrying (e.g. the feature may not exist in this | ||
/// build of the application or on the current platform). | ||
Unavailable, | ||
/// Temporary failure: recommended to retry a few times, but may also be | ||
/// irrecoverable. | ||
/// General failure; there may be a chance of recovery on retry. | ||
/// | ||
/// This is the catch-all kind for errors from known and unknown sources | ||
/// which do not have a more specific kind / handling method. | ||
/// | ||
/// It is suggested to retry a couple of times or retry later when | ||
/// handling; some error sources may be able to resolve themselves, | ||
/// although this is not likely. | ||
Unexpected, | ||
/// A transient failure which likely can be resolved or worked around. | ||
/// | ||
/// This error kind exists for a few specific cases where it is known that | ||
/// the error likely can be resolved internally, but is reported anyway. | ||
Transient, | ||
/// Not ready yet: recommended to try again a little later. | ||
/// | ||
/// This error kind implies the generator needs more time or needs some | ||
/// other part of the application to do something else first before it is | ||
/// ready for use; for example this may be used by external generators | ||
/// which require time for initialization. | ||
NotReady, | ||
/// Uncategorised error | ||
Other, | ||
#[doc(hidden)] | ||
__Nonexhaustive, | ||
} | ||
|
@@ -36,10 +54,7 @@ impl ErrorKind { | |
/// | ||
/// See also `should_wait()`. | ||
pub fn should_retry(self) -> bool { | ||
match self { | ||
ErrorKind::Transient | ErrorKind::NotReady => true, | ||
_ => false, | ||
} | ||
self != ErrorKind::Unavailable | ||
} | ||
|
||
/// True if we should retry but wait before retrying | ||
|
@@ -52,24 +67,30 @@ impl ErrorKind { | |
/// A description of this error kind | ||
pub fn description(self) -> &'static str { | ||
match self { | ||
ErrorKind::Unavailable => "permanent failure", | ||
ErrorKind::Unavailable => "permanently unavailable", | ||
ErrorKind::Unexpected => "unexpected failure", | ||
ErrorKind::Transient => "transient failure", | ||
ErrorKind::NotReady => "not ready yet", | ||
ErrorKind::Other => "uncategorised", | ||
ErrorKind::__Nonexhaustive => unreachable!(), | ||
} | ||
} | ||
} | ||
|
||
|
||
/// Error type of random number generators | ||
/// | ||
/// This is a relatively simple error type, designed for compatibility with and | ||
/// without the Rust `std` library. It embeds a "kind" code, a message (static | ||
/// string only), and an optional chained cause (`std` only). | ||
/// string only), and an optional chained cause (`std` only). The `kind` and | ||
/// `msg` fields can be accessed directly; cause can be accessed via | ||
/// `std::error::Error::cause` or `Error::take_cause`. Construction can only be | ||
/// done via `Error::new` or `Error::with_cause`. | ||
#[derive(Debug)] | ||
pub struct Error { | ||
kind: ErrorKind, | ||
msg: &'static str, | ||
/// The error kind | ||
pub kind: ErrorKind, | ||
/// The error message | ||
pub msg: &'static str, | ||
#[cfg(feature="std")] | ||
cause: Option<Box<stdError + Send + Sync>>, | ||
} | ||
|
@@ -110,14 +131,11 @@ impl Error { | |
Error { kind: kind, msg: msg } | ||
} | ||
|
||
/// Get the error kind | ||
pub fn kind(&self) -> ErrorKind { | ||
self.kind | ||
} | ||
|
||
/// Get the error message | ||
pub fn msg(&self) -> &'static str { | ||
self.msg | ||
/// Take the cause, if any. This allows the embedded cause to be extracted. | ||
/// This uses `Option::take`, leaving `self` with no cause. | ||
#[cfg(feature="std")] | ||
pub fn take_cause(&mut self) -> Option<Box<stdError + Send + Sync>> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just curious, when would you use this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I did consider just making |
||
self.cause.take() | ||
} | ||
} | ||
|
||
|
@@ -126,12 +144,10 @@ impl fmt::Display for Error { | |
#[cfg(feature="std")] { | ||
if let Some(ref cause) = self.cause { | ||
return write!(f, "{} ({}); cause: {}", | ||
self.msg(), | ||
self.kind.description(), | ||
cause); | ||
self.msg, self.kind.description(), cause); | ||
} | ||
} | ||
write!(f, "{} ({})", self.msg(), self.kind.description()) | ||
write!(f, "{} ({})", self.msg, self.kind.description()) | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,7 +62,13 @@ impl<R: Read> RngCore for ReadRng<R> { | |
if dest.len() == 0 { return Ok(()); } | ||
// Use `std::io::read_exact`, which retries on `ErrorKind::Interrupted`. | ||
self.reader.read_exact(dest).map_err(|err| { | ||
Error::with_cause(ErrorKind::Unavailable, "ReadRng: read error", err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it make sense to split this in two errors? To differentiate between unrecoverable errors and others? If I look at There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, but I would think only There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, your right. If we can't imagine the error to occur, best to map it to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see how a broken pipe is possible when reading directly from a file so I updated the PR BTW. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can read from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True. Okay, |
||
match err.kind() { | ||
::std::io::ErrorKind::WouldBlock => Error::with_cause( | ||
ErrorKind::NotReady, | ||
"reading from random device would block", err), | ||
_ => Error::with_cause(ErrorKind::Unavailable, | ||
"error reading random device", err) | ||
} | ||
}) | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -83,63 +83,43 @@ impl<R: RngCore + SeedableRng, Rsdr: RngCore> ReseedingRng<R, Rsdr> { | |
/// Reseed the internal PRNG. | ||
/// | ||
/// This will try to work around errors in the RNG used for reseeding | ||
/// intelligently. If the error kind indicates retrying might help, it will | ||
/// immediately retry a couple of times. If the error kind indicates the | ||
/// seeding RNG is not ready, it will retry later, after `threshold / 256` | ||
/// generated bytes. On other errors in the source RNG, this will skip | ||
/// reseeding and continue using the internal PRNG, until another | ||
/// `threshold` bytes have been generated (at which point it will try | ||
/// reseeding again). | ||
#[inline(never)] | ||
/// intelligently through some combination of retrying and delaying | ||
/// reseeding until later. So long as the internal PRNG doesn't fail, this | ||
/// method will not fail, i.e. failures from the reseeding source are not | ||
/// fatal. | ||
pub fn reseed(&mut self) { | ||
trace!("Reseeding RNG after generating {} bytes", | ||
self.threshold - self.bytes_until_reseed); | ||
self.bytes_until_reseed = self.threshold; | ||
let mut err_count = 0; | ||
loop { | ||
if let Err(e) = R::from_rng(&mut self.reseeder) | ||
.map(|result| self.rng = result) { | ||
let kind = e.kind(); | ||
if kind.should_wait() { | ||
self.bytes_until_reseed = self.threshold >> 8; | ||
warn!("Reseeding RNG delayed for {} bytes", | ||
self.bytes_until_reseed); | ||
} else if kind.should_retry() { | ||
err_count += 1; | ||
// Retry immediately for 5 times (arbitrary limit) | ||
if err_count <= 5 { continue; } | ||
} | ||
warn!("Reseeding RNG failed; continuing without reseeding. Error: {}", e); | ||
} | ||
break; // Successfully reseeded, delayed, or given up. | ||
} | ||
// Behaviour is identical to `try_reseed`; we just squelch the error. | ||
let _res = self.try_reseed(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice clean-up, and I think I can get behind the choice to unify the two reseeding methods. But I think doing the retries immediately is something useful to keep. At least if the error is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You think? I don't expect There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't it wait until There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, the code sets |
||
} | ||
|
||
/// Reseed the internal RNG if the number of bytes that have been | ||
/// generated exceed the threshold. | ||
/// | ||
/// If reseeding fails, return an error with the original cause. Note that | ||
/// if the cause has a permanent failure, we report a transient error and | ||
/// skip reseeding; this means that only two error kinds can be reported | ||
/// from this method: `ErrorKind::Transient` and `ErrorKind::NotReady`. | ||
/// in case of error we simply delay reseeding, allowing the generator to | ||
/// continue its output of random data and try reseeding again later; | ||
/// because of this we always return kind `ErrorKind::Transient`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, I think keeping |
||
#[inline(never)] | ||
pub fn try_reseed(&mut self) -> Result<(), Error> { | ||
trace!("Reseeding RNG after {} generated bytes", | ||
self.threshold - self.bytes_until_reseed); | ||
if let Err(err) = R::from_rng(&mut self.reseeder) | ||
.map(|result| self.rng = result) { | ||
let newkind = match err.kind() { | ||
a @ ErrorKind::NotReady => a, | ||
b @ ErrorKind::Transient => b, | ||
_ => { | ||
self.bytes_until_reseed = self.threshold; // skip reseeding | ||
ErrorKind::Transient | ||
} | ||
if let Err(mut e) = R::from_rng(&mut self.reseeder) | ||
.map(|result| self.rng = result) | ||
{ | ||
let delay = match e.kind { | ||
ErrorKind::Transient => 0, | ||
kind @ _ if kind.should_retry() => self.threshold >> 8, | ||
_ => self.threshold, | ||
}; | ||
return Err(Error::with_cause(newkind, "reseeding failed", err)); | ||
warn!("Reseeding RNG delayed reseeding by {} bytes due to \ | ||
error from source: {}", delay, e); | ||
self.bytes_until_reseed = delay; | ||
e.kind = ErrorKind::Transient; | ||
Err(e) | ||
} else { | ||
self.bytes_until_reseed = self.threshold; | ||
Ok(()) | ||
} | ||
self.bytes_until_reseed = self.threshold; | ||
Ok(()) | ||
} | ||
} | ||
|
||
|
@@ -171,12 +151,21 @@ impl<R: RngCore + SeedableRng, Rsdr: RngCore> RngCore for ReseedingRng<R, Rsdr> | |
} | ||
|
||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { | ||
self.rng.try_fill_bytes(dest)?; | ||
let res1 = self.rng.try_fill_bytes(dest); | ||
self.bytes_until_reseed -= dest.len() as i64; | ||
if self.bytes_until_reseed <= 0 { | ||
self.try_reseed()?; | ||
let res2 = if self.bytes_until_reseed <= 0 { | ||
self.try_reseed() | ||
} else { Ok(()) }; | ||
|
||
if let Err(e) = res1 { | ||
// In the unlikely event the internal PRNG fails, we don't know | ||
// whether this is resolvable; reseed immediately and return | ||
// original error kind. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a nicely found use of the reseeding wrapper. Does it impact benchmarks? Probably not because we benchmark with large slices to fill... I think this is unnecessary because just about every PRNG (everything that implements There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't benchmark this method at all currently, but yes, I might revert the changes to this method. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do you think about this now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Frankly I don't care much, especially since your "block rng" idea might significantly alter this. But eventually I guess we should investigate the performance of I don't care much about changes to this particular method; I can revert if you like but it's probably a small improvement? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think the performance is a big deal, but I am not sure adding this functionality is a good idea. Are there situations where reseeding the RNG when I can't think of any, actually I have a trouble imagining when a CSPRNG can fail... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have some trouble finishing that Block Rng experiment. It is a bit too big for me. Do you want to help / take over, probably after 0.5 is done? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, I'll leave this for now then. Can you create an issue regarding the BlockRng experiment? Sounds like it may not be in time for the 0.5 release but I'll try to take a look. |
||
self.bytes_until_reseed = 0; | ||
Err(e) | ||
} else { | ||
res2 | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Private fields feel neater, but this is just practical and I can't imagine any disadvantages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I specifically wanted to replace
kind
then return the error, and realised it was difficult with the old API. Further, I want to emphasise thatError
is a simple type which won't change much (at least the first two fields).