Skip to main content

dope_session/pool/client/
mod.rs

1use std::collections::VecDeque;
2use std::marker::PhantomData;
3use std::sync::Arc;
4use std::sync::atomic::AtomicBool;
5use std::time::Instant;
6
7use dope_core::driver::{Backend, DriverConfig, PoolDriver};
8use dope_runtime::runtime::Runtime;
9
10use crate::CodecLayer;
11use crate::protocol::client::ClientProtocol;
12use dope_core::profile::Profile;
13use dope_transport::{Tcp, Transport};
14
15pub mod connect;
16pub mod connect_source;
17pub mod recv;
18pub mod state;
19pub mod write;
20
21use self::connect_source::{ConnectAction, ConnectSource};
22use self::state::{CLIENT_IOV_CAP, ClientSlotState};
23use super::Direction;
24use super::Pool;
25use super::core::PoolCore;
26use super::slot::{ConnFlags, INGRESS_BUF_CAP};
27use crate::SlotId;
28
29pub type ClientSession<P> =
30    crate::session::ClientSessionGeneric<P, INGRESS_BUF_CAP, CLIENT_IOV_CAP>;
31
32pub struct Client<P: ClientProtocol, T: Transport, S: ConnectSource<T>> {
33    pub(crate) protocol: P,
34    pub(crate) connect_source: S,
35    pub(crate) socket_opts: dope_transport::config::StreamSocketConfig,
36    pub(crate) connecting_slots: VecDeque<u16>,
37    transport: PhantomData<T>,
38}
39
40impl<P: ClientProtocol, T: Transport, S: ConnectSource<T>> Client<P, T, S> {
41    pub fn new(protocol: P, connect_source: S) -> Self {
42        Self {
43            protocol,
44            connect_source,
45            socket_opts: dope_transport::config::StreamSocketConfig::default(),
46            connecting_slots: VecDeque::with_capacity(16),
47            transport: PhantomData,
48        }
49    }
50
51    #[inline(always)]
52    pub fn protocol(&self) -> &P {
53        &self.protocol
54    }
55
56    #[inline(always)]
57    pub fn protocol_mut(&mut self) -> &mut P {
58        &mut self.protocol
59    }
60
61    #[inline(always)]
62    pub fn connect_source(&self) -> &S {
63        &self.connect_source
64    }
65
66    #[inline(always)]
67    pub fn connect_source_mut(&mut self) -> &mut S {
68        &mut self.connect_source
69    }
70
71    #[inline(always)]
72    pub(crate) fn has_pending(&self) -> bool {
73        !self.connecting_slots.is_empty()
74    }
75}
76
77impl<P, T, S, B> Direction<B> for Client<P, T, S>
78where
79    P: ClientProtocol,
80    T: Transport,
81    T::Addr: Clone,
82    S: ConnectSource<T>,
83    B: PoolDriver,
84{
85    type Transport = T;
86    type SlotUser = ClientSlotState<P, B>;
87    type CodecLayer = P::CodecLayer;
88    type WriteBuf = <P::CodecLayer as crate::CodecLayer>::Staging;
89
90    #[inline(always)]
91    fn has_pending(&self) -> bool {
92        Self::has_pending(self)
93    }
94
95    #[inline]
96    fn tick(
97        &mut self,
98        core: &mut PoolCore<Self::Transport, Self::CodecLayer, Self::WriteBuf, Self::SlotUser, B>,
99        driver: &mut B::Driver,
100    ) {
101        let now = Instant::now();
102        let max_new_connects = core.free_conns.len();
103        for _ in 0..max_new_connects {
104            match self.connect_source.poll_connect(now) {
105                ConnectAction::Connect { addr, tag } => {
106                    if !self.spawn_connect::<B>(core, driver, addr, tag) {
107                        self.connect_source.on_connect_outcome(tag, false, now);
108                        break;
109                    }
110                }
111                ConnectAction::Backoff { .. } | ConnectAction::Idle => break,
112            }
113        }
114        core.flush_recv_rearm(driver);
115    }
116}
117
118impl<P, T, S, F, B> Pool<Client<P, T, S>, F, B>
119where
120    P: ClientProtocol,
121    T: Transport,
122    T::Addr: Clone,
123    S: ConnectSource<T>,
124    F: Profile,
125    B: PoolDriver,
126{
127    pub fn new(
128        direction: Client<P, T, S>,
129        max_conn: usize,
130        slot_id_base: usize,
131    ) -> std::io::Result<Self> {
132        assert!(max_conn > 0 && max_conn <= u16::MAX as usize);
133        let core = PoolCore::<
134            T,
135            P::CodecLayer,
136            <P::CodecLayer as crate::CodecLayer>::Staging,
137            ClientSlotState<P, B>,
138            B,
139        >::new(max_conn, slot_id_base)?;
140        Ok(Self {
141            direction,
142            core,
143            profile: PhantomData,
144        })
145    }
146
147    pub fn slot_id_range(&self) -> std::ops::Range<usize> {
148        let base = self.core.slot_id_base;
149        base..(base + self.core.slots.len())
150    }
151
152    pub fn conn_slot_count(&self) -> usize {
153        self.core.slots.len()
154    }
155
156    pub fn direction_mut(&mut self) -> &mut Client<P, T, S> {
157        &mut self.direction
158    }
159
160    pub fn live_conns(&self) -> impl Iterator<Item = SlotId> + '_ {
161        self.core
162            .slots
163            .iter()
164            .enumerate()
165            .filter(|(_, slot)| slot.conn.flags.contains(ConnFlags::LIVE))
166            .map(|(i, slot)| SlotId::from_parts(i as u32, slot.conn.generation))
167    }
168
169    pub fn is_live(&self, conn_id: SlotId) -> bool {
170        self.core.resolve_live_slot(conn_id).is_ok()
171    }
172
173    pub fn session_mut(&mut self, conn_id: SlotId) -> Option<&mut ClientSession<P>> {
174        let slot_idx = self.core.resolve_live_slot(conn_id).ok()?;
175        Some(&mut self.core.slots[slot_idx].user.get_mut().session)
176    }
177
178    /// Out-of-callback access to both the session and the paired codec state.
179    ///
180    /// Note: mutations made through this accessor do **not** auto-poke the
181    /// outbound path. After mutating codec state in a way that should produce
182    /// wire bytes (e.g. triggering a TLS handshake), call
183    /// [`Pool::poke_outbound`] to wake the slot.
184    pub fn session_with_codec_mut(
185        &mut self,
186        conn_id: SlotId,
187    ) -> Option<(
188        &mut ClientSession<P>,
189        &mut <P::CodecLayer as CodecLayer>::ConnState,
190    )> {
191        let slot_idx = self.core.resolve_live_slot(conn_id).ok()?;
192        let entry = &mut self.core.slots[slot_idx];
193        let codec_state = entry.codec_state.get_mut();
194        let session = &mut entry.user.get_mut().session;
195        Some((session, codec_state))
196    }
197}
198
199#[derive(Clone, Debug, Default)]
200pub struct TcpClientConfig {
201    pub max_conn: usize,
202    pub socket_opts: dope_transport::config::StreamSocketConfig,
203    pub shutdown: Option<Arc<AtomicBool>>,
204    pub cpu_id: Option<u16>,
205}
206
207impl<P, S, F, B> Pool<Client<P, Tcp, S>, F, B>
208where
209    P: ClientProtocol,
210    S: ConnectSource<Tcp>,
211    F: Profile,
212    B: PoolDriver,
213{
214    pub fn build(
215        protocol: P,
216        connect_source: S,
217        cfg: TcpClientConfig,
218    ) -> std::io::Result<Runtime<Self>> {
219        let shutdown = cfg.shutdown.clone();
220        let max_conn = cfg.max_conn;
221        let cpu_id = cfg.cpu_id;
222        assert!(max_conn > 0 && max_conn <= u16::MAX as usize);
223
224        let driver_cfg = <<B as Backend>::Config as DriverConfig>::for_tcp_profile(
225            F::RING_ENTRIES,
226            max_conn,
227            crate::pool::slot::INGRESS_BUF_CAP,
228        )
229        .with_cpu_id(cpu_id);
230        let mut driver = <B as Backend>::new_driver(driver_cfg)?;
231        let pool = Self::build_raw(protocol, connect_source, cfg, &mut driver, 0)?;
232        Ok(Runtime::new(pool, driver).with_shutdown(shutdown))
233    }
234
235    pub fn build_raw(
236        protocol: P,
237        connect_source: S,
238        cfg: TcpClientConfig,
239        _driver: &mut B::Driver,
240        slot_id_base: usize,
241    ) -> std::io::Result<Self> {
242        let TcpClientConfig {
243            max_conn,
244            socket_opts,
245            shutdown: _,
246            cpu_id: _,
247        } = cfg;
248        assert!(max_conn > 0 && max_conn <= u16::MAX as usize);
249        let mut direction = Client::new(protocol, connect_source);
250        direction.socket_opts = socket_opts;
251        Self::new(direction, max_conn, slot_id_base)
252    }
253}
254
255impl<P, T, S, F, B> super::PoolTick<B> for Pool<Client<P, T, S>, F, B>
256where
257    P: ClientProtocol,
258    T: Transport,
259    T::Addr: Clone,
260    S: ConnectSource<T>,
261    F: Profile,
262    B: PoolDriver,
263{
264    #[inline(always)]
265    fn tick(&mut self, driver: &mut B::Driver) {
266        self.direction.tick(&mut self.core, driver);
267        self.poll_connect_requests(driver);
268        let _ = self.pump_outbound(driver);
269    }
270}