script: Make timers per-process (#34581)

Before all timers were managed by the Constellation process, meaning
that they had to trigger IPC calls to be scheduled and fired. Currently,
timers are only used in the `ScriptThread`, so it makes sense that they
are per-process.

This change restores the timer thread functionality that existed before
avoided entirely. Completion is done using a callback that is sent to
the timer thread similarly to how fetch is done. This allows reusing the
existing task queue without making any new channels.

Fixes #15219.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2024-12-24 10:53:35 +01:00 committed by GitHub
parent ff7626bfc6
commit 226299380d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 366 additions and 324 deletions

View file

@ -110,7 +110,7 @@ use canvas_traits::ConstellationCanvasMsg;
use compositing_traits::{
CompositorMsg, CompositorProxy, ConstellationMsg as FromCompositorMsg, SendableFrameTree,
};
use crossbeam_channel::{after, never, select, unbounded, Receiver, Sender};
use crossbeam_channel::{select, unbounded, Receiver, Sender};
use devtools_traits::{
ChromeToDevtoolsControlMsg, DevtoolsControlMsg, DevtoolsPageInfo, NavigationState,
ScriptToDevtoolsControlMsg,
@ -143,7 +143,7 @@ use script_traits::{
LogEntry, MediaSessionActionType, MessagePortMsg, MouseEventType, NavigationHistoryBehavior,
PortMessageTask, SWManagerMsg, SWManagerSenders, ScriptMsg as FromScriptMsg,
ScriptToConstellationChan, ServiceWorkerManagerFactory, ServiceWorkerMsg,
StructuredSerializedData, Theme, TimerSchedulerMsg, TraversalDirection, UpdatePipelineIdReason,
StructuredSerializedData, Theme, TraversalDirection, UpdatePipelineIdReason,
WebDriverCommandMsg, WindowSizeData, WindowSizeType,
};
use serde::{Deserialize, Serialize};
@ -172,7 +172,6 @@ use crate::serviceworker::ServiceWorkerUnprivilegedContent;
use crate::session_history::{
JointSessionHistory, NeedsToReload, SessionHistoryChange, SessionHistoryDiff,
};
use crate::timer_scheduler::TimerScheduler;
use crate::webview::WebViewManager;
type PendingApprovalNavigations = HashMap<PipelineId, (LoadData, NavigationHistoryBehavior)>;
@ -381,15 +380,6 @@ pub struct Constellation<STF, SWF> {
/// memory profiler thread.
mem_profiler_chan: mem::ProfilerChan,
/// A channel for a pipeline to schedule timer events.
scheduler_ipc_sender: IpcSender<TimerSchedulerMsg>,
/// The receiver to which the IPC requests from scheduler_chan will be forwarded.
scheduler_receiver: Receiver<Result<TimerSchedulerMsg, IpcError>>,
/// The logic and data behing scheduling timer events.
timer_scheduler: TimerScheduler,
/// A single WebRender document the constellation operates on.
webrender_document: DocumentId,
@ -660,13 +650,6 @@ where
namespace_ipc_receiver,
);
let (scheduler_ipc_sender, scheduler_ipc_receiver) =
ipc::channel().expect("ipc channel failure");
let scheduler_receiver =
route_ipc_receiver_to_new_crossbeam_receiver_preserving_errors(
scheduler_ipc_receiver,
);
let (background_hang_monitor_ipc_sender, background_hang_monitor_ipc_receiver) =
ipc::channel().expect("ipc channel failure");
let background_hang_monitor_receiver =
@ -762,9 +745,6 @@ where
window_size: initial_window_size,
phantom: PhantomData,
webdriver: WebDriverData::new(),
timer_scheduler: TimerScheduler::new(),
scheduler_ipc_sender,
scheduler_receiver,
document_states: HashMap::new(),
webrender_document: state.webrender_document,
#[cfg(feature = "webgpu")]
@ -1015,7 +995,6 @@ where
.clone(),
layout_to_constellation_chan: self.layout_sender.clone(),
layout_factory: self.layout_factory.clone(),
scheduler_chan: self.scheduler_ipc_sender.clone(),
compositor_proxy: self.compositor_proxy.clone(),
devtools_sender: self.devtools_sender.clone(),
bluetooth_thread: self.bluetooth_ipc_sender.clone(),
@ -1187,16 +1166,7 @@ where
Layout(FromLayoutMsg),
NetworkListener((PipelineId, FetchResponseMsg)),
FromSWManager(SWManagerMsg),
Timer(TimerSchedulerMsg),
}
// A timeout corresponding to the earliest scheduled timer event, if any.
let scheduler_timeout = self
.timer_scheduler
.check_timers()
.map(after)
.unwrap_or(never());
// Get one incoming request.
// This is one of the few places where the compositor is
// allowed to panic. If one of the receiver.recv() calls
@ -1236,14 +1206,6 @@ where
recv(self.swmanager_receiver) -> msg => {
msg.expect("Unexpected SW channel panic in constellation").map(Request::FromSWManager)
}
recv(self.scheduler_receiver) -> msg => {
msg.expect("Unexpected schedule channel panic in constellation").map(Request::Timer)
}
recv(scheduler_timeout) -> _ => {
// Note: by returning, we go back to the top,
// where check_timers will be called.
return;
},
}
};
@ -1272,9 +1234,6 @@ where
Request::FromSWManager(message) => {
self.handle_request_from_swmanager(message);
},
Request::Timer(message) => {
self.timer_scheduler.handle_timer_request(message);
},
}
}

