Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TUI: system(), systemlist(), :! in same process-group #8678

Open
justinmk opened this issue Jul 3, 2018 · 8 comments
Open

TUI: system(), systemlist(), :! in same process-group #8678

justinmk opened this issue Jul 3, 2018 · 8 comments
Labels
compatibility compatibility with Vim or older Neovim job-control OS processes, spawn system OS resources, pipes, streams tui
Milestone

Comments

@justinmk
Copy link
Member

justinmk commented Jul 3, 2018

#8217 and #8450 describe some casualties of the change in #8107: since #8107, all processes spawned by Nvim are created in their own session (by calling setsid() in the child). Using setsid() avoids serious bugs like #6530 (orphaned processes). It's a good thing to do, and is essentially what Vim's job_start() does as well.

However, the terminal version (at least) of Vim doesn't do that for system(), systemlist() and :!.

"Fixing" this as attempted in #8389 would regress #8107 for system(), systemlist(), and :!. For example, CTRL-C during this command would leave orphan processes:

:echo system('sleep 30|sleep 30|sleep 30')

Vim's approach for system(), systemlist(), and :! is:

  1. Block signals
  2. fork()
  3. Unblock signals in the child
  4. On CTRL-C, send SIGINT to the process-group id
  5. The parent (vim) ignores the signal. The child and its descendants do not, so sending the signal to the group kills everything except vim itself.

To do this in Nvim will (I think) require adding a case in process_spawn() to work without using libuv. This could be implemented like how src/nvim/os/pty_process_unix.c implements ProcessType.kProcessTypePty: add ProcessType.kProcessTypeUnix and special-case it.

The work will be in implementing src/nvim/os/unix_process.c (again as mentioned, src/nvim/os/pty_process_unix.c would be a good starting point).

@justinmk justinmk added compatibility compatibility with Vim or older Neovim job-control OS processes, spawn tui help wanted system OS resources, pipes, streams labels Jul 3, 2018
@justinmk justinmk added this to the unplanned milestone Jul 3, 2018
@justinmk justinmk changed the title TUI: system(), systemlist(), :! TUI: system(), systemlist(), :! in same process-group Jul 3, 2018
justinmk added a commit to justinmk/neovim that referenced this issue Jul 4, 2018
(This commit is for reference; the functional change will be reverted.)

ref neovim#8217
ref neovim#8450
ref neovim#8678

In terminal-Vim, system() and :! run in Vim's process-group. But
8d90171 changed all of Nvim's process-spawn utilities to do
setsid(), which conflicts with that expected terminal-Vim behavior.

To "fix" that, this commit defines Process.detach as a TriState, then
handles the kNone case such that system() and :! do not do setsid() in
the spawned child.

But this commit REGRESSES 8d90171 (neovim#8107), so for example the
following code causes orphan processes:
    :echo system('sleep 30|sleep 30|sleep 30')

Q: If we don't create a new session/process-group, how to avoid zombie
   descendants (e.g. process_wait() calls process_stop(), which only
   kills the root process)?
A: Vim's approach in mch_call_shell_fork() is:
   1. BLOCK_SIGNALS (ignores deadly)
   2. fork()
   3. unblock signals in the child
   4. On CTRL-C, send SIGINT to the process-group id: kill(-pid, SIGINT)
   5. Parent (vim) ignores the signal. Child (and descendants) do not.
   https://github.com/vim/vim/blob/e7499ddc33508d3d341e96f84a0e7b95b2d6927c/src/os_unix.c#L4834-L4841
   https://github.com/vim/vim/blob/e7499ddc33508d3d341e96f84a0e7b95b2d6927c/src/os_unix.c#L5122-L5134

But we can't do that if we want to use the existing (libuv-based) form
of process_spawn().
@kghost
Copy link

kghost commented Jul 17, 2019

Currently both neovim and vim is doing wrong. To fix the problem once for all, use the following process:

  1. neovim prepares pipes for the new chile
  2. neovim forks a new child process
  3. child dups pipes to stdin/stdout
  4. both neovim and child: create a new process group for the child (setpgid)
  5. both neovim and child: set the child active process group (tcsetpgrp)
  6. exec in the child (sh -c "......")
  7. while neovim waits child, handle input/output of child via pipes
  8. child exits, neovim is notified by SIGCLD
  9. set neovim to active process group (again tcsetpgrp)

Handle signals

Here is how we handle signals during step 7. As we already set the child as active process group, when user hit Ctrl-C, the SIGINT will be send to the active process group directly (aka, all process created by the command), this is the default pty behaviour. And it is the child's duty to clean up all his children. In most case, all children created will receive SIGINT from tty, and quit themself, if some processes refuse to exit by SIGINT, it is their choice, nothing related to neovim.

Note

  1. no session is needed (setsid), unless we are emulating a term. So only termopen should create a new session, no other command should create a new session.
  2. the signals are generated and delivered by tty, we should avoid to proxy/delegate signals.
  3. when neovim decided to quit before some of its children, instead of use SIGTERM, we should sue SIGHUP to kill all the child process groups.

@justinmk

This comment has been minimized.

@justinmk justinmk added closed:wontfix current behavior is by design, and change is not desired and removed help wanted labels Jul 17, 2019
@kghost

This comment has been minimized.

@justinmk
Copy link
Member Author

I haven't explained what's wrong, but #8450 #8217 #8527 does

At least #8217 can be solved by changing :! to use :terminal implicitly (described in #5431). That is the long-term solution, and would be much, much more valuable.

I'll reopen this since I forgot about the workaround in the main description:

adding a case in process_spawn() to work without using libuv.

But really, the long-term solution is to use :terminal implementation for :!. Nvim is a client-server architecture first. The terminal UI is just a UI.

@justinmk justinmk reopened this Jul 17, 2019
@kghost
Copy link

kghost commented Jul 19, 2019

OK, I got your concept, and happy to progress this way.

Currently uv_spawn is designed for detach process, which do use setsid but doesn't allocate pty. So I'm finding a way to replace it as you suggested,

adding a case in process_spawn() to work without using libuv.

@clason clason removed the closed:wontfix current behavior is by design, and change is not desired label Jul 28, 2021
@qbedard
Copy link

qbedard commented Mar 23, 2022

Seems like this might be the cause of this: kovidgoyal/kitty#4869

Can anyone help me confirm? Any news on a fix?

@laktak
Copy link

laktak commented Jan 8, 2025

Would this fix the issue that the FAQ describes as

! AND SYSTEM() DO WEIRD THINGS WITH INTERACTIVE PROCESSES

I use this a lot in Vim to launch TUIs (file manager, git, etc.) and would like to have this available in Neovim as well.

@zeertzjq
Copy link
Member

zeertzjq commented Jan 9, 2025

No

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compatibility compatibility with Vim or older Neovim job-control OS processes, spawn system OS resources, pipes, streams tui
Projects
None yet
Development

No branches or pull requests

6 participants