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
#![deny(meta_variable_misuse)] use std::fmt; use std::io; /// Error hint that can be formatted /// /// The structure implements `Display` and is usually used in logs like this: /// ``` /// # use std::io; /// # let e: io::Error = io::ErrorKind::Other.into(); /// use async_listen::error_hint; /// eprintln!("Error: {}. {}", e, error_hint(&e)); /// ``` /// /// See [error description](../errors/index.html) for a list of the errors that /// can return a hint. /// /// You can also apply a custom formatting (i.e. a different link) for the /// hint. Just replace an `error_hint` function with something of your own: /// /// ``` /// # use std::io; /// fn error_hint(e: &io::Error) -> String { /// let hint = async_listen::error_hint(e); /// if hint.is_empty() { /// return String::new(); /// } else { /// return format!("{} http://example.org/my-server-errors#{}", /// hint.hint_text(), hint.link_hash()); /// } /// } /// ``` #[derive(Debug)] pub struct ErrorHint { error: Option<KnownError> } #[derive(Debug)] enum KnownError { Enfile, Emfile, } /// Returns true if the error is transient /// /// The transient error is defined here as an error after which we can continue /// accepting subsequent connections without the risk of a tight loop. /// /// For example, a per-connection error like `ConnectionReset` in `accept()` /// system call means then next connection might be ready to be accepted /// immediately. /// /// All other errors should incur a timeout before the next `accept()` is /// performed. The timeout is useful to handle resource exhaustion errors /// like ENFILE and EMFILE: file descriptor might be released after some time /// but the error will be the same if we continue to accept in a tight loop. /// /// This function is most likely should not be used directly, but rather /// through one of the following adapters: /// * [`log_warnings`](trait.ListenExt.html#method.log_warnings) /// * [`handle_errors`](trait.ListenExt.html#method.handle_errors) pub fn is_transient_error(e: &io::Error) -> bool { e.kind() == io::ErrorKind::ConnectionRefused || e.kind() == io::ErrorKind::ConnectionAborted || e.kind() == io::ErrorKind::ConnectionReset } macro_rules! error_match { ($value:expr => { $( ($n: pat | wasi: $wasi: pat | haiku: $haiku:pat) => $val: ident, )* }) => { match $value {$( #[cfg(any(target_env="wasi", target_os="wasi"))] Some($wasi) => Some($val), #[cfg(target_os="haiku")] Some($haiku) => Some($val), #[cfg(all( any(unix, windows, target_os="fuchsia"), not(any(target_env="wasi", target_os="wasi",target_os="haiku")) ))] Some($n) => Some($val), )* _ => None, } } } /// Returns a hint structure that can be formatter to the log output /// /// # Example /// ``` /// # use std::io; /// # let e: io::Error = io::ErrorKind::Other.into(); /// use async_listen::error_hint; /// eprintln!("Error: {}. {}", e, error_hint(&e)); /// ``` /// /// Error message might look like: /// ```text /// Error: Too many open files (os error 24). Increase per-process open file limit https://bit.ly/async-err#EMFILE /// ``` /// /// See [error description](errors/index.html) for a list of the errors that /// can return a hint. /// /// See [`ErrorHint`] for more info on customizing the output /// /// [`ErrorHint`]: wrapper_types/struct.ErrorHint.html pub fn error_hint(e: &io::Error) -> ErrorHint { use KnownError::*; let error = error_match!(e.raw_os_error() => { (24 | wasi: 33 | haiku: -2147459062) => Emfile, (23 | wasi: 41 | haiku: -2147454970) => Enfile, }); return ErrorHint { error } } impl ErrorHint { /// Text of the hint /// /// Since the text is expected to be printed **after** the error message, /// it usually includes call to action, like: /// ```text /// Increase per-process open file limit /// ``` /// /// Usually the hint is good enough to use search engine to find the /// solution to the problem. But usually link is printed too. pub fn hint_text(&self) -> &'static str { use KnownError::*; match &self.error { None => "", Some(Emfile) => "Increase per-process open file limit", Some(Enfile) => "Increase system open file limit", } } /// The part of the link after the hash `#` sign /// /// To make a link prepend with the base URL: /// ``` /// # let h = async_listen::error_hint(&std::io::ErrorKind::Other.into()); /// println!("{}#{}", h.default_link_base(), h.link_hash()) /// ``` /// /// It's expected that implementation may customize base link. Mathing /// for the link hash is also well supported, for exaple if you want to /// change the link only for one of few errors. /// /// Link hashes are stable (we don't change them in future versions). pub fn link_hash(&self) -> &'static str { use KnownError::*; match &self.error { None => "", Some(Emfile) => "EMFILE", Some(Enfile) => "ENFILE", } } /// Returns current base link printed with the hint /// /// Current value is `https://bit.ly/async-err`. In future versions we /// might change the base if we find a better place to host the docs, but /// we don't take this decision lightly. /// /// To make a link, just append a hash part: /// ```no_run /// # let h = async_listen::error_hint(&std::io::ErrorKind::Other.into()); /// println!("{}#{}", h.default_link_base(), h.link_hash()) /// ``` pub fn default_link_base(&self) -> &'static str { return "https://bit.ly/async-err"; } /// Returns true if the hint is empty /// /// Even if there is no hint for the error (error code is unknown) /// the `error_hint` function returns the `ErrorHint` object which is /// empty when displayed. This is a convenience in most cases, but you /// have to check for `is_empty` when formatting your own hint. pub fn is_empty(&self) -> bool { self.error.is_some() } } impl fmt::Display for ErrorHint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.error.is_none() { return Ok(()) } write!(f, "{} {}#{}", self.hint_text(), self.default_link_base(), self.link_hash()) } }