conpty_oxide/blocking/mod.rs
1// SPDX-FileCopyrightText: 2026 conpty-oxide contributors <https://github.com/P4suta/conpty-oxide/graphs/contributors>
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5//! Blocking pseudoconsole sessions.
6//!
7//! [`Command::spawn`] creates a managed [`Session`]. Choose one of three paths:
8//!
9//! - [`Session::wait`] when output is unnecessary;
10//! - [`Session::collect_output`] to retain the remaining raw VT bytes;
11//! - [`Session::into_parts`] for interactive or externally coordinated I/O.
12//!
13//! All three remain managed and root-bounded. `into_parts` separates ownership;
14//! it does not detach the child or its descendants.
15//!
16//! # Service output concurrently
17//!
18//! [Microsoft's ConPTY guidance](https://learn.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session)
19//! recommends servicing conin and conout on separate threads. Once conout's pipe
20//! buffer fills, the console host and its client can stop making progress. A
21//! caller that blocks in [`Child::wait`] without another thread draining output
22//! can therefore deadlock.
23//!
24//! [`Session::wait`] and [`Session::collect_output`] perform the root wait and
25//! output drain together. With [`Session::into_parts`], move [`OwnedReadHalf`]
26//! to its own reader thread while retaining input, child, and resize/clear
27//! control elsewhere.
28//!
29//! # Input shutdown ends the session
30//!
31//! Dropping [`OwnedWriteHalf`] is not the console equivalent of closing a
32//! child's stdin. It closes conin and requests pseudoconsole teardown, which
33//! sends a close event to attached clients. Keep input alive until the program
34//! exits through its own protocol.
35//!
36//! # Output and root completion
37//!
38//! Conout is one raw UTF-8/VT byte stream; stdout and stderr are not separate,
39//! and this crate does not parse or decode it. Decode across reads because a
40//! UTF-8 code point or VT sequence may span chunks.
41//!
42//! Root exit bounds the managed session. A registered wait saves the root's
43//! real status and terminates remaining Job members. Released backends then
44//! reach EOF naturally. Legacy backends grant a drain grace and request close
45//! from a dedicated worker so the same output stream reaches EOF without
46//! running a potentially blocking close on the reader thread.
47//!
48//! # Examples
49//!
50//! ```no_run
51//! use conpty_oxide::blocking::Command;
52//!
53//! # fn main() -> conpty_oxide::Result<()> {
54//! let status = Command::new("cmd.exe")
55//! .args(["/c", "exit", "0"])
56//! .spawn()?
57//! .wait()?;
58//! assert!(status.success());
59//! # Ok(())
60//! # }
61//! ```
62mod builder;
63mod command;
64mod pty;
65mod session;
66
67pub use command::{Child, Command};
68pub use pty::{OwnedReadHalf, OwnedWriteHalf};
69pub use session::{Session, SessionParts};
70
71#[cfg(test)]
72use std::io::{self, Read, Write};
73#[cfg(test)]
74use std::os::windows::io::{AsHandle, AsRawHandle};
75
76#[cfg(test)]
77use crate::backend::BackendKind;
78#[cfg(test)]
79use crate::{ConPtyBackend, ExitStatus, Size};
80
81#[cfg(test)]
82#[path = "mod_tests.rs"]
83mod tests;
84
85/// Shared control types keep one canonical path at the crate root:
86///
87/// ```compile_fail
88/// use conpty_oxide::blocking::PtyController;
89/// ```
90#[cfg(doctest)]
91mod api_boundary {}