Skip to main content

dope_core/driver/
mod.rs

1pub mod bootstrap;
2pub mod completion;
3pub mod control;
4pub mod datagram;
5pub mod ext;
6pub mod profile;
7pub mod ready;
8pub mod recv;
9pub mod route;
10pub mod submission;
11pub mod timer;
12pub mod token;
13
14use std::cell::Cell;
15use std::error::Error;
16use std::fmt::{self, Display, Formatter};
17use std::io::{self, ErrorKind, Result};
18use std::marker::{PhantomData, PhantomPinned};
19use std::pin::{Pin, pin};
20use std::ptr::from_ref;
21use std::time::Instant;
22
23use o3::cell::{BrandToken, RegionToken};
24use o3::collections::CellQueue;
25use o3::marker::ThreadBound;
26use profile::DriverProfile;
27use ready::{Arena, ReadyHandle, ReadyKey, ReadySlot};
28use token::{SlotIndex, Token, TokenCapacity};
29
30use crate::backend::ops::buffers::BufferBackend;
31use crate::backend::{Backend, RecvBuffer};
32use crate::io::fd::{Fd, FdGuard, FdSlot};
33use crate::platform::Platform;
34use crate::platform::raw::file::{FileLimit, lock_memory_best_effort};
35
36type Invariant<'d> = PhantomData<fn(&'d ()) -> &'d ()>;
37
38struct Shared {
39    arena: Box<Arena>,
40    returned_buffers: CellQueue<RecvBuffer>,
41    turn_clock: Cell<Instant>,
42}
43
44pub struct Driver {
45    shared: Shared,
46    backend: Backend,
47    timer_slots: usize,
48    _pin: PhantomPinned,
49}
50
51pub struct DriverRef<'d> {
52    shared: &'d Shared,
53    _brand: Invariant<'d>,
54}
55
56pub struct Scope<'d> {
57    driver: DriverRef<'d>,
58    backend: &'d mut Backend,
59    region: RegionToken<'d>,
60    token: BrandToken<'d>,
61    timer: &'d timer::Timer<'d>,
62    _timer_owner: Box<timer::Timer<'d>>,
63}
64
65pub struct DriverContext<'a, 'd> {
66    driver: DriverRef<'d>,
67    backend: &'a mut Backend,
68    region: &'a mut RegionToken<'d>,
69    timer: &'d timer::Timer<'d>,
70}
71
72pub trait StorageFactory: 'static {
73    type Output<'d>: 'd;
74
75    fn build<'d>(self, driver: &mut DriverContext<'_, 'd>) -> Self::Output<'d>;
76}
77
78impl StorageFactory for () {
79    type Output<'d> = ();
80
81    fn build<'d>(self, _driver: &mut DriverContext<'_, 'd>) -> Self::Output<'d> {}
82}
83
84impl<A: StorageFactory, B: StorageFactory> StorageFactory for (A, B) {
85    type Output<'d> = (A::Output<'d>, B::Output<'d>);
86
87    fn build<'d>(self, driver: &mut DriverContext<'_, 'd>) -> Self::Output<'d> {
88        let first = self.0.build(&mut driver.reborrow());
89        let second = self.1.build(driver);
90        (first, second)
91    }
92}
93
94impl Driver {
95    pub fn init_process() -> Result<()> {
96        lock_memory_best_effort();
97        FileLimit::get()?.raise()
98    }
99
100    pub(crate) fn from_state(
101        state: Backend,
102        fixed_slots: usize,
103        dynamic_slots: usize,
104        timer_slots: usize,
105        recv_buffers: usize,
106    ) -> Result<Self> {
107        Ok(Self {
108            shared: Shared {
109                arena: Arena::new(fixed_slots, dynamic_slots)?,
110                returned_buffers: CellQueue::with_capacity(recv_buffers),
111                turn_clock: Cell::new(Instant::now()),
112            },
113            backend: state,
114            timer_slots,
115            _pin: PhantomPinned,
116        })
117    }
118
119    pub fn scope<R>(self: Pin<&mut Self>, f: impl for<'d> FnOnce(Scope<'d>) -> R) -> R {
120        // SAFETY: the pointer is used only within the synchronous branded scope,
121        // while the pinned exclusive borrow remains active.
122        let this = unsafe { self.get_unchecked_mut() as *mut Self };
123        BrandToken::scope_with_region(move |token, region| {
124            // SAFETY: the higher-ranked closure prevents the generated lifetime
125            // and every reference derived from this pointer from escaping.
126            let this = unsafe { &mut *this };
127            let timer_owner = Box::new(timer::Timer::with_capacity(
128                this.timer_slots,
129                &region,
130                DriverRef::new(&this.shared),
131            ));
132            let timer = &raw const *timer_owner;
133            // SAFETY: `timer_owner` stays in Scope for the whole generative
134            // driver scope, and the higher-ranked closure prevents escape.
135            let timer = unsafe { &*timer };
136            f(Scope {
137                driver: DriverRef::new(&this.shared),
138                backend: &mut this.backend,
139                region,
140                token,
141                timer,
142                _timer_owner: timer_owner,
143            })
144        })
145    }
146
147    #[doc(hidden)]
148    pub fn scope_with_storage<S, R>(
149        self: Pin<&mut Self>,
150        factory: S,
151        f: impl for<'scope, 'd> FnOnce(Scope<'d>, Pin<&'d S::Output<'d>>) -> R,
152    ) -> R
153    where
154        S: StorageFactory,
155    {
156        self.scope(move |mut scope| {
157            let value = factory.build(&mut scope.context());
158            let storage = pin!(value);
159            let storage = from_ref(storage.as_ref().get_ref());
160            // SAFETY: `storage` stays pinned until `f` returns. `f` is
161            // higher-ranked, so the generated lifetime cannot escape.
162            let storage = unsafe { Pin::new_unchecked(&*storage) };
163            f(scope, storage)
164        })
165    }
166}
167
168impl<'d> DriverRef<'d> {
169    fn new(shared: &'d Shared) -> Self {
170        Self {
171            shared,
172            _brand: PhantomData,
173        }
174    }
175
176    fn arena(self) -> &'d Arena {
177        &self.shared.arena
178    }
179
180    pub(crate) fn fixed_ready(self, slot: FdSlot) -> ReadyHandle<'d> {
181        self.arena().fixed_slot(slot)
182    }
183
184    pub(crate) fn fixed_fd_slot(self, raw: u32) -> Option<FdSlot> {
185        self.arena().fd_slot(raw)
186    }
187
188    pub fn make_ready_slot(self, target: Token) -> Result<ReadySlot<'d>> {
189        self.arena().make_slot(target)
190    }
191
192    pub fn make_ready_slot_reserving(self, target: Token, reserve: usize) -> Result<ReadySlot<'d>> {
193        self.arena().make_slot_reserving(target, reserve)
194    }
195
196    pub fn make_ready_slots<I>(self, targets: I) -> Result<Box<[ReadySlot<'d>]>>
197    where
198        I: IntoIterator<Item = Token>,
199        I::IntoIter: ExactSizeIterator,
200    {
201        self.arena().make_slots(targets)
202    }
203
204    pub fn activate_ready(self, key: ReadyKey<'d>) {
205        self.arena().activate(key);
206    }
207
208    #[doc(hidden)]
209    pub fn arm_recv_credit(self, key: ReadyKey<'d>, target: Token) -> bool {
210        self.arena().arm_recv_credit(key, target)
211    }
212
213    #[doc(hidden)]
214    pub fn release_recv_credit(self, key: ReadyKey<'d>, target: Token) {
215        self.arena().release_recv_credit(key, target);
216    }
217
218    #[doc(hidden)]
219    pub fn take_recv_credit(self, key: ReadyKey<'d>, target: Token) -> bool {
220        self.arena().take_recv_credit(key, target)
221    }
222
223    pub fn drain_ready(self, activate: impl FnMut(Token)) {
224        self.arena().drain(activate);
225    }
226
227    pub fn has_ready(self) -> bool {
228        self.arena().has_ready()
229    }
230
231    /// Returns the monotonic-clock snapshot for the current driver turn.
232    ///
233    /// Unlike [`Instant::now`], this is a cached read. The runtime refreshes it
234    /// at completion-batch entry and immediately before preparing to park, so
235    /// callbacks in one turn share a coherent time base without performing a
236    /// clock read for every event.
237    pub fn turn_now(self) -> Instant {
238        self.shared.turn_clock.get()
239    }
240
241    pub(crate) fn return_buffer(self, buffer: RecvBuffer) {
242        assert!(
243            self.shared.returned_buffers.push_back(buffer).is_ok(),
244            "dope: provided-buffer return queue overflow"
245        );
246    }
247}
248
249impl Copy for DriverRef<'_> {}
250
251impl Clone for DriverRef<'_> {
252    fn clone(&self) -> Self {
253        *self
254    }
255}
256
257impl PartialEq for DriverRef<'_> {
258    fn eq(&self, other: &Self) -> bool {
259        core::ptr::eq(self.shared, other.shared)
260    }
261}
262
263impl Eq for DriverRef<'_> {}
264
265impl<'d> Scope<'d> {
266    pub fn context(&mut self) -> DriverContext<'_, 'd> {
267        DriverContext {
268            driver: self.driver,
269            backend: &mut *self.backend,
270            region: &mut self.region,
271            timer: self.timer,
272        }
273    }
274
275    pub fn token(&mut self) -> &mut BrandToken<'d> {
276        &mut self.token
277    }
278
279    pub fn token_and_context(&mut self) -> (&mut BrandToken<'d>, DriverContext<'_, 'd>) {
280        (
281            &mut self.token,
282            DriverContext {
283                driver: self.driver,
284                backend: &mut *self.backend,
285                region: &mut self.region,
286                timer: self.timer,
287            },
288        )
289    }
290
291    pub fn driver_ref(&self) -> DriverRef<'d> {
292        self.driver
293    }
294}
295
296impl<'a, 'd> DriverContext<'a, 'd> {
297    pub fn reborrow(&mut self) -> DriverContext<'_, 'd> {
298        DriverContext {
299            driver: self.driver,
300            backend: &mut *self.backend,
301            region: &mut *self.region,
302            timer: self.timer,
303        }
304    }
305
306    pub fn region_token(&mut self) -> &mut RegionToken<'d> {
307        self.region
308    }
309
310    #[doc(hidden)]
311    pub fn region_token_ref(&self) -> &RegionToken<'d> {
312        self.region
313    }
314
315    pub fn driver_ref(&self) -> DriverRef<'d> {
316        self.driver
317    }
318
319    pub fn timer(&self) -> &'d timer::Timer<'d> {
320        self.timer
321    }
322
323    /// Returns the monotonic-clock snapshot for the current driver turn.
324    pub fn turn_now(&self) -> Instant {
325        self.driver.turn_now()
326    }
327
328    /// Starts a new driver-clock epoch and returns its snapshot.
329    ///
330    /// Runtimes should refresh once before dispatching a completion/ready batch
331    /// and once after application callbacks, immediately before timeout
332    /// expiration and park-duration calculations.
333    #[doc(hidden)]
334    pub fn refresh_turn_clock(&mut self) -> Instant {
335        let now = Instant::now();
336        self.driver.shared.turn_clock.set(now);
337        now
338    }
339
340    pub(crate) fn backend(&mut self) -> &mut Backend {
341        self.backend
342    }
343
344    pub(crate) fn backend_ref(&self) -> &Backend {
345        self.backend
346    }
347
348    pub(crate) fn release_buffer(&mut self, buffer: RecvBuffer) {
349        <Backend as BufferBackend>::release_buffer(self.backend(), buffer);
350    }
351
352    pub(crate) fn flush_returned_buffers(&mut self) {
353        while let Some(buffer) = self.driver.shared.returned_buffers.pop_front() {
354            self.release_buffer(buffer);
355        }
356    }
357    pub fn guard(&mut self, fd: Fd<'d>) -> FdGuard<'_, 'd> {
358        let (slot, driver, retire_slot) = fd.into_parts();
359        FdGuard::new(self.backend(), slot, driver, retire_slot)
360    }
361}
362
363#[derive(Clone, Copy, Debug, PartialEq, Eq)]
364pub struct Config {
365    pub ring_entries: u32,
366    pub cq_entries: u32,
367    pub fixed_file_slots: u32,
368    pub accept_slots: u32,
369    pub recv: recv::Config,
370    pub defer_taskrun: bool,
371    pub ready_slots: usize,
372    pub timer_slots: usize,
373}
374
375impl Default for Config {
376    fn default() -> Self {
377        Self {
378            ring_entries: 1024,
379            cq_entries: 2048,
380            fixed_file_slots: 65536,
381            accept_slots: 65536,
382            recv: recv::Config {
383                len: 4096,
384                entries: 128,
385            },
386            defer_taskrun: false,
387            ready_slots: 65536,
388            timer_slots: 65536,
389        }
390    }
391}
392
393impl Config {
394    const MAX_ENTRIES: u32 = 32768;
395
396    pub fn fixed_file_slots(&self) -> u32 {
397        self.fixed_file_slots
398    }
399
400    fn sized_sq(max_connections: u32, outbound_reserve: u32) -> u32 {
401        max_connections
402            .saturating_add(outbound_reserve)
403            .next_power_of_two()
404            .clamp(64, Self::MAX_ENTRIES)
405    }
406
407    pub fn for_profile<P: DriverProfile>() -> Self {
408        Self {
409            ring_entries: P::RING_ENTRIES,
410            cq_entries: P::CQ_ENTRIES,
411            fixed_file_slots: P::FIXED_FILE_SLOTS,
412            accept_slots: P::FIXED_FILE_SLOTS.saturating_sub(P::OUTBOUND_RESERVE),
413            recv: recv::Config {
414                len: P::RECV_BUF_LEN,
415                entries: P::RECV_BUF_ENTRIES,
416            },
417            defer_taskrun: P::DEFER_TASKRUN,
418            ready_slots: P::READY_SLOTS,
419            timer_slots: P::READY_SLOTS,
420        }
421    }
422
423    pub fn for_tcp_profile<P: DriverProfile>(max_connections: usize) -> Self {
424        let max_connections = u32::try_from(max_connections).unwrap_or(u32::MAX);
425        let accept_slots =
426            max_connections.min(P::FIXED_FILE_SLOTS.saturating_sub(P::OUTBOUND_RESERVE));
427        Self {
428            ring_entries: Self::sized_sq(accept_slots, P::OUTBOUND_RESERVE).min(P::RING_ENTRIES),
429            cq_entries: P::CQ_ENTRIES,
430            fixed_file_slots: accept_slots.saturating_add(P::OUTBOUND_RESERVE),
431            accept_slots,
432            recv: recv::Config::for_accept(accept_slots, P::RECV_BUF_LEN, Self::MAX_ENTRIES),
433            defer_taskrun: P::DEFER_TASKRUN,
434            ready_slots: P::READY_SLOTS,
435            timer_slots: P::READY_SLOTS,
436        }
437    }
438
439    pub fn for_quic_udp(recv_buf_entries: u32, recv_buf_len: u32) -> Self {
440        Self {
441            ring_entries: 256,
442            cq_entries: 1024,
443            fixed_file_slots: 16,
444            accept_slots: 0,
445            recv: recv::Config {
446                len: recv_buf_len as usize,
447                entries: recv_buf_entries.min(u16::MAX as u32) as u16,
448            },
449            defer_taskrun: false,
450            ready_slots: 1024,
451            timer_slots: 1024,
452        }
453    }
454
455    pub fn with_recv(mut self, len: usize, entries: u16) -> Self {
456        self.recv.apply_overrides(len, entries);
457        self
458    }
459
460    pub(crate) fn validate(&self) -> Result<()> {
461        if self.timer_slots > u32::MAX as usize {
462            return Err(io::Error::new(
463                ErrorKind::InvalidInput,
464                "dope: timer capacity exceeds u32 slots",
465            ));
466        }
467        if self.accept_slots > self.fixed_file_slots {
468            return Err(io::Error::new(
469                ErrorKind::InvalidInput,
470                "dope: accept slots exceed fixed-file capacity",
471            ));
472        }
473        if TokenCapacity::new(self.fixed_file_slots as usize).is_none() {
474            return Err(io::Error::new(
475                ErrorKind::InvalidInput,
476                "dope: fixed-file capacity exceeds token slots",
477            ));
478        }
479        Backend::snapshot()?
480            .check_slots(self.fixed_file_slots)
481            .map_err(io::Error::from)
482    }
483}
484
485#[derive(Debug, Clone, Copy)]
486pub struct PushError;
487
488impl Display for PushError {
489    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
490        formatter.write_str("dope: SQE push failed")
491    }
492}
493
494impl Error for PushError {}
495
496impl From<PushError> for io::Error {
497    fn from(_: PushError) -> Self {
498        io::Error::from(ErrorKind::WouldBlock)
499    }
500}
501
502pub struct OutboundReservation<'d> {
503    base: u32,
504    capacity: u32,
505    _brand: Invariant<'d>,
506    _thread: ThreadBound,
507}
508
509#[derive(Clone, Copy)]
510pub struct OutboundSlot<'d> {
511    fd: FdSlot,
512    _brand: Invariant<'d>,
513}
514
515impl<'d> OutboundSlot<'d> {
516    pub fn bind(self, driver: DriverRef<'d>) -> Fd<'d> {
517        Fd::from_range_slot(self.fd, driver)
518    }
519}
520
521impl<'d> OutboundReservation<'d> {
522    pub(crate) fn new(base: u32, capacity: u32) -> Self {
523        Self {
524            base,
525            capacity,
526            _brand: PhantomData,
527            _thread: ThreadBound::NEW,
528        }
529    }
530
531    pub fn empty() -> Self {
532        Self {
533            base: 0,
534            capacity: 0,
535            _brand: PhantomData,
536            _thread: ThreadBound::NEW,
537        }
538    }
539
540    pub fn slot(&self, local: SlotIndex) -> Option<OutboundSlot<'d>> {
541        if local.raw() >= self.capacity {
542            return None;
543        }
544        let index = SlotIndex::try_new(self.base.checked_add(local.raw())?)?;
545        Some(OutboundSlot {
546            fd: FdSlot::from_index(index),
547            _brand: PhantomData,
548        })
549    }
550
551    pub(crate) fn into_range(self) -> (u32, u32) {
552        (self.base, self.capacity)
553    }
554}