Prevent zombie processes in multi-process mode. (#36329)

This introduces a process manager that holds for each process a
"lifeline": this is the receiving end of a ipc channel that is not used
to send anything, but only to monitor the process presence. We turn that
ipc receiver into a crossbeam one to integrate the monitoring into the
constellation run loop. The sender side is made part of the initial
"UnprivilegedContent" data structure sent to the new process, both for
content and for service worker processes.
When a process dies we currently wait() on it to let the OS do a clean
shutdown.

Signed-off-by: webbeef <me@webbeef.org>
This commit is contained in:
webbeef 2025-04-04 12:39:13 -07:00 committed by GitHub
parent c09c31ef85
commit c7a7862574
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 177 additions and 44 deletions

View file

@ -113,7 +113,7 @@ use constellation_traits::{
AnimationTickType, CompositorHitTestResult, ConstellationMsg as FromCompositorMsg, LogEntry,
PaintMetricEvent, ScrollState, TraversalDirection, WindowSizeType,
};
use crossbeam_channel::{Receiver, Sender, select, unbounded};
use crossbeam_channel::{Receiver, Select, Sender, unbounded};
use devtools_traits::{
ChromeToDevtoolsControlMsg, DevtoolsControlMsg, DevtoolsPageInfo, NavigationState,
ScriptToDevtoolsControlMsg,
@ -171,6 +171,7 @@ use crate::browsingcontext::{
};
use crate::event_loop::EventLoop;
use crate::pipeline::{InitialPipelineState, Pipeline};
use crate::process_manager::ProcessManager;
use crate::serviceworker::ServiceWorkerUnprivilegedContent;
use crate::session_history::{
JointSessionHistory, NeedsToReload, SessionHistoryChange, SessionHistoryDiff,
@ -469,6 +470,9 @@ pub struct Constellation<STF, SWF> {
/// User content manager
user_content_manager: UserContentManager,
/// The process manager.
process_manager: ProcessManager,
}
/// State needed to construct a constellation.
@ -735,6 +739,7 @@ where
active_media_session: None,
rippy_data,
user_content_manager: state.user_content_manager,
process_manager: ProcessManager::new(),
};
constellation.run();
@ -996,6 +1001,12 @@ where
);
}
if let Some((lifeline_receiver, process)) = pipeline.lifeline {
let crossbeam_receiver =
route_ipc_receiver_to_new_crossbeam_receiver_preserving_errors(lifeline_receiver);
self.process_manager.add(crossbeam_receiver, process);
}
assert!(!self.pipelines.contains_key(&pipeline_id));
self.pipelines.insert(pipeline_id, pipeline.pipeline);
}
@ -1114,6 +1125,7 @@ where
BackgroundHangMonitor(HangMonitorAlert),
Compositor(FromCompositorMsg),
FromSWManager(SWManagerMsg),
RemoveProcess(usize),
}
// Get one incoming request.
// This is one of the few places where the compositor is
@ -1126,26 +1138,49 @@ where
// produces undefined behaviour, resulting in the destructor
// being called. If this happens, there's not much we can do
// other than panic.
let mut sel = Select::new();
sel.recv(&self.namespace_receiver);
sel.recv(&self.script_receiver);
sel.recv(&self.background_hang_monitor_receiver);
sel.recv(&self.compositor_receiver);
sel.recv(&self.swmanager_receiver);
self.process_manager.register(&mut sel);
let request = {
let oper = sel.select();
let index = oper.index();
#[cfg(feature = "tracing")]
let _span =
tracing::trace_span!("handle_request::select", servo_profiling = true).entered();
select! {
recv(self.namespace_receiver) -> msg => {
msg.expect("Unexpected script channel panic in constellation").map(Request::PipelineNamespace)
}
recv(self.script_receiver) -> msg => {
msg.expect("Unexpected script channel panic in constellation").map(Request::Script)
}
recv(self.background_hang_monitor_receiver) -> msg => {
msg.expect("Unexpected BHM channel panic in constellation").map(Request::BackgroundHangMonitor)
}
recv(self.compositor_receiver) -> msg => {
Ok(Request::Compositor(msg.expect("Unexpected compositor channel panic in constellation")))
}
recv(self.swmanager_receiver) -> msg => {
msg.expect("Unexpected SW channel panic in constellation").map(Request::FromSWManager)
}
match index {
0 => oper
.recv(&self.namespace_receiver)
.expect("Unexpected script channel panic in constellation")
.map(Request::PipelineNamespace),
1 => oper
.recv(&self.script_receiver)
.expect("Unexpected script channel panic in constellation")
.map(Request::Script),
2 => oper
.recv(&self.background_hang_monitor_receiver)
.expect("Unexpected BHM channel panic in constellation")
.map(Request::BackgroundHangMonitor),
3 => Ok(Request::Compositor(
oper.recv(&self.compositor_receiver)
.expect("Unexpected compositor channel panic in constellation"),
)),
4 => oper
.recv(&self.swmanager_receiver)
.expect("Unexpected SW channel panic in constellation")
.map(Request::FromSWManager),
_ => {
// This can only be a error reading on a closed lifeline receiver.
let process_index = index - 5;
let _ = oper.recv(self.process_manager.receiver_at(process_index));
Ok(Request::RemoveProcess(process_index))
},
}
};
@ -1168,6 +1203,7 @@ where
Request::FromSWManager(message) => {
self.handle_request_from_swmanager(message);
},
Request::RemoveProcess(index) => self.process_manager.remove(index),
}
}
@ -2398,13 +2434,24 @@ where
own_sender: own_sender.clone(),
receiver,
};
let content = ServiceWorkerUnprivilegedContent::new(sw_senders, origin);
if opts::get().multiprocess {
if content.spawn_multiprocess().is_err() {
let (sender, receiver) =
ipc::channel().expect("Failed to create lifeline channel for sw");
let content =
ServiceWorkerUnprivilegedContent::new(sw_senders, origin, Some(sender));
if let Ok(process) = content.spawn_multiprocess() {
let crossbeam_receiver =
route_ipc_receiver_to_new_crossbeam_receiver_preserving_errors(
receiver,
);
self.process_manager.add(crossbeam_receiver, process);
} else {
return warn!("Failed to spawn process for SW manager.");
}
} else {
let content = ServiceWorkerUnprivilegedContent::new(sw_senders, origin, None);
content.start::<SWF>();
}
entry.insert(own_sender)