Skip to main content

compio_pool/
manage.rs

1//! The manager trait: how the pool creates, checks and moves connections.
2
3use std::future::Future;
4
5use crate::slot::SlotMeta;
6
7/// Creates and validates connections for a [`Pool`](crate::Pool).
8///
9/// This is the trait you implement for your protocol (Postgres, Redis, an HTTP
10/// client, …).
11///
12/// # Why there are no `Send` bounds on the futures
13///
14/// `compio` is a completion-based, thread-per-core runtime: its IO handles are
15/// bound to the driver of the thread that created them and are `!Send`. A pool
16/// built on `bb8`'s trait shape cannot hold a `compio::net::TcpStream` at all,
17/// because `bb8` requires `Connection: Send`.
18///
19/// So `Manage` requires `Send + Sync` on the **manager** (it is shared across
20/// threads inside the pool handle) but places no bounds whatsoever on
21/// [`Connection`](Manage::Connection) or on the returned futures. Connections
22/// live in thread-local shards and never cross a thread boundary unless you
23/// additionally implement [`Detach`].
24pub trait Manage: Send + Sync + 'static {
25    /// The pooled resource. May be `!Send`; must be owned (`'static`),
26    /// since shards are stored in thread-local slots.
27    type Connection: 'static;
28
29    /// Error produced when establishing or validating a connection.
30    type Error;
31
32    /// Establishes a new connection.
33    ///
34    /// Called on the thread that will own the connection, so it is safe (and
35    /// expected) to touch the current `compio` runtime here.
36    fn connect(&self) -> impl Future<Output = Result<Self::Connection, Self::Error>>;
37
38    /// Prepares a previously-used connection for hand-off.
39    ///
40    /// Called on acquire, not on return, because returning happens in `Drop`,
41    /// which cannot await. Use it for a cheap liveness check and to reset
42    /// protocol state (roll back an open transaction, drain a pipeline).
43    ///
44    /// Returning `Err` discards the connection; the pool then transparently
45    /// tries the next idle one or opens a fresh connection.
46    fn recycle(
47        &self,
48        conn: &mut Self::Connection,
49        meta: &SlotMeta,
50    ) -> impl Future<Output = Result<(), Self::Error>>;
51
52    /// Called before a connection is dropped, for graceful shutdown.
53    ///
54    /// The default is a no-op: dropping the connection closes it. Override this
55    /// only for protocols with an explicit goodbye message (Redis `QUIT`,
56    /// Postgres `Terminate`). It is synchronous by design — it runs from `Drop`
57    /// paths and during thread teardown, where awaiting is not possible.
58    fn disconnect(&self, conn: Self::Connection) {
59        let _ = conn;
60    }
61}
62
63/// Opt-in marker for connections that can legally change threads.
64///
65/// Implementing this lets a connection be parked in the cross-thread
66/// [`Reservoir`](crate::Reservoir), so an idle connection on one compio thread
67/// can be picked up by a busy one instead of sitting unused.
68///
69/// # Only idle connections, never one with an operation in flight
70///
71/// Moving a handle between drivers is sound only when nothing is still
72/// submitted against it. The pool upholds half of that for you: a connection
73/// reaches [`detach`](Detach::detach) only after its [`Pooled`](crate::Pooled)
74/// guard has been dropped, and a checkout cancelled mid-operation is poisoned
75/// and destroyed rather than parked. [`detach`](Detach::detach) returning
76/// `Option` is the other half — the implementation gets to *check*, rather
77/// than trust, that the handle is genuinely unshared.
78///
79/// # Platform reality
80///
81/// Whether this is sound depends on the driver, and the answer differs by OS:
82///
83/// * **io_uring and poll (Unix)** — `Driver::attach` is a no-op. The fd table
84///   is process-wide and any thread may submit ops on any fd, so moving a
85///   connection between compio threads costs nothing. `Detach` is safe here.
86/// * **IOCP (Windows)** — `attach` calls `CreateIoCompletionPort`, and compio
87///   documents that "a handle can and only can attach once to one driver".
88///   A socket bound to thread A's port keeps delivering completions to thread
89///   A's port forever. **Do not implement `Detach` on Windows** for types
90///   backed by an IOCP handle.
91///
92/// Beyond the fd itself, everything you carry in [`Parked`](Detach::Parked)
93/// must be `Send`: buffers, TLS session state, prepared-statement caches. A
94/// connection whose state includes an `Rc` cannot implement this trait, which
95/// is precisely the point of separating it from [`Manage`].
96pub trait Detach: Manage {
97    /// The connection reduced to a form that may cross threads.
98    ///
99    /// Typically an `OwnedFd`/`SharedFd` plus whatever protocol state is `Send`.
100    type Parked: Send + 'static;
101
102    /// Converts a connection into its thread-portable form.
103    ///
104    /// Returns `None` when the connection cannot be made portable — in
105    /// practice, when an IO operation still holds a reference to the
106    /// underlying handle. **The connection is consumed either way**: `None`
107    /// means it has been dropped, and the pool accounts for it as closed
108    /// rather than parked, then dials a replacement.
109    ///
110    /// With `compio`, the check is
111    /// [`SharedFd::try_unwrap`](compio::driver::SharedFd::try_unwrap), which
112    /// succeeds exactly when no submission still references the fd. See
113    /// `examples/steal.rs`.
114    fn detach(conn: Self::Connection) -> Option<Self::Parked>;
115
116    /// Rebuilds a connection on the current thread.
117    ///
118    /// This is where the socket is re-wrapped in *this* thread's runtime —
119    /// `TcpStream::from_std`, or a `from_raw_fd` constructor, depending on the
120    /// compio version. Under io_uring and poll that is bookkeeping only; the
121    /// fd table is process-wide.
122    ///
123    /// Returning `Err` drops the parked connection; the caller falls back to
124    /// opening a fresh one.
125    fn attach(parked: Self::Parked) -> impl Future<Output = Result<Self::Connection, Self::Error>>;
126}