Repository: asciinema/asciinema (language: rust)

Issue:
Recording wont exit when using P10k's instant prompt
**Describe the bug**
asciinema rec wont terminate when exiting zsh shell if using p10k-instant-prompt.

**To Reproduce**
Steps to reproduce the behavior:
1. ascinema rec -c "zsh"
2. ...
3. exit
4. asciinema keeps recording

**Expected behavior**
Stop recording

**Versions:**
 - OS: Ubuntu 22.04
 - asciinema cli: asciinema 3.0.0-beta.2




Discussion / hints:
Hmm, probably a deadlock.

What happens when you record with `-c bash` and launch `zsh` from bash manually?
It hangs after exiting bash.

Here is a recording i made with a great tool i found:
[![asciicast](https://asciinema.org/a/GlpFdYWUjnXS9buNTCxbIoQQm.svg)](https://asciinema.org/a/GlpFdYWUjnXS9buNTCxbIoQQm)
Just tried to reproduce it – I have zsh, p10k and instant prompt enabled on my Linux Mint x86_64 PC and I wasn't able to. It worked just fine.

Out of curiosity, are you sure you have only one installation of asciinema on your machine? Because the version you specified yells at you for saying where you want to store the recording. Can it be that you have both the rust + python version available on your path and Bash and ZSH get it mixed up?
I dont think so. Its a fresh install through `cargo install --git ...` and `whereis` outputs the expected `.cargo/bin`.
By yelling you mean `::: Recording session started, writing to test.cast`?

To be clear, I dont see the problem when I comment out the lines sourcing the instant prompt script. Could be something related? I will try using a newer version, maybe it is just outdated.



You can try latest develop, yeah. 

Btw, what terminal application do you use?
I am seeing the error on a WSL2 Ubuntu through Wezterm and on an Arch install through ssh in Wezterm
I tried it now on Arch, using Wezterm, with zsh, p10k, instant prompt (selected verbose mode in the setup wizard), but can't reproduce it either.
When you ssh to the Arch install, this is from Wezterm running on Windows maybe?
I installed p10k again (deleting instant prompt script) and Im getting same results.

> When you ssh to the Arch install, this is from Wezterm running on Windows maybe?

Yes it is, but Im getting the same problem when connecting through other apps (I tried with an Android app, JuiceSSH)




Maybe this is something in your dotfiles / ZSH setup. Might be worthwhile to go as vanilla as possible and re-add your config things and see where it breaks? 

Reason is: I know that when instant prompt launches and there is some kind of delay for it to launch caused by other things in the dotfiles, it will print out things. Maybe this is the case here?
Ok, so given it's the same on JuiceSSH, we can rule out Wezterm. 

However I'm confused about instant prompt. You first wrote:

> To be clear, I dont see the problem when I comment out the lines sourcing the instant prompt script.

And then:

> I installed p10k again (deleting instant prompt script) and Im getting same results.

So it does or does not work _without_ instant prompt?
It does work without instant prompt.

I removed the instant prompt script before reinstalling p10k (and configuring) to ensure the last version is installed. Sorry for the confusion.

I will try and bisect where else could it break.

From the demo you recorded it seems to me that the zsh process actually doesn't fully exit. asciinema finishes when the shell process exits and the allocated PTY for the shell gets closed. So, for some reasone the slave side of the PTY doesn't get closed. (this is probably not useful to you, writing this more as a note for myself :)) 
Doing `kill -9 $$` outputs Killed but still the same issue.
Are you able to install asciinema 2.4.0 and test with it?
Yes, same result. (I uninstalled the rust version)
I am playing with the source code and It gets stuck at pty::handle_child on execvp.
It seems to me that there is not much to be done?

Right, so this confirms that zsh process doesn't exit, for some reason.

Can you try recording with the `script` command instead of asciinema? Or with `ttyrec`?
Using `script` zsh exits.
Comparing with the `script` command source code, I found out it calls some function `ul_pty_signals_setup`.
In that function it sets some signals:
```
sigaddset(&ourset, SIGCHLD);
sigaddset(&ourset, SIGWINCH);
sigaddset(&ourset, SIGALRM);
sigaddset(&ourset, SIGTERM);
sigaddset(&ourset, SIGINT);
sigaddset(&ourset, SIGQUIT);
```

I noticed that in `asciinema`, the `sigalrm` and `sigchld` are not being handled.

Handling this signals allows zsh to exit nicely.
I will open a PR.

Fix patch:
diff --git a/src/pty.rs b/src/pty.rs
index dc8699945..c271192d5 100644
--- a/src/pty.rs
+++ b/src/pty.rs
@@ -4,7 +4,7 @@ use anyhow::{bail, Result};
 use nix::errno::Errno;
 use nix::sys::select::{select, FdSet};
 use nix::{libc, pty, sys::signal, sys::wait, unistd, unistd::ForkResult};
-use signal_hook::consts::{SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGWINCH};
+use signal_hook::consts::{SIGALRM, SIGCHLD, SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGWINCH};
 use signal_hook::SigId;
 use std::collections::HashMap;
 use std::ffi::{CString, NulError};
@@ -84,11 +84,14 @@ fn copy<T: Tty + ?Sized, R: Recorder>(
     let mut input: Vec<u8> = Vec::with_capacity(BUF_SIZE);
     let mut output: Vec<u8> = Vec::with_capacity(BUF_SIZE);
     let mut master_closed = false;
+
     let sigwinch_fd = SignalFd::open(SIGWINCH)?;
     let sigint_fd = SignalFd::open(SIGINT)?;
     let sigterm_fd = SignalFd::open(SIGTERM)?;
     let sigquit_fd = SignalFd::open(SIGQUIT)?;
     let sighup_fd = SignalFd::open(SIGHUP)?;
+    let sigalrm_fd = SignalFd::open(SIGALRM)?;
+    let sigchld_fd = SignalFd::open(SIGCHLD)?;
 
     set_non_blocking(&master_raw_fd)?;
 
@@ -104,6 +107,8 @@ fn copy<T: Tty + ?Sized, R: Recorder>(
         rfds.insert(&sigterm_fd);
         rfds.insert(&sigquit_fd);
         rfds.insert(&sighup_fd);
+        rfds.insert(&sigalrm_fd);
+        rfds.insert(&sigchld_fd);
 
         if !master_closed {
             rfds.insert(&master_fd);
@@ -134,6 +139,8 @@ fn copy<T: Tty + ?Sized, R: Recorder>(
         let sigterm_read = rfds.contains(&sigterm_fd);
         let sigquit_read = rfds.contains(&sigquit_fd);
         let sighup_read = rfds.contains(&sighup_fd);
+        let sigalrm_read = rfds.contains(&sigalrm_fd);
+        let sigchld_read = rfds.contains(&sigchld_fd);
 
         if master_read {
             while let Some(n) = read_non_blocking(&mut master, &mut buf)? {
@@ -220,7 +227,7 @@ fn copy<T: Tty + ?Sized, R: Recorder>(
             sigint_fd.flush();
         }
 
-        if sigterm_read || sigquit_read || sighup_read {
+        if sigterm_read || sigquit_read || sighup_read || sigalrm_read || sigchld_read {
             if sigterm_read {
                 sigterm_fd.flush();
             }
@@ -233,6 +240,13 @@ fn copy<T: Tty + ?Sized, R: Recorder>(
                 sighup_fd.flush();
             }
 
+            if sigalrm_read {
+                sigalrm_fd.flush();
+            }
+
+            if sigchld_read {
+                sigchld_fd.flush();
+            }
             unsafe { libc::kill(child.as_raw(), SIGTERM) };
 
             return Ok(());


Test patch: