fix(host): 跨进程启动串行化可继承管道窗口
This commit is contained in:
@@ -325,7 +325,11 @@ impl CoreSupervisor {
|
|||||||
use std::os::windows::process::CommandExt;
|
use std::os::windows::process::CommandExt;
|
||||||
command.creation_flags(0x08000000); // CREATE_NO_WINDOW
|
command.creation_flags(0x08000000); // CREATE_NO_WINDOW
|
||||||
}
|
}
|
||||||
let child = command.group_spawn().map_err(|_| "CORE_SPAWN_FAILED")?;
|
let child = {
|
||||||
|
#[cfg(windows)]
|
||||||
|
let _creation = crate::process_creation::lock()?;
|
||||||
|
command.group_spawn().map_err(|_| "CORE_SPAWN_FAILED")?
|
||||||
|
};
|
||||||
let mut session = Session {
|
let mut session = Session {
|
||||||
child,
|
child,
|
||||||
lifetime: Arc::new(Mutex::new(None)),
|
lifetime: Arc::new(Mutex::new(None)),
|
||||||
|
|||||||
@@ -148,6 +148,9 @@ impl<'a> Suspended<'a> {
|
|||||||
startup.lpAttributeList = attributes.buffer.as_mut_ptr().cast();
|
startup.lpAttributeList = attributes.buffer.as_mut_ptr().cast();
|
||||||
// Keep both the handle array and the owning pipe ends alive across
|
// Keep both the handle array and the owning pipe ends alive across
|
||||||
// CreateProcessW. No arbitrary inheritable Host handle is admitted.
|
// CreateProcessW. No arbitrary inheritable Host handle is admitted.
|
||||||
|
let io = io
|
||||||
|
.map(crate::extension_stdio::ChildIo::inherit)
|
||||||
|
.transpose()?;
|
||||||
let inherited = io.as_ref().map(|value| value.handles());
|
let inherited = io.as_ref().map(|value| value.handles());
|
||||||
if let Some(handles) = &inherited {
|
if let Some(handles) = &inherited {
|
||||||
if unsafe {
|
if unsafe {
|
||||||
|
|||||||
@@ -46,13 +46,6 @@ impl ChildIo {
|
|||||||
output,
|
output,
|
||||||
error,
|
error,
|
||||||
};
|
};
|
||||||
for handle in child.handles() {
|
|
||||||
if unsafe { SetHandleInformation(handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) }
|
|
||||||
== 0
|
|
||||||
{
|
|
||||||
return Err(HostError::new("EXTENSION_PIPE_CREATE_FAILED"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok((
|
Ok((
|
||||||
child,
|
child,
|
||||||
HostIo {
|
HostIo {
|
||||||
@@ -62,6 +55,23 @@ impl ChildIo {
|
|||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
/// Own both the pipe ends and the launch lock. Field drop order closes all
|
||||||
|
/// inheritable ends before allowing a competing Host launch to proceed.
|
||||||
|
pub(crate) fn inherit(self) -> Result<InheritedIo> {
|
||||||
|
let lock = crate::process_creation::lock().map_err(HostError::new)?;
|
||||||
|
let guarded = InheritedIo {
|
||||||
|
child: self,
|
||||||
|
_creation: lock,
|
||||||
|
};
|
||||||
|
for handle in guarded.handles() {
|
||||||
|
if unsafe { SetHandleInformation(handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) }
|
||||||
|
== 0
|
||||||
|
{
|
||||||
|
return Err(HostError::new("EXTENSION_PIPE_CREATE_FAILED"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(guarded)
|
||||||
|
}
|
||||||
pub(crate) fn handles(&self) -> [HANDLE; 3] {
|
pub(crate) fn handles(&self) -> [HANDLE; 3] {
|
||||||
[
|
[
|
||||||
self.input.as_raw_handle(),
|
self.input.as_raw_handle(),
|
||||||
@@ -71,6 +81,16 @@ impl ChildIo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct InheritedIo {
|
||||||
|
child: ChildIo,
|
||||||
|
_creation: std::sync::MutexGuard<'static, ()>,
|
||||||
|
}
|
||||||
|
impl InheritedIo {
|
||||||
|
pub(crate) fn handles(&self) -> [HANDLE; 3] {
|
||||||
|
self.child.handles()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// NDJSON maximum excludes the line terminator. A protocol/IO error poisons
|
/// NDJSON maximum excludes the line terminator. A protocol/IO error poisons
|
||||||
/// the decoder; the runtime must terminate the instance and close its pipes.
|
/// the decoder; the runtime must terminate the instance and close its pipes.
|
||||||
/// This synchronous decoder needs a separate IO cancellation/deadline owner.
|
/// This synchronous decoder needs a separate IO cancellation/deadline owner.
|
||||||
@@ -148,7 +168,7 @@ pub fn write_frame(writer: &mut impl std::io::Write, frame: &[u8]) -> Result<()>
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
#[test]
|
#[test]
|
||||||
fn only_child_ends_are_inheritable_and_all_streams_are_distinct() {
|
fn pipes_are_private_until_the_serialized_creation_window() {
|
||||||
let (child, host) = ChildIo::create().unwrap();
|
let (child, host) = ChildIo::create().unwrap();
|
||||||
let ends = child
|
let ends = child
|
||||||
.handles()
|
.handles()
|
||||||
@@ -159,12 +179,39 @@ mod tests {
|
|||||||
host.error.as_raw_handle(),
|
host.error.as_raw_handle(),
|
||||||
])
|
])
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
for handle in &ends {
|
||||||
|
let mut flags = 0;
|
||||||
|
assert_ne!(unsafe { GetHandleInformation(*handle, &mut flags) }, 0);
|
||||||
|
assert_eq!(flags & HANDLE_FLAG_INHERIT, 0);
|
||||||
|
}
|
||||||
|
let child = child.inherit().unwrap();
|
||||||
for (index, handle) in ends.iter().enumerate() {
|
for (index, handle) in ends.iter().enumerate() {
|
||||||
let mut flags = 0;
|
let mut flags = 0;
|
||||||
assert_ne!(unsafe { GetHandleInformation(*handle, &mut flags) }, 0);
|
assert_ne!(unsafe { GetHandleInformation(*handle, &mut flags) }, 0);
|
||||||
assert_eq!(flags & HANDLE_FLAG_INHERIT != 0, index < 3);
|
assert_eq!(flags & HANDLE_FLAG_INHERIT != 0, index < 3);
|
||||||
assert!(!ends[..index].contains(handle));
|
assert!(!ends[..index].contains(handle));
|
||||||
}
|
}
|
||||||
|
let (started_tx, started_rx) = std::sync::mpsc::channel();
|
||||||
|
let (acquired_tx, acquired_rx) = std::sync::mpsc::channel();
|
||||||
|
let worker = std::thread::spawn(move || {
|
||||||
|
started_tx.send(()).unwrap();
|
||||||
|
let _creation = crate::process_creation::lock().unwrap();
|
||||||
|
acquired_tx.send(()).unwrap();
|
||||||
|
});
|
||||||
|
started_rx.recv().unwrap();
|
||||||
|
assert!(acquired_rx
|
||||||
|
.recv_timeout(std::time::Duration::from_millis(30))
|
||||||
|
.is_err());
|
||||||
|
drop(child);
|
||||||
|
acquired_rx
|
||||||
|
.recv_timeout(std::time::Duration::from_secs(5))
|
||||||
|
.unwrap();
|
||||||
|
worker.join().unwrap();
|
||||||
|
// The last writer was closed before the lock was released; no child
|
||||||
|
// process was launched in this ownership test, so Host sees EOF.
|
||||||
|
use std::io::Read;
|
||||||
|
let mut output = host.output;
|
||||||
|
assert_eq!(output.read(&mut [0; 1]).unwrap(), 0);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn frames_are_bounded_across_fragmentation_and_poison_after_errors() {
|
fn frames_are_bounded_across_fragmentation_and_poison_after_errors() {
|
||||||
|
|||||||
@@ -96,3 +96,6 @@ pub mod extension_call_authorization;
|
|||||||
|
|
||||||
#[cfg(all(windows, feature = "desktop"))]
|
#[cfg(all(windows, feature = "desktop"))]
|
||||||
pub mod extension_instance;
|
pub mod extension_instance;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod process_creation;
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
//! Coordinate Host-controlled Windows launches while inheritable handles exist.
|
||||||
|
//! This does not serialize foreign libraries that bypass this Host boundary.
|
||||||
|
use std::sync::{Mutex, MutexGuard};
|
||||||
|
static CREATION: Mutex<()> = Mutex::new(());
|
||||||
|
|
||||||
|
pub(crate) fn lock() -> Result<MutexGuard<'static, ()>, &'static str> {
|
||||||
|
CREATION.lock().map_err(|_| "PROCESS_CREATION_LOCK_FAILED")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user