Skip to main content

toasty_core/error/
connection_lost.rs

1use super::Error;
2
3/// Error indicating the underlying database connection is broken.
4///
5/// Drivers return this when the backend reports the socket is closed,
6/// the session was killed, or another fatal connection-level fault has
7/// occurred. The pool evicts the connection before the error reaches
8/// the caller, so retrying on the same `Db` will pick up a fresh one.
9///
10/// Toasty does not retry the operation automatically: a write that
11/// failed mid-flight may or may not have reached the server, and only
12/// the caller knows whether the operation is safe to retry.
13#[derive(Debug)]
14pub(super) struct ConnectionLost {
15    pub(super) inner: Box<dyn std::error::Error + Send + Sync>,
16}
17
18impl std::error::Error for ConnectionLost {
19    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
20        Some(self.inner.as_ref())
21    }
22}
23
24impl core::fmt::Display for ConnectionLost {
25    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
26        write!(f, "connection lost: ")?;
27        core::fmt::Display::fmt(&self.inner, f)?;
28        let mut source = self.inner.source();
29        while let Some(err) = source {
30            write!(f, ": {}", err)?;
31            source = err.source();
32        }
33        Ok(())
34    }
35}
36
37impl Error {
38    /// Creates a connection-lost error from an underlying driver error.
39    ///
40    /// Drivers MUST map their backend's "connection is gone" errors (a
41    /// closed `tokio_postgres` socket, `mysql_async::Error::Io`, an
42    /// end-of-stream during the wire protocol, etc.) to this
43    /// constructor. The pool's recycle path branches on this variant:
44    ///
45    /// - The slot is evicted from the pool instead of being returned to
46    ///   the idle set.
47    /// - The pool's background sweep is woken to eagerly ping every
48    ///   other idle connection so a backend restart costs at most one
49    ///   failed query, not one per pooled connection.
50    /// - User code can branch on
51    ///   [`is_connection_lost`](Error::is_connection_lost) to decide
52    ///   whether to retry (the operation may or may not have reached
53    ///   the server — only the caller knows whether it is safe to
54    ///   retry).
55    ///
56    /// Returning a `driver_operation_failed` for a connection-level
57    /// fault instead of this variant leaks a dead slot back into the
58    /// pool.
59    ///
60    /// # Examples
61    ///
62    /// ```
63    /// use toasty_core::Error;
64    ///
65    /// let io_err = std::io::Error::new(
66    ///     std::io::ErrorKind::ConnectionReset,
67    ///     "broken pipe",
68    /// );
69    /// let err = Error::connection_lost(io_err);
70    /// assert!(err.is_connection_lost());
71    /// ```
72    pub fn connection_lost(err: impl std::error::Error + Send + Sync + 'static) -> Error {
73        Error::from(super::ErrorKind::ConnectionLost(ConnectionLost {
74            inner: Box::new(err),
75        }))
76    }
77
78    /// Returns `true` if this error indicates the connection was lost.
79    ///
80    /// The pool has already evicted the underlying connection by the
81    /// time this error reaches the caller. Operations may be safe to
82    /// retry on the same `Db`, but only the caller knows whether the
83    /// operation itself is idempotent.
84    pub fn is_connection_lost(&self) -> bool {
85        matches!(self.kind(), super::ErrorKind::ConnectionLost(_))
86    }
87}