terminal: Close the terminal if EOF is detected#39082
terminal: Close the terminal if EOF is detected#39082lemorage wants to merge 14 commits intozed-industries:mainfrom
Conversation
|
Passing by and noting that this change is a bit odd and things were working in Linux for me before. |
|
Umm, possible. I never tested the before version in linux. I can grab some time see if any Alacritty related issue resulting in this. |
|
I'd like for us to see if we can make a simple test for this behavior as it feels like its easy for us to break things here (as is evident by this PR being raised now). Would you mind having a brief look at whether this is easily testable? If not I'll try to commit a bit of time into looking into that myself this week. |
|
Sure. Np! |
|
#39232 adds a test that fails on Linux, to help with fixing the issue. |
|
Thanks! I'll see! |
crates/terminal/src/terminal.rs
Outdated
| /* Close only if success or no exit code */ | ||
| self.child_exited.is_none_or(|e| e.code() == Some(0)) | ||
| } else { | ||
| /* Always close the interactive shell */ |
There was a problem hiding this comment.
I think this will cause us to close the shell again if we fail to spawn it for whatever reason. Now, that shouldn't really be happening but an example would be a $SHELL being misconfigured and us using Shell::System, which we then again don't surface to the user because of the immediate closing of the shell
There was a problem hiding this comment.
Valid concern. But tbh, a fundamental problem is that we cannot distinguish between:
- PTY Exit from spawn failure
- PTY Exit from user pressing Ctrl+D
So I find it hard to have the best of both worlds. Do u have any insights on this?
There was a problem hiding this comment.
Hmm, that is problematic. Is there really no way for us to differentiate spawn failure from a EOF signal here? ...
3667b25 to
bb3b4a9
Compare
crates/terminal/src/terminal.rs
Outdated
| let is_interactive_shell = | ||
| !matches!(self.template.shell, Shell::WithArguments { .. }); |
There was a problem hiding this comment.
This doesn't work. Either of the three variants could be an interactive shell. A better approach i think would be to check if self.task.is_none(), then we are definitely a shell session and not a task
There was a problem hiding this comment.
Good idea! I'll see.
crates/terminal/src/terminal.rs
Outdated
| // Give shell time to process SIGINT | ||
| smol::Timer::after(Duration::from_millis(50)).await; |
There was a problem hiding this comment.
This seems a bit odd, we have the event receiving logic from before expecting a wakeup after all. We should keep that instead, if the events differ on platforms we might want to make this a bit more flexible, but waiting for a given duration makes the test flaky
There was a problem hiding this comment.
I remembered this test had some issues on Linux. I'll check this later on my Linux machine.
| Some(task) => task, | ||
| None => { | ||
| if self.child_exited.is_none_or(|e| e.code() == Some(0)) { | ||
| // Always record the latest exit status. |
There was a problem hiding this comment.
Can we add a comment to this entire block describing the intent of this PR? Otherwise this logic looks arbitrary to readers and we might regress unintentionally
|
After Ctrl + C and Ctrl + D, the terminal exits smoothly on Linux. This is verified. But in the |
Veykril
left a comment
There was a problem hiding this comment.
Sorry for the delay, turns out I never submitted this review
crates/terminal/src/terminal.rs
Outdated
| let wakeup = event_rx.recv().await.expect("No wakeup event received"); | ||
| assert_eq!(wakeup, first_event, "Expected wakeup, got {wakeup:?}"); | ||
| #[cfg(target_os = "linux")] | ||
| let first_event = Event::BreadcrumbsChanged; |
There was a problem hiding this comment.
Given this is weirdly the first event on linux, could you check if the timing issue disappears if we make it poll events until the first wakeup event comes in? I unfortunately do not have a linux machine set up at the moment so I can't test this myself.
|
I did some time of testing. So if we poll events until the first wakeup event comes in, the test still fails on Linux unless we adds the following: while let Ok(Ok(_first_event)) = smol_timeout(Duration::from_millis(100), event_rx.recv()).await {}But this is only a remedy on Linux end, which will in turn cause tests failing on MacOS. So whatever a special case |
cebcf05 to
a2e3281
Compare
|
Sorry for the silence on my part, @smitbarmase will take this over and have a look as they have a linux machine to test this on |
|
@Veykril Thanks! No worries! |
|
Hey @lemorage, any findings after rebase? |
|
@smitbarmase Could u make the CI tests running? The tests work properly on my Mac, but I am not sure if they are all passed across platforms. |
smitbarmase
left a comment
There was a problem hiding this comment.
Hey, thanks for this. Looking at the code, it seems like a bunch of cases don’t really make sense. Also, from testing, it doesn’t do what it claims to do, since the keyboard_input_sent heuristic is unreachable.
I’m sure this may have drifted during a rebase or changed further on top of the original patch. I’m going to close this for now, but feel free to take another shot at it. I think we should have tests covering every branch this might introduce.
After checking test_terminal_eof more, it is indeed flaky on Linux because we call Ctrl-C and Ctrl-D right after each other. Sometimes Ctrl-C causes consuming/flushing behavior, which makes the test behave inconsistently. This needs special attention to fix, and I think it’s a separate issue from this patch.
A better way to test the behavior we want would be to run false, and then exit. That also blocks the terminal from closing, and it reproduces on both macOS and Linux with bash.
| None => { | ||
| if self.child_exited.is_none_or(|e| e.code() == Some(0)) { | ||
| // Always record the latest exit status. | ||
| if let Some(e) = exit_status { |
There was a problem hiding this comment.
We already do this on line 2241 right? Why do we need this again right after it?
| // | ||
| // For tasks/commands, we close on successful exit (exit code 0). | ||
| let should_close = if raw_status.is_none() { | ||
| if self.task.is_none() { |
There was a problem hiding this comment.
We already know we are in task is None case, so this is always true?
| // receive keyboard input, while user exits always require it. | ||
| // | ||
| // For tasks/commands, we close on successful exit (exit code 0). | ||
| let should_close = if raw_status.is_none() { |
There was a problem hiding this comment.
Also, raw_status is never None (if you check the call sites) so everything inside it is just a dead code.
Closes #38901 Supersedes #39082 This PR fixes an issue where the terminal tab would stay open after the user exits a shell that has a non-zero exit code (e.g. running `false` then `exit`, or pressing Ctrl-C followed by Ctrl-D). We now track whether any keyboard input was sent to distinguish user-initiated exits from shell spawn failures. Release Notes: - Fixed terminal tab not closing when the shell exits with a non-zero code. Co-authored-by: Glenn Miao <one.lemorage@gmail.com>
Closes #38901
Release Notes:
Implementation Details:
Shell::WithArguments) continue to close based on exit code (0 = success)