mirror of
https://github.com/servo/servo.git
synced 2025-08-08 15:05:35 +01:00
constellation: join on script-threads (#38424)
We currently send exit signals to script-threads, and we also join on the BHM worker. This ensure the constellation shuts-down only after script has dropped it's sender to the BHM worker. By joining on the script-threads, we have a guarantee that they have exited(which is stronger than having dropped their senders) by the time the constellation exits. Testing: Manually opened many tabs and closed the window, both in single- and multi-process modes. Fixes: Part of - https://github.com/servo/servo/issues/30849 --------- Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com>
This commit is contained in:
parent
b23adab8a5
commit
a99ad240a0
5 changed files with 45 additions and 16 deletions
|
@ -461,6 +461,9 @@ pub struct Constellation<STF, SWF> {
|
||||||
|
|
||||||
/// The async runtime.
|
/// The async runtime.
|
||||||
async_runtime: Box<dyn AsyncRuntime>,
|
async_runtime: Box<dyn AsyncRuntime>,
|
||||||
|
|
||||||
|
/// When in single-process mode, join handles for script-threads.
|
||||||
|
script_join_handles: HashMap<WebViewId, JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State needed to construct a constellation.
|
/// State needed to construct a constellation.
|
||||||
|
@ -711,6 +714,7 @@ where
|
||||||
user_content_manager: state.user_content_manager,
|
user_content_manager: state.user_content_manager,
|
||||||
process_manager: ProcessManager::new(state.mem_profiler_chan),
|
process_manager: ProcessManager::new(state.mem_profiler_chan),
|
||||||
async_runtime: state.async_runtime,
|
async_runtime: state.async_runtime,
|
||||||
|
script_join_handles: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
constellation.run();
|
constellation.run();
|
||||||
|
@ -972,6 +976,10 @@ where
|
||||||
self.background_monitor_control_senders.push(chan);
|
self.background_monitor_control_senders.push(chan);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(join_handle) = pipeline.join_handle {
|
||||||
|
self.script_join_handles.insert(webview_id, join_handle);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(host) = host {
|
if let Some(host) = host {
|
||||||
debug!("{}: Adding new host entry {}", webview_id, host,);
|
debug!("{}: Adding new host entry {}", webview_id, host,);
|
||||||
self.set_event_loop(
|
self.set_event_loop(
|
||||||
|
@ -2523,12 +2531,20 @@ where
|
||||||
fn handle_shutdown(&mut self) {
|
fn handle_shutdown(&mut self) {
|
||||||
debug!("Handling shutdown.");
|
debug!("Handling shutdown.");
|
||||||
|
|
||||||
|
// In single process mode, join on script-threads
|
||||||
|
// from webview which haven't been manually closed before.
|
||||||
|
for (_, join_handle) in self.script_join_handles.drain() {
|
||||||
|
if join_handle.join().is_err() {
|
||||||
|
error!("Failed to join on a script-thread.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// In single process mode, join on the background hang monitor worker thread.
|
// In single process mode, join on the background hang monitor worker thread.
|
||||||
drop(self.background_monitor_register.take());
|
drop(self.background_monitor_register.take());
|
||||||
if let Some(join_handle) = self.background_monitor_register_join_handle.take() {
|
if let Some(join_handle) = self.background_monitor_register_join_handle.take() {
|
||||||
join_handle
|
if join_handle.join().is_err() {
|
||||||
.join()
|
error!("Failed to join on the bhm background thread.");
|
||||||
.expect("Failed to join on the BHM background thread.");
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point, there are no active pipelines,
|
// At this point, there are no active pipelines,
|
||||||
|
@ -3053,6 +3069,11 @@ where
|
||||||
.remove(&browsing_context.bc_group_id);
|
.remove(&browsing_context.bc_group_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: In single-process mode,
|
||||||
|
// if the webview is manually closed, we drop the join handle without joining on it.
|
||||||
|
// It is unlikely the thread will still run when the constellation shuts-down.
|
||||||
|
self.script_join_handles.remove(&webview_id);
|
||||||
|
|
||||||
debug!("{webview_id}: Closed");
|
debug!("{webview_id}: Closed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -209,6 +209,7 @@ pub struct NewPipeline {
|
||||||
pub pipeline: Pipeline,
|
pub pipeline: Pipeline,
|
||||||
pub bhm_control_chan: Option<IpcSender<BackgroundHangMonitorControlMsg>>,
|
pub bhm_control_chan: Option<IpcSender<BackgroundHangMonitorControlMsg>>,
|
||||||
pub lifeline: Option<(IpcReceiver<()>, Process)>,
|
pub lifeline: Option<(IpcReceiver<()>, Process)>,
|
||||||
|
pub join_handle: Option<JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pipeline {
|
impl Pipeline {
|
||||||
|
@ -218,7 +219,7 @@ impl Pipeline {
|
||||||
) -> Result<NewPipeline, Error> {
|
) -> Result<NewPipeline, Error> {
|
||||||
// Note: we allow channel creation to panic, since recovering from this
|
// Note: we allow channel creation to panic, since recovering from this
|
||||||
// probably requires a general low-memory strategy.
|
// probably requires a general low-memory strategy.
|
||||||
let (script_chan, (bhm_control_chan, lifeline)) = match state.event_loop {
|
let (script_chan, (bhm_control_chan, lifeline, join_handle)) = match state.event_loop {
|
||||||
Some(script_chan) => {
|
Some(script_chan) => {
|
||||||
let new_layout_info = NewLayoutInfo {
|
let new_layout_info = NewLayoutInfo {
|
||||||
parent_info: state.parent_pipeline_id,
|
parent_info: state.parent_pipeline_id,
|
||||||
|
@ -235,7 +236,7 @@ impl Pipeline {
|
||||||
{
|
{
|
||||||
warn!("Sending to script during pipeline creation failed ({})", e);
|
warn!("Sending to script during pipeline creation failed ({})", e);
|
||||||
}
|
}
|
||||||
(script_chan, (None, None))
|
(script_chan, (None, None, None))
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
let (script_chan, script_port) = ipc::channel().expect("Pipeline script chan");
|
let (script_chan, script_port) = ipc::channel().expect("Pipeline script chan");
|
||||||
|
@ -315,18 +316,18 @@ impl Pipeline {
|
||||||
ipc::channel().expect("Failed to create lifeline channel");
|
ipc::channel().expect("Failed to create lifeline channel");
|
||||||
unprivileged_pipeline_content.lifeline_sender = Some(sender);
|
unprivileged_pipeline_content.lifeline_sender = Some(sender);
|
||||||
let process = unprivileged_pipeline_content.spawn_multiprocess()?;
|
let process = unprivileged_pipeline_content.spawn_multiprocess()?;
|
||||||
(Some(bhm_control_chan), Some((receiver, process)))
|
(Some(bhm_control_chan), Some((receiver, process)), None)
|
||||||
} else {
|
} else {
|
||||||
// Should not be None in single-process mode.
|
// Should not be None in single-process mode.
|
||||||
let register = state
|
let register = state
|
||||||
.background_monitor_register
|
.background_monitor_register
|
||||||
.expect("Couldn't start content, no background monitor has been initiated");
|
.expect("Couldn't start content, no background monitor has been initiated");
|
||||||
unprivileged_pipeline_content.start_all::<STF>(
|
let join_handle = unprivileged_pipeline_content.start_all::<STF>(
|
||||||
false,
|
false,
|
||||||
state.layout_factory,
|
state.layout_factory,
|
||||||
register,
|
register,
|
||||||
);
|
);
|
||||||
(None, None)
|
(None, None, Some(join_handle))
|
||||||
};
|
};
|
||||||
|
|
||||||
(EventLoop::new(script_chan), multiprocess_data)
|
(EventLoop::new(script_chan), multiprocess_data)
|
||||||
|
@ -347,6 +348,7 @@ impl Pipeline {
|
||||||
pipeline,
|
pipeline,
|
||||||
bhm_control_chan,
|
bhm_control_chan,
|
||||||
lifeline,
|
lifeline,
|
||||||
|
join_handle,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -501,7 +503,7 @@ impl UnprivilegedPipelineContent {
|
||||||
wait_for_completion: bool,
|
wait_for_completion: bool,
|
||||||
layout_factory: Arc<dyn LayoutFactory>,
|
layout_factory: Arc<dyn LayoutFactory>,
|
||||||
background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
|
background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
|
||||||
) {
|
) -> JoinHandle<()> {
|
||||||
// Setup pipeline-namespace-installing for all threads in this process.
|
// Setup pipeline-namespace-installing for all threads in this process.
|
||||||
// Idempotent in single-process mode.
|
// Idempotent in single-process mode.
|
||||||
PipelineNamespace::set_installer_sender(self.namespace_request_sender);
|
PipelineNamespace::set_installer_sender(self.namespace_request_sender);
|
||||||
|
@ -511,7 +513,7 @@ impl UnprivilegedPipelineContent {
|
||||||
self.rippy_data,
|
self.rippy_data,
|
||||||
));
|
));
|
||||||
let (content_process_shutdown_chan, content_process_shutdown_port) = unbounded();
|
let (content_process_shutdown_chan, content_process_shutdown_port) = unbounded();
|
||||||
STF::create(
|
let join_handle = STF::create(
|
||||||
InitialScriptState {
|
InitialScriptState {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
browsing_context_id: self.browsing_context_id,
|
browsing_context_id: self.browsing_context_id,
|
||||||
|
@ -551,6 +553,8 @@ impl UnprivilegedPipelineContent {
|
||||||
Err(_) => error!("Script-thread shut-down unexpectedly"),
|
Err(_) => error!("Script-thread shut-down unexpectedly"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
join_handle
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_multiprocess(self) -> Result<Process, Error> {
|
pub fn spawn_multiprocess(self) -> Result<Process, Error> {
|
||||||
|
|
|
@ -25,7 +25,7 @@ use std::rc::Rc;
|
||||||
use std::result::Result;
|
use std::result::Result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::thread;
|
use std::thread::{self, JoinHandle};
|
||||||
use std::time::{Duration, Instant, SystemTime};
|
use std::time::{Duration, Instant, SystemTime};
|
||||||
|
|
||||||
use background_hang_monitor_api::{
|
use background_hang_monitor_api::{
|
||||||
|
@ -412,7 +412,7 @@ impl ScriptThreadFactory for ScriptThread {
|
||||||
layout_factory: Arc<dyn LayoutFactory>,
|
layout_factory: Arc<dyn LayoutFactory>,
|
||||||
system_font_service: Arc<SystemFontServiceProxy>,
|
system_font_service: Arc<SystemFontServiceProxy>,
|
||||||
load_data: LoadData,
|
load_data: LoadData,
|
||||||
) {
|
) -> JoinHandle<()> {
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
.name(format!("Script{:?}", state.id))
|
.name(format!("Script{:?}", state.id))
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
|
@ -462,7 +462,7 @@ impl ScriptThreadFactory for ScriptThread {
|
||||||
// This must always be the very last operation performed before the thread completes
|
// This must always be the very last operation performed before the thread completes
|
||||||
failsafe.neuter();
|
failsafe.neuter();
|
||||||
})
|
})
|
||||||
.expect("Thread spawning failed");
|
.expect("Thread spawning failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1265,7 +1265,7 @@ pub fn run_content_process(token: String) {
|
||||||
|
|
||||||
content.register_system_memory_reporter();
|
content.register_system_memory_reporter();
|
||||||
|
|
||||||
content.start_all::<script::ScriptThread>(
|
let script_join_handle = content.start_all::<script::ScriptThread>(
|
||||||
true,
|
true,
|
||||||
layout_factory,
|
layout_factory,
|
||||||
background_hang_monitor_register,
|
background_hang_monitor_register,
|
||||||
|
@ -1274,7 +1274,10 @@ pub fn run_content_process(token: String) {
|
||||||
// Since wait_for_completion is true,
|
// Since wait_for_completion is true,
|
||||||
// here we know that the script-thread
|
// here we know that the script-thread
|
||||||
// will exit(or already has),
|
// will exit(or already has),
|
||||||
// and so we can join on the BHM worker thread.
|
// and so we can join first on the script, and then on the BHM worker, threads.
|
||||||
|
script_join_handle
|
||||||
|
.join()
|
||||||
|
.expect("Failed to join on the script thread.");
|
||||||
join_handle
|
join_handle
|
||||||
.join()
|
.join()
|
||||||
.expect("Failed to join on the BHM background thread.");
|
.expect("Failed to join on the BHM background thread.");
|
||||||
|
|
|
@ -15,6 +15,7 @@ use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicIsize, AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicIsize, AtomicU64, Ordering};
|
||||||
|
use std::thread::JoinHandle;
|
||||||
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use atomic_refcell::AtomicRefCell;
|
use atomic_refcell::AtomicRefCell;
|
||||||
|
@ -305,7 +306,7 @@ pub trait ScriptThreadFactory {
|
||||||
layout_factory: Arc<dyn LayoutFactory>,
|
layout_factory: Arc<dyn LayoutFactory>,
|
||||||
system_font_service: Arc<SystemFontServiceProxy>,
|
system_font_service: Arc<SystemFontServiceProxy>,
|
||||||
load_data: LoadData,
|
load_data: LoadData,
|
||||||
);
|
) -> JoinHandle<()>;
|
||||||
}
|
}
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct OffsetParentResponse {
|
pub struct OffsetParentResponse {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue