Stop terminal grace polling after authority loss

This commit is contained in:
ms2k 2026-07-17 06:32:21 +09:00
commit 0f4845d4f8

View file

@ -63,6 +63,12 @@ enum GuestReleaseAuthority {
HostOnly,
}
enum GuestWaitOutcome {
Absent,
TimedOut,
Revoked,
}
#[derive(Debug)]
pub enum TerminalError {
Io(io::Error),
@ -788,9 +794,18 @@ fn release_physical(
failure.get_or_insert(error);
break;
}
if let Err(error) = wait_until(SIGNAL_GRACE, || process.group_absent()) {
failure.get_or_insert(error);
break;
match wait_for_group_absence(process, authorize_guest) {
Ok(GuestWaitOutcome::Absent) => break,
Ok(GuestWaitOutcome::TimedOut) => {}
Ok(GuestWaitOutcome::Revoked) => {
guest_authorized = false;
break;
}
Err(error) => {
failure.get_or_insert(error);
guest_authorized = false;
break;
}
}
}
}
@ -812,29 +827,7 @@ fn release_physical(
None
}
};
let mut output = Vec::new();
if connected {
let deadline = Instant::now() + DRAIN_DEADLINE;
let mut bytes = [0_u8; MAX_OUTPUT_BYTES];
let mut drained = 0;
while Instant::now() < deadline && drained < MAX_DRAIN_BYTES {
match process.read_output(&mut bytes) {
Ok(0) => break,
Ok(length) => {
let length = length.min(MAX_DRAIN_BYTES - drained);
drained += length;
output.push(SessionTerminalMessage::Output(TerminalOutput {
session_id,
bytes: bytes[..length].to_vec(),
}));
}
Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
std::thread::sleep(POLL_INTERVAL);
}
Err(_) => break,
}
}
}
let output = drain_output(process, session_id, connected);
process.close_pty();
if guest_authorized && authorize_guest() {
match process.group_absent() {
@ -860,6 +853,38 @@ fn release_physical(
))
}
fn drain_output(
process: &mut dyn SessionTerminalProcess,
session_id: SessionId,
connected: bool,
) -> Vec<SessionTerminalMessage> {
let mut output = Vec::new();
if !connected {
return output;
}
let deadline = Instant::now() + DRAIN_DEADLINE;
let mut bytes = [0_u8; MAX_OUTPUT_BYTES];
let mut drained = 0;
while Instant::now() < deadline && drained < MAX_DRAIN_BYTES {
match process.read_output(&mut bytes) {
Ok(0) => break,
Ok(length) => {
let length = length.min(MAX_DRAIN_BYTES - drained);
drained += length;
output.push(SessionTerminalMessage::Output(TerminalOutput {
session_id,
bytes: bytes[..length].to_vec(),
}));
}
Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
std::thread::sleep(POLL_INTERVAL);
}
Err(_) => break,
}
}
output
}
fn with_cleanup<T>(original: TerminalError, cleanup: Result<T, TerminalError>) -> TerminalError {
match cleanup {
Ok(_) => original,
@ -870,6 +895,23 @@ fn with_cleanup<T>(original: TerminalError, cleanup: Result<T, TerminalError>) -
}
}
fn wait_for_group_absence(
process: &dyn SessionTerminalProcess,
authorize_guest: &mut dyn FnMut() -> bool,
) -> Result<GuestWaitOutcome, TerminalError> {
let deadline = Instant::now() + SIGNAL_GRACE;
while Instant::now() < deadline {
if !authorize_guest() {
return Ok(GuestWaitOutcome::Revoked);
}
if process.group_absent()? {
return Ok(GuestWaitOutcome::Absent);
}
std::thread::sleep(POLL_INTERVAL);
}
Ok(GuestWaitOutcome::TimedOut)
}
fn flush_input(terminal: &mut ActiveTerminal) -> Result<(), TerminalError> {
while !terminal.pending_input.is_empty() {
let (first, second) = terminal.pending_input.as_slices();
@ -888,20 +930,6 @@ fn flush_input(terminal: &mut ActiveTerminal) -> Result<(), TerminalError> {
Ok(())
}
fn wait_until(
duration: Duration,
mut predicate: impl FnMut() -> Result<bool, TerminalError>,
) -> Result<(), TerminalError> {
let deadline = Instant::now() + duration;
while Instant::now() < deadline {
if predicate()? {
return Ok(());
}
std::thread::sleep(POLL_INTERVAL);
}
Ok(())
}
fn exit_message(
session_id: SessionId,
loss: Option<TerminalLoss>,