mirror of
https://github.com/openai/codex.git
synced 2026-10-24 18:18:41 +00:00
## Why On Windows, background terminals could stay visible after their shell process had already exited. The elevated runner waits for the PTY output reader to reach EOF before it sends the final exit message, but the ConPTY helper was reducing ownership down to raw handles too early. That left the pseudoconsole's borrowed pipe handles alive past teardown, so EOF never propagated and the session stayed `running`. ## What changed - change `utils/pty/src/win/conpty.rs` to hand off owned ConPTY resources instead of leaking only raw handles - make `windows-sandbox-rs/src/conpty/mod.rs` keep the pseudoconsole owner and the backing pipe handles together until teardown - update the elevated runner and the legacy unified-exec backend to keep that `ConptyInstance` alive, take only the specific pipe handles they need, and drop the owner at teardown instead of trying to close a detached pseudoconsole handle later ## Testing - desktop app in `Auto-review`: 11 x `cmd /c "ping -n 3 google.com"` all exited cleanly and did not accumulate in the UI - desktop app in `Auto-review`: 5 x `cmd /c "ping -n 30 google.com"` appeared in the UI and drained back out on their own
193 lines
5.4 KiB
Rust
193 lines
5.4 KiB
Rust
#![allow(clippy::unwrap_used)]
|
|
|
|
// This file is copied from https://github.com/wezterm/wezterm (MIT license).
|
|
// Copyright (c) 2018-Present Wez Furlong
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
|
|
use crate::win::psuedocon::PsuedoCon;
|
|
use anyhow::Error;
|
|
use filedescriptor::FileDescriptor;
|
|
use filedescriptor::Pipe;
|
|
use portable_pty::Child;
|
|
use portable_pty::MasterPty;
|
|
use portable_pty::PtyPair;
|
|
use portable_pty::PtySize;
|
|
use portable_pty::PtySystem;
|
|
use portable_pty::SlavePty;
|
|
use portable_pty::cmdbuilder::CommandBuilder;
|
|
use std::mem::ManuallyDrop;
|
|
use std::os::windows::io::RawHandle;
|
|
use std::ptr;
|
|
use std::sync::Arc;
|
|
use std::sync::Mutex;
|
|
use winapi::um::wincon::COORD;
|
|
|
|
#[derive(Default)]
|
|
pub struct ConPtySystem {}
|
|
|
|
fn create_conpty_handles(
|
|
size: PtySize,
|
|
) -> anyhow::Result<(PsuedoCon, FileDescriptor, FileDescriptor)> {
|
|
let stdin = Pipe::new()?;
|
|
let stdout = Pipe::new()?;
|
|
|
|
let con = PsuedoCon::new(
|
|
COORD {
|
|
X: size.cols as i16,
|
|
Y: size.rows as i16,
|
|
},
|
|
stdin.read,
|
|
stdout.write,
|
|
)?;
|
|
|
|
Ok((con, stdin.write, stdout.read))
|
|
}
|
|
|
|
pub struct RawConPty {
|
|
con: PsuedoCon,
|
|
input_write: FileDescriptor,
|
|
output_read: FileDescriptor,
|
|
}
|
|
|
|
impl RawConPty {
|
|
pub fn new(cols: i16, rows: i16) -> anyhow::Result<Self> {
|
|
let (con, input_write, output_read) = create_conpty_handles(PtySize {
|
|
rows: rows as u16,
|
|
cols: cols as u16,
|
|
pixel_width: 0,
|
|
pixel_height: 0,
|
|
})?;
|
|
Ok(Self {
|
|
con,
|
|
input_write,
|
|
output_read,
|
|
})
|
|
}
|
|
|
|
pub fn pseudoconsole_handle(&self) -> RawHandle {
|
|
self.con.raw_handle()
|
|
}
|
|
|
|
pub fn into_handles(self) -> (PsuedoCon, FileDescriptor, FileDescriptor) {
|
|
let me = ManuallyDrop::new(self);
|
|
unsafe {
|
|
(
|
|
ptr::read(&me.con),
|
|
ptr::read(&me.input_write),
|
|
ptr::read(&me.output_read),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PtySystem for ConPtySystem {
|
|
fn openpty(&self, size: PtySize) -> anyhow::Result<PtyPair> {
|
|
let (con, writable, readable) = create_conpty_handles(size)?;
|
|
|
|
let master = ConPtyMasterPty {
|
|
inner: Arc::new(Mutex::new(Inner {
|
|
con,
|
|
readable,
|
|
writable: Some(writable),
|
|
size,
|
|
})),
|
|
};
|
|
|
|
let slave = ConPtySlavePty {
|
|
inner: master.inner.clone(),
|
|
};
|
|
|
|
Ok(PtyPair {
|
|
master: Box::new(master),
|
|
slave: Box::new(slave),
|
|
})
|
|
}
|
|
}
|
|
|
|
struct Inner {
|
|
con: PsuedoCon,
|
|
readable: FileDescriptor,
|
|
writable: Option<FileDescriptor>,
|
|
size: PtySize,
|
|
}
|
|
|
|
impl Inner {
|
|
pub fn resize(
|
|
&mut self,
|
|
num_rows: u16,
|
|
num_cols: u16,
|
|
pixel_width: u16,
|
|
pixel_height: u16,
|
|
) -> Result<(), Error> {
|
|
self.con.resize(COORD {
|
|
X: num_cols as i16,
|
|
Y: num_rows as i16,
|
|
})?;
|
|
self.size = PtySize {
|
|
rows: num_rows,
|
|
cols: num_cols,
|
|
pixel_width,
|
|
pixel_height,
|
|
};
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct ConPtyMasterPty {
|
|
inner: Arc<Mutex<Inner>>,
|
|
}
|
|
|
|
pub struct ConPtySlavePty {
|
|
inner: Arc<Mutex<Inner>>,
|
|
}
|
|
|
|
impl MasterPty for ConPtyMasterPty {
|
|
fn resize(&self, size: PtySize) -> anyhow::Result<()> {
|
|
let mut inner = self.inner.lock().unwrap();
|
|
inner.resize(size.rows, size.cols, size.pixel_width, size.pixel_height)
|
|
}
|
|
|
|
fn get_size(&self) -> Result<PtySize, Error> {
|
|
let inner = self.inner.lock().unwrap();
|
|
Ok(inner.size)
|
|
}
|
|
|
|
fn try_clone_reader(&self) -> anyhow::Result<Box<dyn std::io::Read + Send>> {
|
|
Ok(Box::new(self.inner.lock().unwrap().readable.try_clone()?))
|
|
}
|
|
|
|
fn take_writer(&self) -> anyhow::Result<Box<dyn std::io::Write + Send>> {
|
|
Ok(Box::new(
|
|
self.inner
|
|
.lock()
|
|
.unwrap()
|
|
.writable
|
|
.take()
|
|
.ok_or_else(|| anyhow::anyhow!("writer already taken"))?,
|
|
))
|
|
}
|
|
}
|
|
|
|
impl SlavePty for ConPtySlavePty {
|
|
fn spawn_command(&self, cmd: CommandBuilder) -> anyhow::Result<Box<dyn Child + Send + Sync>> {
|
|
let inner = self.inner.lock().unwrap();
|
|
let child = inner.con.spawn_command(cmd)?;
|
|
Ok(Box::new(child))
|
|
}
|
|
}
|