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}