Skip to main content

pal_async/unix/
epoll.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! An executor based on epoll.
5
6use super::epoll_uring::EPOLL_URING_STATE;
7use super::epoll_uring::EPOLL_URING_TOKEN;
8use super::epoll_uring::EpollIoUring;
9use super::epoll_uring::EpollUringThreadState;
10use super::wait::FdWait;
11use crate::fd::FdReadyDriver;
12use crate::fd::PollFdReady;
13use crate::interest::InterestSlot;
14use crate::interest::PollEvents;
15use crate::interest::PollInterestSet;
16use crate::io_pool::IoBackend;
17use crate::io_pool::IoDriver;
18use crate::io_pool::IoPool;
19use crate::timer::Instant;
20use crate::timer::PollTimer;
21use crate::timer::TimerDriver;
22use crate::timer::TimerQueue;
23use crate::timer::TimerQueueId;
24use crate::timer::TimerResult;
25use crate::wait::WaitDriver;
26use crate::waker::WakerList;
27use futures::FutureExt;
28use futures::task::ArcWake;
29use futures::task::waker_ref;
30use once_cell::sync::OnceCell;
31use pal::unix::Errno;
32use pal::unix::SyscallResult;
33use pal::unix::while_eintr;
34use pal_event::Event;
35use parking_lot::Mutex;
36use std::fs::File;
37use std::future::Future;
38use std::io;
39use std::os::unix::prelude::*;
40use std::pin::pin;
41use std::sync::Arc;
42use std::task::Context;
43use std::task::Poll;
44
45/// An single-threaded task pool backed by epoll.
46pub type EpollPool = IoPool<EpollBackend>;
47
48/// A driver to spawn tasks and IO objects on [`EpollPool`].
49pub type EpollDriver = IoDriver<EpollBackend>;
50
51/// A weak reference to an [`EpollDriver`].
52pub type WeakEpollDriver = crate::io_pool::WeakIoDriver<EpollBackend>;
53
54#[derive(Debug)]
55pub struct EpollBackend {
56    epfd: EpollFd,
57    wake_event: Event,
58    state: Mutex<EpollState>,
59    io_uring: OnceCell<EpollIoUring>,
60}
61
62impl Default for EpollBackend {
63    fn default() -> Self {
64        let epfd = EpollFd::new().expect("epoll not functional");
65        let wake_event = Event::new();
66        // Register for notifications when the wake event is signaled. Use
67        // edge-triggered mode because we can (we immediately consume the event
68        // when it is signaled) and because miri requires it.
69        epfd.add(
70            wake_event.as_fd().as_raw_fd(),
71            libc::EPOLLIN | libc::EPOLLET,
72            0,
73        )
74        .expect("could not add wake event");
75        Self {
76            epfd,
77            wake_event,
78            state: Mutex::new(EpollState {
79                state: PoolState::Running,
80                timers: TimerQueue::default(),
81                fd_ready_to_delete: Vec::new(),
82            }),
83            io_uring: OnceCell::new(),
84        }
85    }
86}
87
88#[derive(Debug)]
89struct EpollState {
90    state: PoolState,
91    timers: TimerQueue,
92    fd_ready_to_delete: Vec<Arc<FdReadyOp>>,
93}
94
95#[derive(Debug)]
96enum PoolState {
97    Running,
98    RunAgain,
99    Sleeping(Option<Instant>),
100    Waking,
101}
102
103impl PoolState {
104    fn reset(&mut self) {
105        match self {
106            PoolState::Running => {}
107            PoolState::RunAgain => {}
108            PoolState::Sleeping(_) => unreachable!(),
109            PoolState::Waking => {}
110        }
111        *self = PoolState::Running;
112    }
113
114    /// Returns true if the wake event must be signaled.
115    #[must_use]
116    fn wake(&mut self) -> bool {
117        match self {
118            PoolState::Running => {
119                *self = PoolState::RunAgain;
120                false
121            }
122            PoolState::RunAgain => false,
123            PoolState::Sleeping(_) => {
124                *self = PoolState::Waking;
125                true
126            }
127            PoolState::Waking => false,
128        }
129    }
130
131    fn can_sleep(&self) -> bool {
132        match self {
133            PoolState::Running => true,
134            PoolState::RunAgain => false,
135            PoolState::Sleeping(_) => unreachable!(),
136            PoolState::Waking => unreachable!(),
137        }
138    }
139
140    fn sleep(&mut self, deadline: Option<Instant>) {
141        match self {
142            PoolState::Running => {}
143            PoolState::RunAgain => unreachable!(),
144            PoolState::Sleeping(_) => unreachable!(),
145            PoolState::Waking => unreachable!(),
146        }
147        *self = PoolState::Sleeping(deadline);
148    }
149
150    /// Returns true if the wake event must be signaled.
151    #[must_use]
152    fn wake_for_timer(&mut self, deadline: Instant) -> bool {
153        match self {
154            PoolState::Running => false,
155            PoolState::RunAgain => false,
156            PoolState::Waking => false,
157            &mut PoolState::Sleeping(Some(current_deadline)) if current_deadline <= deadline => {
158                false
159            }
160            PoolState::Sleeping(_) => {
161                *self = PoolState::Waking;
162                true
163            }
164        }
165    }
166
167    fn is_referencing_ops(&self) -> bool {
168        match self {
169            PoolState::Running => false,
170            PoolState::RunAgain => false,
171            PoolState::Sleeping(_) => true,
172            PoolState::Waking => true,
173        }
174    }
175}
176
177impl IoBackend for EpollBackend {
178    fn name() -> &'static str {
179        "epoll"
180    }
181
182    fn run<Fut: Future>(self: &Arc<Self>, fut: Fut) -> Fut::Output {
183        let mut fut = pin!(fut);
184
185        let waker = waker_ref(self);
186        let mut cx = Context::from_waker(&waker);
187
188        let mut to_delete: Vec<_> = Vec::new();
189        let mut wakers = WakerList::default();
190
191        // Set up thread-local state for on-thread io-uring access.
192        // The io-uring may not be initialized yet (it's lazy), so we
193        // use a null pointer initially and update it on first use.
194        let uring_thread_state = EpollUringThreadState::new(std::ptr::null());
195
196        EPOLL_URING_STATE.with(|cell| {
197            cell.lend(&uring_thread_state, || {
198                let mut state = self.state.lock();
199                loop {
200                    state.state.reset();
201
202                    // Wake timers.
203                    state.timers.wake_expired(&mut wakers);
204                    drop(state);
205
206                    // Drain completions before polling so futures see
207                    // results from the previous cycle immediately.
208                    // Also update the thread-local ring pointer so
209                    // queue_sqe takes the fast path for on-thread callers.
210                    if let Some(uring) = self.io_uring.get() {
211                        // SAFETY: We are on the epoll thread and sole accessor
212                        // of the ring's SQ/CQ.
213                        unsafe { uring_thread_state.set_ring(uring) }
214                            .process_completions(&mut wakers);
215                    }
216
217                    wakers.wake();
218                    to_delete.clear();
219
220                    match fut.poll_unpin(&mut cx) {
221                        Poll::Ready(r) => break r,
222                        Poll::Pending => {}
223                    }
224
225                    state = self.state.lock();
226                    // This list is only populated while in the Sleeping state.
227                    assert!(state.fd_ready_to_delete.is_empty());
228
229                    if state.state.can_sleep() {
230                        let deadline = state.timers.next_deadline();
231                        state.state.sleep(deadline);
232                        drop(state);
233
234                        // Flush queued SQEs before sleeping. This is
235                        // the single io_uring_enter() per sleep cycle,
236                        // naturally batching all SQEs queued during
237                        // RunAgain into one syscall.
238                        if let Some(uring) = self.io_uring.get() {
239                            // SAFETY: We are on the epoll thread and sole
240                            // accessor of the ring's SQ/CQ.
241                            unsafe { uring_thread_state.set_ring(uring) }.flush();
242                        }
243
244                        let timeout = deadline
245                            .map(|deadline| {
246                                let now = Instant::now();
247                                (deadline.max(now) - now)
248                                    .as_millis()
249                                    .try_into()
250                                    .unwrap_or(i32::MAX)
251                            })
252                            .unwrap_or(-1);
253
254                        let mut events = [libc::epoll_event { events: 0, u64: 0 }; 8];
255                        let n = while_eintr(|| self.epfd.wait(&mut events, timeout))
256                            .expect("epoll_wait failed unexpectedly");
257
258                        // Block unnecessary wakeups.
259                        let _ = self.state.lock().state.wake();
260
261                        for event in &events[..n] {
262                            if event.u64 == 0 {
263                                self.wake_event.try_wait();
264                            } else if event.u64 == EPOLL_URING_TOKEN {
265                                // Completions are drained at the top of
266                                // the loop, so nothing to do here.
267                            } else {
268                                // SAFETY: the operation context is still alive and
269                                // can be dereferenced. It's possible the underlying
270                                // FdReady has been dropped, but in that case the
271                                // associated operation context will have been added
272                                // to the fd_ready_to_delete list.
273                                //
274                                // Note that this is only true until state reverts
275                                // to the Running state, which occurs below.
276                                let op = unsafe { &*(event.u64 as usize as *const FdReadyOp) };
277                                op.wake_ready(event.events, &mut wakers);
278                            }
279                        }
280
281                        state = self.state.lock();
282
283                        // Free any FdReadyOp objects that were deleted while in the epoll_wait call.
284                        to_delete.append(&mut state.fd_ready_to_delete);
285                    }
286                }
287            }) // cell.lend
288        }) // EPOLL_URING_STATE.with
289    }
290}
291
292#[derive(Debug)]
293struct EpollFd(File);
294
295impl EpollFd {
296    fn new() -> Result<Self, Errno> {
297        // SAFETY: epoll_create1 creates a new, uniquely owned fd.
298        let epfd = unsafe {
299            File::from_raw_fd(libc::epoll_create1(libc::EPOLL_CLOEXEC).syscall_result()?)
300        };
301        Ok(Self(epfd))
302    }
303
304    fn add(&self, fd: RawFd, events: i32, context: u64) -> Result<(), Errno> {
305        let mut event = libc::epoll_event {
306            events: events as u32,
307            u64: context,
308        };
309        // SAFETY: safe to call with any fd.
310        unsafe {
311            libc::epoll_ctl(self.0.as_raw_fd(), libc::EPOLL_CTL_ADD, fd, &mut event)
312                .syscall_result()?;
313        }
314        Ok(())
315    }
316
317    fn del(&self, fd: RawFd) -> Result<(), Errno> {
318        // SAFETY: safe to call with any fd.
319        unsafe {
320            libc::epoll_ctl(
321                self.0.as_raw_fd(),
322                libc::EPOLL_CTL_DEL,
323                fd,
324                std::ptr::null_mut(),
325            )
326            .syscall_result()?;
327        }
328        Ok(())
329    }
330
331    fn wait(&self, events: &mut [libc::epoll_event], timeout: i32) -> Result<usize, Errno> {
332        let maxevents = events.len() as i32;
333        // SAFETY: maxevents is set appropriately to write to the events slice.
334        let n = unsafe {
335            libc::epoll_wait(self.0.as_raw_fd(), events.as_mut_ptr(), maxevents, timeout)
336                .syscall_result()?
337        };
338        Ok(n as usize)
339    }
340}
341
342impl ArcWake for EpollBackend {
343    fn wake_by_ref(arc_self: &Arc<Self>) {
344        let wake = arc_self.state.lock().state.wake();
345        if wake {
346            arc_self.wake_event.signal();
347        }
348    }
349}
350
351#[derive(Debug)]
352pub struct FdReady {
353    op: Arc<FdReadyOp>,
354    epoll: Arc<EpollBackend>,
355    fd: RawFd,
356}
357
358#[derive(Debug)]
359struct FdReadyOp {
360    inner: Mutex<FdReadyInner>,
361}
362
363#[derive(Debug)]
364struct FdReadyInner {
365    interests: PollInterestSet,
366}
367
368impl FdReadyOp {
369    fn wake_ready(&self, ep_events: u32, wakers: &mut WakerList) {
370        let revents = PollEvents::from_epoll_events(ep_events);
371        self.inner.lock().interests.wake_ready(revents, wakers)
372    }
373}
374
375impl FdReadyDriver for EpollDriver {
376    type FdReady = FdReady;
377
378    fn new_fd_ready(&self, fd: RawFd) -> io::Result<Self::FdReady> {
379        let op = Arc::new(FdReadyOp {
380            inner: Mutex::new(FdReadyInner {
381                interests: PollInterestSet::default(),
382            }),
383        });
384        self.inner.epfd.add(
385            fd,
386            libc::EPOLLET | libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLPRI | libc::EPOLLRDHUP,
387            Arc::as_ptr(&op) as usize as u64,
388        )?;
389        // Add reference owned by epfd, reclaimed in drop.
390        let _ = Arc::into_raw(op.clone());
391
392        Ok(FdReady {
393            op,
394            epoll: self.inner.clone(),
395            fd,
396        })
397    }
398}
399
400impl Drop for FdReady {
401    fn drop(&mut self) {
402        // N.B. This can fail if `self.fd` was closed before `self` is
403        // dropped. Since other executors don't behave this way, this might
404        // be a common mistake. But it's not recoverable here (e.g. by
405        // ignoring EBADF or ENOENT), since `self.fd` might be reused for
406        // another file descriptor in the race window between closing the fd
407        // and issuing this ioctl.
408        //
409        // Instead, the user of this object should ensure that `self` is
410        // dropped before the associated fd is closed (e.g. by putting the
411        // `FdReady` above the `File` or whatever in their struct).
412        self.epoll
413            .epfd
414            .del(self.fd)
415            .expect("epoll_ctl unexpectedly failed");
416
417        // SAFETY: Reclaiming the reference added in new_fd_ready.
418        let op = unsafe { Arc::from_raw(Arc::as_ptr(&self.op)) };
419        let mut state = self.epoll.state.lock();
420        if state.state.is_referencing_ops() {
421            state.fd_ready_to_delete.push(op);
422            if state.state.wake() {
423                drop(state);
424                self.epoll.wake_event.signal();
425            }
426        }
427    }
428}
429
430impl PollFdReady for FdReady {
431    fn poll_fd_ready(
432        &mut self,
433        cx: &mut Context<'_>,
434        slot: InterestSlot,
435        events: PollEvents,
436    ) -> Poll<PollEvents> {
437        self.op.inner.lock().interests.poll_ready(cx, slot, events)
438    }
439
440    fn clear_fd_ready(&mut self, slot: InterestSlot) {
441        self.op.inner.lock().interests.clear_ready(slot)
442    }
443}
444
445impl WaitDriver for EpollDriver {
446    type Wait = FdWait<FdReady>;
447
448    fn new_wait(&self, fd: RawFd, read_size: usize) -> io::Result<Self::Wait> {
449        Ok(FdWait::new(fd, self.new_fd_ready(fd)?, read_size))
450    }
451}
452
453impl TimerDriver for EpollDriver {
454    type Timer = Timer;
455
456    fn new_timer(&self) -> Self::Timer {
457        let id = self.inner.state.lock().timers.add();
458        Timer {
459            epoll: self.inner.clone(),
460            id,
461        }
462    }
463}
464
465impl crate::io_uring::IoUringDriver for EpollDriver {
466    type Submitter = EpollIoUring;
467
468    fn io_uring_submitter(&self) -> Option<&EpollIoUring> {
469        self.inner
470            .io_uring
471            .get_or_try_init(|| {
472                EpollIoUring::new(
473                    self.inner.epfd.0.as_raw_fd(),
474                    self.inner.wake_event.as_fd().as_raw_fd(),
475                )
476            })
477            .ok()
478    }
479}
480
481#[derive(Debug)]
482pub struct Timer {
483    epoll: Arc<EpollBackend>,
484    id: TimerQueueId,
485}
486
487impl Drop for Timer {
488    fn drop(&mut self) {
489        let _waker = self.epoll.state.lock().timers.remove(self.id);
490    }
491}
492
493impl PollTimer for Timer {
494    fn poll_timer(&mut self, cx: &mut Context<'_>, deadline: Option<Instant>) -> Poll<Instant> {
495        let mut state = self.epoll.state.lock();
496        if let Some(deadline) = deadline {
497            state.timers.set_deadline(self.id, deadline);
498        }
499        match state.timers.poll_deadline(cx, self.id) {
500            TimerResult::TimedOut(now) => Poll::Ready(now),
501            TimerResult::Pending(deadline) => {
502                if state.state.wake_for_timer(deadline) {
503                    drop(state);
504                    self.epoll.wake_event.signal();
505                }
506                Poll::Pending
507            }
508        }
509    }
510
511    fn set_deadline(&mut self, deadline: Instant) {
512        let mut state = self.epoll.state.lock();
513        if state.timers.set_deadline(self.id, deadline) && state.state.wake_for_timer(deadline) {
514            drop(state);
515            self.epoll.wake_event.signal();
516        }
517    }
518}
519
520#[cfg(test)]
521mod tests {
522    use super::EpollPool;
523    use crate::executor_tests;
524
525    #[test]
526    fn waker_works() {
527        EpollPool::run_with(|_| executor_tests::waker_tests())
528    }
529
530    #[test]
531    fn spawn_works() {
532        executor_tests::spawn_tests(|| {
533            let pool = EpollPool::new();
534            (pool.driver(), move || pool.run())
535        })
536    }
537
538    #[test]
539    fn sleep_works() {
540        EpollPool::run_with(executor_tests::sleep_tests)
541    }
542
543    #[test]
544    fn wait_works() {
545        EpollPool::run_with(executor_tests::wait_tests)
546    }
547
548    #[test]
549    fn socket_works() {
550        EpollPool::run_with(executor_tests::socket_tests)
551    }
552
553    #[test]
554    fn process_works() {
555        EpollPool::run_with(executor_tests::process_tests)
556    }
557
558    #[test]
559    fn uring_works() {
560        EpollPool::run_with(executor_tests::io_uring_tests::uring_tests)
561    }
562}