View file

@ -16,7 +16,6 @@ mod pipeline;
mod sandboxing;
mod serviceworker;
mod session_history;
mod timer_scheduler;
mod webview;
pub use crate::constellation::{Constellation, InitialConstellationState};

View file

@ -35,7 +35,7 @@ use script_layout_interface::{LayoutFactory, ScriptThreadFactory};
use script_traits::{
AnimationState, ConstellationControlMsg, DiscardBrowsingContext, DocumentActivity,
InitialScriptState, LayoutMsg, LoadData, NewLayoutInfo, SWManagerMsg,
ScriptToConstellationChan, TimerSchedulerMsg, WindowSizeData,
ScriptToConstellationChan, WindowSizeData,
};
use serde::{Deserialize, Serialize};
use servo_config::opts::{self, Opts};
@ -139,9 +139,6 @@ pub struct InitialPipelineState {
/// A fatory for creating layouts to be used by the ScriptThread.
pub layout_factory: Arc<dyn LayoutFactory>,
/// A channel to schedule timer events.
pub scheduler_chan: IpcSender<TimerSchedulerMsg>,
/// A channel to the compositor.
pub compositor_proxy: CompositorProxy,
@ -270,7 +267,6 @@ impl Pipeline {
.background_hang_monitor_to_constellation_chan
.clone(),
bhm_control_port: None,
scheduler_chan: state.scheduler_chan,
devtools_ipc_sender: script_to_devtools_ipc_sender,
bluetooth_thread: state.bluetooth_thread,
swmanager_thread: state.swmanager_thread,
@ -478,7 +474,6 @@ pub struct UnprivilegedPipelineContent {
background_hang_monitor_to_constellation_chan: IpcSender<HangMonitorAlert>,
bhm_control_port: Option<IpcReceiver<BackgroundHangMonitorControlMsg>>,
layout_to_constellation_chan: IpcSender<LayoutMsg>,
scheduler_chan: IpcSender<TimerSchedulerMsg>,
devtools_ipc_sender: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
bluetooth_thread: IpcSender<BluetoothRequest>,
swmanager_thread: IpcSender<SWManagerMsg>,
@ -528,7 +523,6 @@ impl UnprivilegedPipelineContent {
script_to_constellation_chan: self.script_to_constellation_chan.clone(),
background_hang_monitor_register: background_hang_monitor_register.clone(),
layout_to_constellation_chan: self.layout_to_constellation_chan.clone(),
scheduler_chan: self.scheduler_chan,
bluetooth_thread: self.bluetooth_thread,
resource_threads: self.resource_threads,
image_cache: image_cache.clone(),

View file

@ -1,73 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cmp::{self, Ord};
use std::collections::BinaryHeap;
use std::time::{Duration, Instant};
use script_traits::{TimerEvent, TimerEventRequest, TimerSchedulerMsg};
pub struct TimerScheduler(BinaryHeap<ScheduledEvent>);
struct ScheduledEvent {
request: TimerEventRequest,
for_time: Instant,
}
impl Ord for ScheduledEvent {
fn cmp(&self, other: &ScheduledEvent) -> cmp::Ordering {
self.for_time.cmp(&other.for_time).reverse()
}
}
impl PartialOrd for ScheduledEvent {
fn partial_cmp(&self, other: &ScheduledEvent) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Eq for ScheduledEvent {}
impl PartialEq for ScheduledEvent {
fn eq(&self, other: &ScheduledEvent) -> bool {
std::ptr::eq(self, other)
}
}
impl TimerScheduler {
pub fn new() -> Self {
TimerScheduler(BinaryHeap::<ScheduledEvent>::new())
}
/// Dispatch any events whose due time is past,
/// and return a timeout corresponding to the earliest scheduled event, if any.
pub fn check_timers(&mut self) -> Option<Duration> {
let now = Instant::now();
loop {
match self.0.peek() {
// Dispatch the event if its due time is past
Some(event) if event.for_time <= now => {
let TimerEventRequest(ref sender, source, id, _) = event.request;
let _ = sender.send(TimerEvent(source, id));
},
// Do not schedule a timeout.
None => return None,
// Schedule a timeout for the earliest event.
Some(event) => return Some(event.for_time - now),
}
// Remove the event from the priority queue
// (Note this only executes when the first event has been dispatched).
self.0.pop();
}
}
/// Handle an incoming timer request.
pub fn handle_timer_request(&mut self, request: TimerSchedulerMsg) {
let TimerEventRequest(_, _, _, delay) = request.0;
let event = ScheduledEvent {
request: request.0,
for_time: Instant::now() + delay,
};
self.0.push(event);
}
}