Serve concurrent terminal sessions with exact teardown

This commit is contained in:
ms2k 2026-07-18 02:51:19 +09:00
commit 254bb37764

View file

@ -302,6 +302,23 @@ fn run_session_terminal_matrix() -> Result<(), MatrixError> {
.store
.apply(&[DomainStoreMutation::PutSession(session.clone())])
.map_err(failure)?;
let second_session_id = SessionId::new([41; 32]);
let mut second_session = Session {
generation_id: GenerationId::new(7),
session_id: second_session_id,
cli_id: ClientId::new([42; 32]),
session_anchor_id: SessionAnchorId::new([43; 32]),
session_phase: SessionPhase::Registering,
};
fixture
.store
.apply(&[DomainStoreMutation::PutSession(second_session.clone())])
.map_err(failure)?;
second_session.session_phase = SessionPhase::Attached;
fixture
.store
.apply(&[DomainStoreMutation::PutSession(second_session)])
.map_err(failure)?;
session.session_phase = SessionPhase::Attached;
fixture
.store
@ -317,6 +334,9 @@ fn run_session_terminal_matrix() -> Result<(), MatrixError> {
let count = workspace.join("session-count");
let finish = workspace.join("session-finish");
let signals = workspace.join("session-signals");
let second_count = workspace.join("session-second-count");
let second_finish = workspace.join("session-second-finish");
let second_signals = workspace.join("session-second-signals");
let line = "x".repeat(1024);
let script = format!(
"trap 'echo INT >> /workspace/session-signals' INT; \
@ -326,6 +346,15 @@ fn run_session_terminal_matrix() -> Result<(), MatrixError> {
n=0; while test ! -e /workspace/session-finish; do \
echo '{line}'; n=$((n+1)); echo $n > /workspace/session-count; done; exit 23"
);
let second_line = "y".repeat(1024);
let second_script = format!(
"trap 'echo INT >> /workspace/session-second-signals' INT; \
trap 'echo QUIT >> /workspace/session-second-signals' QUIT; \
trap 'echo TSTP >> /workspace/session-second-signals' TSTP; \
trap 'echo WINCH >> /workspace/session-second-signals' WINCH; \
n=0; while test ! -e /workspace/session-second-finish; do \
echo '{second_line}'; n=$((n+1)); echo $n > /workspace/session-second-count; done; exit 42"
);
let request = TerminalAcquisition {
domain_id: fixture.configuration.domain_id(),
generation_id: GenerationId::new(7),
@ -344,7 +373,20 @@ fn run_session_terminal_matrix() -> Result<(), MatrixError> {
Arc::new(NoActivePrompt),
);
engine.acquire(&request).map_err(failure)?;
engine
.acquire(&TerminalAcquisition {
session_id: second_session_id,
internal_pid_file: runtime_domain.join("session-second-internal.pid"),
argv: vec![
b"/bin/sh".to_vec(),
b"-c".to_vec(),
second_script.into_bytes(),
],
..request.clone()
})
.map_err(failure)?;
wait_for_path(&count)?;
wait_for_path(&second_count)?;
let running = fixture
.store
.snapshot()
@ -384,6 +426,9 @@ fn run_session_terminal_matrix() -> Result<(), MatrixError> {
.map_err(failure)?;
}
wait_for_signal_lines(&mut engine, session_id, &signals, 4)?;
if second_signals.exists() {
return Err(failure("terminal signals crossed Session IDs"));
}
let mut forwarded_before_prompt = 0_usize;
for _ in 0..2_000 {
let messages = engine
@ -417,11 +462,21 @@ fn run_session_terminal_matrix() -> Result<(), MatrixError> {
.map_err(failure)?;
thread::sleep(Duration::from_millis(300));
let before = fs::read_to_string(&count)?;
thread::sleep(Duration::from_millis(300));
let second_before = fs::read_to_string(&second_count)?;
for _ in 0..60 {
engine
.poll(second_session_id, GenerationId::new(7))
.map_err(failure)?;
thread::sleep(Duration::from_millis(5));
}
let after = fs::read_to_string(&count)?;
let second_after = fs::read_to_string(&second_count)?;
if before != after {
return Err(failure("prompt route did not apply PTY backpressure"));
}
if second_before == second_after {
return Err(failure("one Prompt paused another Session Terminal"));
}
session.session_phase = SessionPhase::Attached;
fixture
.store
@ -466,11 +521,49 @@ fn run_session_terminal_matrix() -> Result<(), MatrixError> {
}
if exit.and_then(lazaret_core::records::SessionExit::exit_code) != Some(23)
|| engine.contains(session_id)
|| !engine.contains(second_session_id)
{
return Err(failure(
"Session Terminal exit or no-reattachment cleanup failed",
));
}
let second_before_first_exit = fs::read_to_string(&second_count)?;
for _ in 0..200 {
engine
.poll(second_session_id, GenerationId::new(7))
.map_err(failure)?;
if fs::read_to_string(&second_count).ok().as_deref()
!= Some(second_before_first_exit.as_str())
{
break;
}
thread::sleep(Duration::from_millis(5));
}
if fs::read_to_string(&second_count).ok().as_deref() == Some(second_before_first_exit.as_str())
{
return Err(failure("first Session exit paused the second Session"));
}
fs::write(&second_finish, b"finish")?;
let mut second_exit = None;
for _ in 0..2_000 {
for message in engine
.poll(second_session_id, GenerationId::new(7))
.map_err(failure)?
{
if let SessionTerminalMessage::Exit(outcome) = message {
second_exit = Some(outcome);
}
}
if second_exit.is_some() {
break;
}
thread::sleep(Duration::from_millis(5));
}
if second_exit.and_then(lazaret_core::records::SessionExit::exit_code) != Some(42)
|| engine.contains(second_session_id)
{
return Err(failure("second Session exit routing or cleanup failed"));
}
runtime
.stop(&SandboxCoordinator::new(&fixture.store))
.map_err(failure)