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}