fix(tui): terminal escapes ride the writer queue and teardown is bounded
A terminal that stopped draining the pty parked the writer thread inside its tty write while it held the stderr lock; the next inline stderr write from the event loop (mouse toggle, notification, kitty clear) deadlocked the loop, and /quit hung on the unbounded writer join and the teardown writes. EscapeWriter queues out-of-band escapes on the frame writer: the mouse capture toggle, notifications (title, progress, protocol), the auth cursor, gboom keyboard flags, the wrap clipboard request, and the kitty clears (queued per agent view and merged into the frame's post-flush, nested subagent views included). The $EDITOR/$PAGER handoff aborts when the writer does not drain. WriterThread::join_within and the teardown run under a 2 s grace; after a stall fd 2 is silenced, the exit hints, relaunch and update prints are skipped, and the panic hook keeps only its log report. Ported from upstream 1.0.19 (grok-build EscapeWriter, WriterJoin, run_bounded_teardown), extended to the remaining inline writes. Claude-Session: https://claude.ai/code/session_01RUqV8HVGXcAETyYbZ2tPQ4
This commit is contained in:
1 parent
91f14112bf
commit
4512df79e6
17 files changed
+560
-165
No files matched your search
@@ -1596,16 +1596,21 @@ async fn async_main() -> Result<()> {
|
||||
let result = kigi_tui::app::run(args, bg_update_rx).await;
|
||||
kigi_sandbox::flush();
|
||||
match result {
|
||||
Ok(true) => {
|
||||
Ok(exit) if exit.quit_for_update => {
|
||||
let adopted = bg_update_wait.lock().await.take();
|
||||
if finish_update_on_exit(adopted, &update_config).await {
|
||||
eprintln!("Update installed. Run `kigi` to start.");
|
||||
} else {
|
||||
eprintln!("Update did not complete. Run `kigi update` to retry.");
|
||||
let installed =
|
||||
finish_update_on_exit(adopted, &update_config, exit.terminal_reading).await;
|
||||
// A terminal that stopped reading at teardown would block these prints.
|
||||
if exit.terminal_reading {
|
||||
if installed {
|
||||
eprintln!("Update installed. Run `kigi` to start.");
|
||||
} else {
|
||||
eprintln!("Update did not complete. Run `kigi update` to retry.");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Ok(false) => Ok(()),
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
@@ -1621,8 +1626,13 @@ async fn async_main() -> Result<()> {
|
||||
async fn finish_update_on_exit(
|
||||
adopted: Option<tokio::task::JoinHandle<std::io::Result<std::process::ExitStatus>>>,
|
||||
update_config: &UpdateConfig,
|
||||
terminal_reading: bool,
|
||||
) -> bool {
|
||||
let run_blocking = |reason: Option<String>| async move {
|
||||
// The blocking updater prints and its child inherits stderr: not on a stalled terminal.
|
||||
if !terminal_reading {
|
||||
return false;
|
||||
}
|
||||
if let Some(reason) = reason {
|
||||
eprintln!("{reason}");
|
||||
}
|
||||
@@ -1636,7 +1646,9 @@ async fn finish_update_on_exit(
|
||||
};
|
||||
match adopted {
|
||||
Some(handle) => {
|
||||
eprintln!("Waiting for the update download to finish...");
|
||||
if terminal_reading {
|
||||
eprintln!("Waiting for the update download to finish...");
|
||||
}
|
||||
match handle.await {
|
||||
Ok(Ok(status)) if status.success() => true,
|
||||
Ok(Ok(status)) => {
|
||||
|
||||
Reference in new issue
Block a user