aboutsummaryrefslogtreecommitdiff
path: root/src/platform
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-07 16:14:46 +0300
committerGitHub <noreply@github.com>2026-09-07 15:14:46 +0200
commite7d8220788b4fa7c266a1e4457591b694478771c (patch)
tree272303d74e0b58c11f9c0c434dae8335d3d06703 /src/platform
parent482818130407691154d1635d4029b95d0e730104 (diff)
fix: preserve sessions after interrupted pane exits (#3418)
* fix: preserve sessions when pane shells are signaled refs #3415 * fix: preserve shutdown checkpoints across platforms refs #3415 --------- Co-authored-by: Jonathan Liebig <jonathan.liebig@gmail.com>
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/mod.rs49
-rw-r--r--src/platform/unix_common.rs8
-rw-r--r--src/platform/windows.rs9
3 files changed, 66 insertions, 0 deletions
diff --git a/src/platform/mod.rs b/src/platform/mod.rs
index 86a431aa..d48ad4fa 100644
--- a/src/platform/mod.rs
+++ b/src/platform/mod.rs
@@ -25,6 +25,36 @@ pub enum Signal {
Kill,
}
+/// Why a pane runtime ended, before application persistence policy is applied.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ChildExitReason {
+ Exited,
+ Interrupted,
+ /// Imported runtimes have no child wait handle in the replacement server.
+ #[cfg(unix)]
+ Handoff,
+ WaitFailed,
+}
+
+impl ChildExitReason {
+ pub(crate) fn requires_session_checkpoint(self) -> bool {
+ match self {
+ Self::Interrupted => true,
+ #[cfg(unix)]
+ Self::Handoff => true,
+ _ => false,
+ }
+ }
+}
+
+#[cfg(unix)]
+pub(crate) use unix_common::classify_child_exit;
+
+#[cfg(not(any(unix, windows)))]
+pub(crate) fn classify_child_exit(_status: &portable_pty::ExitStatus) -> ChildExitReason {
+ ChildExitReason::Exited
+}
+
pub(crate) fn detached_custom_command_process(command: &str) -> std::process::Command {
let mut process = detached_custom_command_process_platform(command);
configure_background_command(&mut process);
@@ -415,6 +445,25 @@ impl PrefixInputSource for RealPrefixInputSource {
}
}
+#[cfg(all(test, any(unix, windows)))]
+#[test]
+fn child_exit_classification_only_checkpoints_interruptions() {
+ for code in [0, 1, 130, 255, 0xC0000005] {
+ let reason = classify_child_exit(&portable_pty::ExitStatus::with_exit_code(code));
+ assert_eq!(reason, ChildExitReason::Exited, "exit code {code:#x}");
+ assert!(!reason.requires_session_checkpoint());
+ }
+ #[cfg(windows)]
+ let status = portable_pty::ExitStatus::with_exit_code(0xC000013A);
+ #[cfg(not(windows))]
+ let status = portable_pty::ExitStatus::with_signal("Terminated: 15");
+ assert_eq!(classify_child_exit(&status), ChildExitReason::Interrupted);
+ assert!(classify_child_exit(&status).requires_session_checkpoint());
+ #[cfg(unix)]
+ assert!(ChildExitReason::Handoff.requires_session_checkpoint());
+ assert!(!ChildExitReason::WaitFailed.requires_session_checkpoint());
+}
+
#[cfg(all(test, unix))]
mod tests {
use super::*;
diff --git a/src/platform/unix_common.rs b/src/platform/unix_common.rs
index dff67357..104f6b69 100644
--- a/src/platform/unix_common.rs
+++ b/src/platform/unix_common.rs
@@ -1,5 +1,13 @@
use std::path::{Path, PathBuf};
+pub(crate) fn classify_child_exit(status: &portable_pty::ExitStatus) -> super::ChildExitReason {
+ if status.signal().is_some() {
+ super::ChildExitReason::Interrupted
+ } else {
+ super::ChildExitReason::Exited
+ }
+}
+
pub(crate) fn wait_client_stream_readable(stream: &crate::ipc::LocalStream) -> std::io::Result<()> {
use std::os::fd::{AsFd as _, AsRawFd as _};
let crate::ipc::LocalStream::UdSocket(stream) = stream;
diff --git a/src/platform/windows.rs b/src/platform/windows.rs
index ce0afedb..4deb1864 100644
--- a/src/platform/windows.rs
+++ b/src/platform/windows.rs
@@ -15,6 +15,15 @@ use std::{
mod clipboard_image;
+pub(crate) fn classify_child_exit(status: &portable_pty::ExitStatus) -> super::ChildExitReason {
+ // STATUS_CONTROL_C_EXIT is reported without a Unix signal by portable-pty.
+ if status.exit_code() == 0xC000013A {
+ super::ChildExitReason::Interrupted
+ } else {
+ super::ChildExitReason::Exited
+ }
+}
+
pub(crate) fn wait_client_stream_readable(
_stream: &crate::ipc::LocalStream,
) -> std::io::Result<()> {