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 }