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())
    }
}