mirror of
https://github.com/openai/codex.git
synced 2026-05-02 10:26:45 +00:00
Addresses #13945 The vendored WezTerm ConPTY backend in `codex-rs/utils/pty/src/win/mod.rs` treated `TerminateProcess` return values backwards: nonzero success was handled as failure, and `0` failure was handled as success. This is likely causing a number of bugs reported against Codex running on Windows native where processes are not cleaned up.
179 lines
5.9 KiB
Rust
179 lines
5.9 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.
|
|
|
|
// Local modifications:
|
|
// - Fix Codex bug #13945 in the Windows PTY kill path. The vendored code treated
|
|
// `TerminateProcess`'s nonzero success return as failure and `0` as success,
|
|
// which inverts kill outcomes for both `WinChild::do_kill` and
|
|
// `WinChildKiller::kill`.
|
|
// - This bug still exists in the original WezTerm source as of 2026-03-08, so
|
|
// this is an intentional divergence from upstream.
|
|
|
|
use anyhow::Context as _;
|
|
use filedescriptor::OwnedHandle;
|
|
use portable_pty::Child;
|
|
use portable_pty::ChildKiller;
|
|
use portable_pty::ExitStatus;
|
|
use std::io::Error as IoError;
|
|
use std::io::Result as IoResult;
|
|
use std::os::windows::io::AsRawHandle;
|
|
use std::pin::Pin;
|
|
use std::sync::Mutex;
|
|
use std::task::Context;
|
|
use std::task::Poll;
|
|
use winapi::shared::minwindef::DWORD;
|
|
use winapi::um::minwinbase::STILL_ACTIVE;
|
|
use winapi::um::processthreadsapi::*;
|
|
use winapi::um::synchapi::WaitForSingleObject;
|
|
use winapi::um::winbase::INFINITE;
|
|
|
|
pub mod conpty;
|
|
mod procthreadattr;
|
|
mod psuedocon;
|
|
|
|
pub use conpty::ConPtySystem;
|
|
pub use psuedocon::conpty_supported;
|
|
|
|
#[derive(Debug)]
|
|
pub struct WinChild {
|
|
proc: Mutex<OwnedHandle>,
|
|
}
|
|
|
|
impl WinChild {
|
|
fn is_complete(&mut self) -> IoResult<Option<ExitStatus>> {
|
|
let mut status: DWORD = 0;
|
|
let proc = self.proc.lock().unwrap().try_clone().unwrap();
|
|
let res = unsafe { GetExitCodeProcess(proc.as_raw_handle() as _, &mut status) };
|
|
if res != 0 {
|
|
if status == STILL_ACTIVE {
|
|
Ok(None)
|
|
} else {
|
|
Ok(Some(ExitStatus::with_exit_code(status)))
|
|
}
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
fn do_kill(&mut self) -> IoResult<()> {
|
|
let proc = self.proc.lock().unwrap().try_clone().unwrap();
|
|
let res = unsafe { TerminateProcess(proc.as_raw_handle() as _, 1) };
|
|
// Codex bug #13945: Win32 returns nonzero on success, so only `0` is an error.
|
|
if res == 0 {
|
|
Err(IoError::last_os_error())
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ChildKiller for WinChild {
|
|
fn kill(&mut self) -> IoResult<()> {
|
|
self.do_kill().ok();
|
|
Ok(())
|
|
}
|
|
|
|
fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
|
|
let proc = self.proc.lock().unwrap().try_clone().unwrap();
|
|
Box::new(WinChildKiller { proc })
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct WinChildKiller {
|
|
proc: OwnedHandle,
|
|
}
|
|
|
|
impl ChildKiller for WinChildKiller {
|
|
fn kill(&mut self) -> IoResult<()> {
|
|
let res = unsafe { TerminateProcess(self.proc.as_raw_handle() as _, 1) };
|
|
// Codex bug #13945: Win32 returns nonzero on success, so only `0` is an error.
|
|
if res == 0 {
|
|
Err(IoError::last_os_error())
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
|
|
let proc = self.proc.try_clone().unwrap();
|
|
Box::new(WinChildKiller { proc })
|
|
}
|
|
}
|
|
|
|
impl Child for WinChild {
|
|
fn try_wait(&mut self) -> IoResult<Option<ExitStatus>> {
|
|
self.is_complete()
|
|
}
|
|
|
|
fn wait(&mut self) -> IoResult<ExitStatus> {
|
|
if let Ok(Some(status)) = self.try_wait() {
|
|
return Ok(status);
|
|
}
|
|
let proc = self.proc.lock().unwrap().try_clone().unwrap();
|
|
unsafe {
|
|
WaitForSingleObject(proc.as_raw_handle() as _, INFINITE);
|
|
}
|
|
let mut status: DWORD = 0;
|
|
let res = unsafe { GetExitCodeProcess(proc.as_raw_handle() as _, &mut status) };
|
|
if res != 0 {
|
|
Ok(ExitStatus::with_exit_code(status))
|
|
} else {
|
|
Err(IoError::last_os_error())
|
|
}
|
|
}
|
|
|
|
fn process_id(&self) -> Option<u32> {
|
|
let res = unsafe { GetProcessId(self.proc.lock().unwrap().as_raw_handle() as _) };
|
|
if res == 0 {
|
|
None
|
|
} else {
|
|
Some(res)
|
|
}
|
|
}
|
|
|
|
fn as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle> {
|
|
let proc = self.proc.lock().unwrap();
|
|
Some(proc.as_raw_handle())
|
|
}
|
|
}
|
|
|
|
impl std::future::Future for WinChild {
|
|
type Output = anyhow::Result<ExitStatus>;
|
|
|
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<anyhow::Result<ExitStatus>> {
|
|
match self.is_complete() {
|
|
Ok(Some(status)) => Poll::Ready(Ok(status)),
|
|
Err(err) => Poll::Ready(Err(err).context("Failed to retrieve process exit status")),
|
|
Ok(None) => {
|
|
let proc = self.proc.lock().unwrap().try_clone()?;
|
|
let waker = cx.waker().clone();
|
|
std::thread::spawn(move || {
|
|
unsafe {
|
|
WaitForSingleObject(proc.as_raw_handle() as _, INFINITE);
|
|
}
|
|
waker.wake();
|
|
});
|
|
Poll::Pending
|
|
}
|
|
}
|
|
}
|
|
}
|