git.lucas.co / cce-browser
web browser (Servo)
git clone https://git.lucas.co/cce-browser.git

src/wpe/glib_source.rs (5.1K)

  1 //! Waking on GLib activity instead of polling for it.
  2 //!
  3 //! WPE runs on a GLib `GMainContext`; cce-ui runs a calloop loop. The first
  4 //! cut of [`super::WebKitHost::pump`] simply drained the context on a timer,
  5 //! which works but burns wakeups when nothing is happening and adds latency
  6 //! when something is.
  7 //!
  8 //! The bridge here is deliberately narrow: **calloop decides *when to look*,
  9 //! GLib still does its own iteration.** We never reimplement GLib's
 10 //! prepare/check/dispatch protocol — `g_main_context_iteration` does that,
 11 //! correctly, and we only use `g_main_context_query` to learn what to wait on.
 12 //!
 13 //! GLib's fd set changes as WebKit opens sockets, and calloop wants stable
 14 //! registrations, so the changing set lives in an **inner epoll fd** that is
 15 //! itself the one stable thing calloop watches. Each pump re-syncs that set.
 16 //! GLib also asks for a timeout, which a calloop timer carries.
 17 
 18 use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
 19 
 20 use rustix::event::epoll;
 21 
 22 use super::ffi::*;
 23 
 24 /// The GLib fd set, mirrored into one epoll fd that calloop can watch.
 25 pub(super) struct GlibPoll {
 26     epfd: OwnedFd,
 27     /// What is currently registered, so a re-sync can diff rather than
 28     /// teardown-and-rebuild every pump.
 29     registered: Vec<(i32, epoll::EventFlags)>,
 30     fds: Vec<GPollFD>,
 31     /// GLib's requested timeout in ms; `None` means "no timer needed".
 32     pub(super) timeout: Option<u32>,
 33 }
 34 
 35 fn flags_of(events: u16) -> epoll::EventFlags {
 36     let mut f = epoll::EventFlags::empty();
 37     // G_IO_IN / OUT / ERR / HUP, which are the poll(2) values.
 38     if events & 0x001 != 0 {
 39         f |= epoll::EventFlags::IN;
 40     }
 41     if events & 0x004 != 0 {
 42         f |= epoll::EventFlags::OUT;
 43     }
 44     if events & 0x008 != 0 {
 45         f |= epoll::EventFlags::ERR;
 46     }
 47     if events & 0x010 != 0 {
 48         f |= epoll::EventFlags::HUP;
 49     }
 50     f
 51 }
 52 
 53 impl GlibPoll {
 54     pub(super) fn new() -> std::io::Result<Self> {
 55         let epfd = epoll::create(epoll::CreateFlags::CLOEXEC)?;
 56         let mut this = Self {
 57             epfd,
 58             registered: Vec::new(),
 59             fds: Vec::new(),
 60             timeout: None,
 61         };
 62         this.sync();
 63         Ok(this)
 64     }
 65 
 66     pub(super) fn fd(&self) -> BorrowedFd<'_> {
 67         self.epfd.as_fd()
 68     }
 69 
 70     /// Ask GLib what it wants polled, and make the epoll set match.
 71     ///
 72     /// Called after every dispatch, because WebKit adds and drops fds as it
 73     /// opens connections — a set captured once goes stale within a page load.
 74     pub(super) fn sync(&mut self) {
 75         unsafe {
 76             let ctx = g_main_context_default();
 77             // `query` is only meaningful between prepare and check; we are not
 78             // running that protocol ourselves, but prepare also updates the
 79             // context's own idea of the timeout, so call it for that.
 80             let mut max_priority: i32 = 0;
 81             g_main_context_prepare(ctx, &mut max_priority);
 82 
 83             let mut timeout: i32 = -1;
 84             // Two-pass: ask for the count, then fill.
 85             let n = g_main_context_query(ctx, max_priority, &mut timeout, std::ptr::null_mut(), 0);
 86             self.fds.clear();
 87             self.fds.resize(n.max(0) as usize, std::mem::zeroed());
 88             let n = if self.fds.is_empty() {
 89                 0
 90             } else {
 91                 g_main_context_query(
 92                     ctx,
 93                     max_priority,
 94                     &mut timeout,
 95                     self.fds.as_mut_ptr(),
 96                     self.fds.len() as i32,
 97                 )
 98             };
 99             self.fds.truncate(n.max(0) as usize);
100             self.timeout = (timeout >= 0).then_some(timeout as u32);
101         }
102 
103         let want: Vec<(i32, epoll::EventFlags)> = self
104             .fds
105             .iter()
106             .map(|p| (p.fd, flags_of(p.events)))
107             .collect();
108 
109         // Diff against what is registered. Same-fd-different-flags is a
110         // modify, not a delete plus add, so a busy socket is not churned.
111         for (fd, flags) in &want {
112             let borrowed = unsafe { BorrowedFd::borrow_raw(*fd) };
113             let data = epoll::EventData::new_u64(*fd as u64);
114             match self.registered.iter().find(|(f, _)| f == fd) {
115                 Some((_, old)) if old == flags => {}
116                 Some(_) => {
117                     let _ = epoll::modify(&self.epfd, borrowed, data, *flags);
118                 }
119                 None => {
120                     let _ = epoll::add(&self.epfd, borrowed, data, *flags);
121                 }
122             }
123         }
124         for (fd, _) in &self.registered {
125             if !want.iter().any(|(f, _)| f == fd) {
126                 let _ = epoll::delete(&self.epfd, unsafe { BorrowedFd::borrow_raw(*fd) });
127             }
128         }
129         self.registered = want;
130     }
131 
132     /// Drain the inner epoll so it stops reporting readable. calloop is
133     /// level-triggered on this fd; without this the loop would spin on a
134     /// socket GLib has not consumed yet.
135     pub(super) fn drain(&self) {
136         let mut events = epoll::EventVec::with_capacity(16);
137         let _ = epoll::wait(&self.epfd, &mut events, 0);
138     }
139 }