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}