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

src/wpe/glib_source.rs (5.6K)

  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     /// When GLib next wants dispatching with no fd to say so — its own
 34     /// timeout, measured from the moment it was asked (`sync`). `None`:
 35     /// nothing scheduled, so only an fd (or the app) wakes it.
 36     pub(super) deadline: Option<std::time::Instant>,
 37 }
 38 
 39 fn flags_of(events: u16) -> epoll::EventFlags {
 40     let mut f = epoll::EventFlags::empty();
 41     // G_IO_IN / OUT / ERR / HUP, which are the poll(2) values.
 42     if events & 0x001 != 0 {
 43         f |= epoll::EventFlags::IN;
 44     }
 45     if events & 0x004 != 0 {
 46         f |= epoll::EventFlags::OUT;
 47     }
 48     if events & 0x008 != 0 {
 49         f |= epoll::EventFlags::ERR;
 50     }
 51     if events & 0x010 != 0 {
 52         f |= epoll::EventFlags::HUP;
 53     }
 54     f
 55 }
 56 
 57 impl GlibPoll {
 58     pub(super) fn new() -> std::io::Result<Self> {
 59         let epfd = epoll::create(epoll::CreateFlags::CLOEXEC)?;
 60         let mut this = Self {
 61             epfd,
 62             registered: Vec::new(),
 63             fds: Vec::new(),
 64             timeout: None,
 65             deadline: None,
 66         };
 67         this.sync();
 68         Ok(this)
 69     }
 70 
 71     pub(super) fn fd(&self) -> BorrowedFd<'_> {
 72         self.epfd.as_fd()
 73     }
 74 
 75     /// Ask GLib what it wants polled, and make the epoll set match.
 76     ///
 77     /// Called after every dispatch, because WebKit adds and drops fds as it
 78     /// opens connections — a set captured once goes stale within a page load.
 79     pub(super) fn sync(&mut self) {
 80         unsafe {
 81             let ctx = g_main_context_default();
 82             // `query` is only meaningful between prepare and check; we are not
 83             // running that protocol ourselves, but prepare also updates the
 84             // context's own idea of the timeout, so call it for that.
 85             let mut max_priority: i32 = 0;
 86             g_main_context_prepare(ctx, &mut max_priority);
 87 
 88             let mut timeout: i32 = -1;
 89             // Two-pass: ask for the count, then fill.
 90             let n = g_main_context_query(ctx, max_priority, &mut timeout, std::ptr::null_mut(), 0);
 91             self.fds.clear();
 92             self.fds.resize(n.max(0) as usize, std::mem::zeroed());
 93             let n = if self.fds.is_empty() {
 94                 0
 95             } else {
 96                 g_main_context_query(
 97                     ctx,
 98                     max_priority,
 99                     &mut timeout,
100                     self.fds.as_mut_ptr(),
101                     self.fds.len() as i32,
102                 )
103             };
104             self.fds.truncate(n.max(0) as usize);
105             self.timeout = (timeout >= 0).then_some(timeout as u32);
106             // At least 4 ms out: a source that is ready again at once (a
107             // repeating idle) must not turn the loop into a spin.
108             self.deadline = self.timeout.map(|ms| {
109                 std::time::Instant::now() + std::time::Duration::from_millis(u64::from(ms).max(4))
110             });
111         }
112 
113         let want: Vec<(i32, epoll::EventFlags)> = self
114             .fds
115             .iter()
116             .map(|p| (p.fd, flags_of(p.events)))
117             .collect();
118 
119         // Diff against what is registered. Same-fd-different-flags is a
120         // modify, not a delete plus add, so a busy socket is not churned.
121         for (fd, flags) in &want {
122             let borrowed = unsafe { BorrowedFd::borrow_raw(*fd) };
123             let data = epoll::EventData::new_u64(*fd as u64);
124             match self.registered.iter().find(|(f, _)| f == fd) {
125                 Some((_, old)) if old == flags => {}
126                 Some(_) => {
127                     let _ = epoll::modify(&self.epfd, borrowed, data, *flags);
128                 }
129                 None => {
130                     let _ = epoll::add(&self.epfd, borrowed, data, *flags);
131                 }
132             }
133         }
134         for (fd, _) in &self.registered {
135             if !want.iter().any(|(f, _)| f == fd) {
136                 let _ = epoll::delete(&self.epfd, unsafe { BorrowedFd::borrow_raw(*fd) });
137             }
138         }
139         self.registered = want;
140     }
141 
142     /// Drain the inner epoll so it stops reporting readable. calloop is
143     /// level-triggered on this fd; without this the loop would spin on a
144     /// socket GLib has not consumed yet.
145     pub(super) fn drain(&self) {
146         let mut events = epoll::EventVec::with_capacity(16);
147         let _ = epoll::wait(&self.epfd, &mut events, 0);
148     }
149 }