feat: quiesce the VM and unmount windows before native application exit

This commit is contained in:
2026-10-24 01:01:22 +08:00
parent 864e57ce1f
commit 0ad4555922
6 changed files with 383 additions and 26 deletions
+76
View File
@@ -233,6 +233,7 @@ struct Session {
layout_cancels: usize,
paint_cancels: usize,
paint_slices: usize,
exiting: bool,
}
impl std::ops::Deref for Session {
@@ -771,6 +772,9 @@ fn create_window(
"window creation is forbidden during read-only rendering",
));
}
if state.exiting {
return Err(invalid("runtime is exiting; window creation is forbidden"));
}
if state.presentations.len() >= 16 {
return Err(VmError::ExecutionLimit("16 window budget"));
}
@@ -1812,6 +1816,77 @@ fn project_tabs(
}
impl UiBridge {
/// Detach all occurrences without running layout/paint. All windows are
/// already closed before source unmount hooks run, so they cannot remount.
pub fn shutdown(&self, vm: &mut Vm, callbacks: bool) -> Result<(usize, usize, usize)> {
let tasks = self.tasks.quiesce(vm)?;
let mut drivers = Vec::new();
let mut mounts = 0;
let prepared = (|| -> Result<usize> {
let mut state = self
.session
.lock()
.map_err(|_| anyhow::anyhow!("UI session poisoned"))?;
state.exiting = true;
let ids: Vec<_> = state.presentations.keys().copied().collect();
for id in &ids {
let view = state.presentations.get_mut(id).expect("known window");
let desktop = view.desktop;
mounts += view.mounts.active.len();
if callbacks {
if let Some(hooks) = view.mounts.synchronize(
vm,
desktop,
None,
Vec::new(),
&crate::document::Frame::default(),
false,
)? {
drivers.push((*id, hooks));
}
} else {
view.mounts.retire(vm, desktop)?;
}
let window = object(Some(&vm.get_attr(desktop, "window")?))?;
crate::window::retire(vm, window)?;
vm.set_attr(desktop, "pending", Value::Nil)?;
vm.set_attr(desktop, "focus", Value::Nil)?;
view.pending = Some(None);
view.active = None;
view.closing = true;
}
Ok(ids.len())
})();
let mut result = prepared.map(|windows| (windows, mounts, tasks));
for (id, hooks) in drivers {
if result.is_ok() {
let call = (|| -> Result<()> {
self.select_window(id)?;
let value = vm
.call_with_limits(
hooks,
&[],
endor_core::ExecutionLimits {
max_frames: 128,
max_instructions: 1_000_000,
},
)
.map_err(|error| crate::endor_host::runtime_error(vm, error))?;
vm.release_value(value)?;
Ok(())
})();
if let Err(error) = call {
result = Err(error);
}
}
if let Err(error) = vm.release(hooks) {
result = Err(error.into());
}
}
self.select_window(crate::window::MAIN_WINDOW)?;
result
}
pub fn has_cooperative_tasks(&self) -> bool {
self.tasks.has_cooperative_work()
}
@@ -2902,6 +2977,7 @@ pub fn install(vm: &mut Vm, roots: &mut Vec<ObjectRef>) -> Result<UiBridge> {
layout_cancels: 0,
paint_cancels: 0,
paint_slices: 0,
exiting: false,
}));
let mut public_functions = Vec::new();
let mut factories = Vec::new();