async_listen/
error.rs

1#![deny(meta_variable_misuse)]
2
3use std::fmt;
4use std::io;
5
6/// Error hint that can be formatted
7///
8/// The structure implements `Display` and is usually used in logs like this:
9/// ```
10/// # use std::io;
11/// # let e: io::Error = io::ErrorKind::Other.into();
12/// use async_listen::error_hint;
13/// eprintln!("Error: {}. {}", e, error_hint(&e));
14/// ```
15///
16/// See [error description](../errors/index.html) for a list of the errors that
17/// can return a hint.
18///
19/// You can also apply a custom formatting (i.e. a different link) for the
20/// hint. Just replace an `error_hint` function with something of your own:
21///
22/// ```
23/// # use std::io;
24/// fn error_hint(e: &io::Error) -> String {
25///     let hint = async_listen::error_hint(e);
26///     if hint.is_empty() {
27///         return String::new();
28///     } else {
29///         return format!("{} http://example.org/my-server-errors#{}",
30///             hint.hint_text(), hint.link_hash());
31///     }
32/// }
33/// ```
34#[derive(Debug)]
35pub struct ErrorHint {
36    error: Option<KnownError>
37}
38
39#[derive(Debug)]
40enum KnownError {
41    Enfile,
42    Emfile,
43}
44
45/// Returns true if the error is transient
46///
47/// The transient error is defined here as an error after which we can continue
48/// accepting subsequent connections without the risk of a tight loop.
49///
50/// For example, a per-connection error like `ConnectionReset` in `accept()`
51/// system call means then next connection might be ready to be accepted
52/// immediately.
53///
54/// All other errors should incur a timeout before the next `accept()` is
55/// performed.  The timeout is useful to handle resource exhaustion errors
56/// like ENFILE and EMFILE: file descriptor might be released after some time
57/// but the error will be the same if we continue to accept in a tight loop.
58///
59/// This function is most likely should not be used directly, but rather
60/// through one of the following adapters:
61/// * [`log_warnings`](trait.ListenExt.html#method.log_warnings)
62/// * [`handle_errors`](trait.ListenExt.html#method.handle_errors)
63pub fn is_transient_error(e: &io::Error) -> bool {
64    e.kind() == io::ErrorKind::ConnectionRefused ||
65    e.kind() == io::ErrorKind::ConnectionAborted ||
66    e.kind() == io::ErrorKind::ConnectionReset
67}
68
69macro_rules! error_match {
70    ($value:expr => {
71        $(
72        ($n: pat | wasi: $wasi: pat | haiku: $haiku:pat) => $val: ident,
73        )*
74    }) => {
75        match $value {$(
76            #[cfg(any(target_env="wasi", target_os="wasi"))]
77            Some($wasi) => Some($val),
78            #[cfg(target_os="haiku")]
79            Some($haiku) => Some($val),
80            #[cfg(all(
81                any(unix, windows, target_os="fuchsia"),
82                not(any(target_env="wasi", target_os="wasi",target_os="haiku"))
83            ))]
84            Some($n) => Some($val),
85        )*
86            _ => None,
87        }
88    }
89}
90
91/// Returns a hint structure that can be formatter to the log output
92///
93/// # Example
94/// ```
95/// # use std::io;
96/// # let e: io::Error = io::ErrorKind::Other.into();
97/// use async_listen::error_hint;
98/// eprintln!("Error: {}. {}", e, error_hint(&e));
99/// ```
100///
101/// Error message might look like:
102/// ```text
103/// Error: Too many open files (os error 24). Increase per-process open file limit https://bit.ly/async-err#EMFILE
104/// ```
105///
106/// See [error description](errors/index.html) for a list of the errors that
107/// can return a hint.
108///
109/// See [`ErrorHint`] for more info on customizing the output
110///
111/// [`ErrorHint`]: wrapper_types/struct.ErrorHint.html
112pub fn error_hint(e: &io::Error) -> ErrorHint {
113    use KnownError::*;
114    let error = error_match!(e.raw_os_error() => {
115        (24 | wasi: 33 | haiku: -2147459062) => Emfile,
116        (23 | wasi: 41 | haiku: -2147454970) => Enfile,
117    });
118    return ErrorHint { error }
119}
120
121
122impl ErrorHint {
123    /// Text of the hint
124    ///
125    /// Since the text is expected to be printed **after** the error message,
126    /// it usually includes call to action, like:
127    /// ```text
128    /// Increase per-process open file limit
129    /// ```
130    ///
131    /// Usually the hint is good enough to use search engine to find the
132    /// solution to the problem. But usually link is printed too.
133    pub fn hint_text(&self) -> &'static str {
134        use KnownError::*;
135        match &self.error {
136            None => "",
137            Some(Emfile) => "Increase per-process open file limit",
138            Some(Enfile) => "Increase system open file limit",
139        }
140    }
141
142    /// The part of the link after the hash `#` sign
143    ///
144    /// To make a link prepend with the base URL:
145    /// ```
146    /// # let h = async_listen::error_hint(&std::io::ErrorKind::Other.into());
147    /// println!("{}#{}", h.default_link_base(), h.link_hash())
148    /// ```
149    ///
150    /// It's expected that implementation may customize base link. Mathing
151    /// for the link hash is also well supported, for exaple if you want to
152    /// change the link only for one of few errors.
153    ///
154    /// Link hashes are stable (we don't change them in future versions).
155    pub fn link_hash(&self) -> &'static str {
156        use KnownError::*;
157        match &self.error {
158            None => "",
159            Some(Emfile) => "EMFILE",
160            Some(Enfile) => "ENFILE",
161        }
162    }
163
164    /// Returns current base link printed with the hint
165    ///
166    /// Current value is `https://bit.ly/async-err`. In future versions we
167    /// might change the base if we find a better place to host the docs, but
168    /// we don't take this decision lightly.
169    ///
170    /// To make a link, just append a hash part:
171    /// ```no_run
172    /// # let h = async_listen::error_hint(&std::io::ErrorKind::Other.into());
173    /// println!("{}#{}", h.default_link_base(), h.link_hash())
174    /// ```
175    pub fn default_link_base(&self) -> &'static str {
176        return "https://bit.ly/async-err";
177    }
178
179    /// Returns true if the hint is empty
180    ///
181    /// Even if there is no hint for the error (error code is unknown)
182    /// the `error_hint` function returns the `ErrorHint` object which is
183    /// empty when displayed. This is a convenience in most cases, but you
184    /// have to check for `is_empty` when formatting your own hint.
185    pub fn is_empty(&self) -> bool {
186        self.error.is_some()
187    }
188}
189
190impl fmt::Display for ErrorHint {
191    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
192        if self.error.is_none() {
193            return Ok(())
194        }
195        write!(f, "{} {}#{}",
196            self.hint_text(), self.default_link_base(), self.link_hash())
197    }
198}