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 }