1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#[cfg(feature = "once_cell_try")]
use std::sync::OnceLock;
use std::{io, marker::PhantomData};

use compio_buf::IntoInner;
use compio_driver::AsRawFd;
#[cfg(not(feature = "once_cell_try"))]
use once_cell::sync::OnceCell as OnceLock;

use crate::Runtime;

/// Attach a handle to the driver of current thread.
///
/// A handle can and only can attach once to one driver. However, the handle
/// itself is Send & Sync. We mark it !Send & !Sync to warn users, making them
/// ensure that they are using it in the correct thread.
#[derive(Debug, Clone)]
pub struct Attacher {
    // Make it thread safe.
    once: OnceLock<usize>,
    // Make it !Send & !Sync.
    _p: PhantomData<*mut ()>,
}

impl Attacher {
    /// Create [`Attacher`].
    pub const fn new() -> Self {
        Self {
            once: OnceLock::new(),
            _p: PhantomData,
        }
    }

    /// Attach the source. This method could be called many times, but if the
    /// action fails, the error will only return once.
    ///
    /// You should always call this method before accessing the runtime. It
    /// ensures that the current runtime is the exact runtime attached before.
    pub fn attach(&self, source: &impl AsRawFd) -> io::Result<()> {
        let r = Runtime::current();
        let inner = r.inner();
        let id = self.once.get_or_try_init(|| {
            inner.attach(source.as_raw_fd())?;
            io::Result::Ok(inner.id())
        })?;
        if id != &inner.id() {
            Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "the current runtime is not the attached runtime",
            ))
        } else {
            Ok(())
        }
    }

    /// Check if [`attach`] has been called.
    pub fn is_attached(&self) -> bool {
        self.once.get().is_some()
    }

    /// Try clone self with the cloned source. The attach state will be
    /// reserved.
    ///
    /// ## Platform specific
    /// * io-uring/polling: it will try to attach in the current thread if
    ///   needed.
    pub fn try_clone(&self, source: &impl AsRawFd) -> io::Result<Self> {
        if cfg!(windows) {
            Ok(self.clone())
        } else {
            let new_self = Self::new();
            if self.is_attached() {
                new_self.attach(source)?;
            }
            Ok(new_self)
        }
    }
}

/// Represents an attachable resource to driver.
pub trait Attachable {
    /// Attach self to the global driver.
    fn attach(&self) -> io::Result<()>;

    /// Check if [`Attachable::attach`] has been called.
    fn is_attached(&self) -> bool;
}

/// A [`Send`] wrapper for attachable resource that has not been attached. The
/// resource should be able to send to another thread before attaching.
pub struct Unattached<T: Attachable>(T);

impl<T: Attachable> Unattached<T> {
    /// Create the [`Unattached`] wrapper, or fail if the resource has already
    /// been attached.
    pub fn new(a: T) -> Result<Self, T> {
        if a.is_attached() { Err(a) } else { Ok(Self(a)) }
    }

    /// Create [`Unattached`] without checking.
    ///
    /// # Safety
    ///
    /// The caller should ensure that the resource has not been attached.
    pub unsafe fn new_unchecked(a: T) -> Self {
        Self(a)
    }
}

impl<T: Attachable> IntoInner for Unattached<T> {
    type Inner = T;

    fn into_inner(self) -> Self::Inner {
        self.0
    }
}

unsafe impl<T: Attachable> Send for Unattached<T> {}
unsafe impl<T: Attachable> Sync for Unattached<T> {}

#[macro_export]
#[doc(hidden)]
macro_rules! impl_attachable {
    ($t:ty, $inner:ident) => {
        impl $crate::Attachable for $t {
            fn attach(&self) -> ::std::io::Result<()> {
                self.$inner.attach()
            }

            fn is_attached(&self) -> bool {
                self.$inner.is_attached()
            }
        }
    };
}