Repository: xonsh/xonsh

Issue:
xonsh sometimes crashes on Ctrl-c at the prompt
At a prompt Ctrl-c is expected to clear the line of input but in some cases it causes xonsh to hang for a while before crashing to /bin/sh. This can be reproduced as follows:
```
$ @('invalid command')
xonsh: subprocess mode: command not found: invalid command
$ arbitrary text # now press up and Ctrl-c.Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/xonsh/main.py", line 337, in main
    return main_xonsh(args)
  File "/usr/lib/python3.6/site-packages/xonsh/main.py", line 366, in main_xonsh
    shell.shell.cmdloop()
  File "/usr/lib/python3.6/site-packages/xonsh/readline_shell.py", line 470, in cmdloop
    self._cmdloop(intro=intro)
  File "/usr/lib/python3.6/site-packages/xonsh/readline_shell.py", line 430, in _cmdloop
    line = self.singleline()
  File "/usr/lib/python3.6/site-packages/xonsh/readline_shell.py", line 288, in singleline
    rtn = input(self.prompt)
  File "/usr/lib/python3.6/site-packages/xonsh/proc.py", line 758, in _signal_int
    signal.pthread_kill(threading.get_ident(), signal.SIGINT)
  File "/usr/lib/python3.6/site-packages/xonsh/proc.py", line 758, in _signal_int
    signal.pthread_kill(threading.get_ident(), signal.SIGINT)
  File "/usr/lib/python3.6/site-packages/xonsh/proc.py", line 758, in _signal_int
    signal.pthread_kill(threading.get_ident(), signal.SIGINT)
  [Previous line repeated 326 more times]
  File "/usr/lib/python3.6/site-packages/xonsh/proc.py", line 754, in _signal_int
    self.send_signal(signum)
  File "/usr/lib/python3.6/site-packages/xonsh/proc.py", line 899, in send_signal
    while self.proc is None and dt < self.timeout:
RecursionError: maximum recursion depth exceeded in comparison
Xonsh encountered an issue during launch
Failback to /bin/sh
```
I first observed this with 0.5.5 and have reproduced it with 0.5.6. The crash also occurs if the up arrow is used to just get the previous input and then Ctrl-c is pressed.

The crash did not occur where the @ substitution produced a valid command or an argument.


Discussion:
Hi @danielspencer, thanks for reporting this. I can't seem to reproduce this on either the prompt-toolkit or readline shell. Can you please post the output of your `xonfig`?
`xonfig` output:
```
+------------------+---------------------------------------------------+
| xonsh            | 0.5.6                                             |
| Git SHA          | fba07c5                                           |
| Commit Date      | Feb 21 15:50:42 2017                              |
| Python           | 3.6.0                                             |
| PLY              | 3.9                                               |
| have readline    | True                                              |
| prompt toolkit   | None                                              |
| shell type       | readline                                          |
| pygments         | None                                              |
| on posix         | True                                              |
| on linux         | <xonsh.lazyasd.LazyBool object at 0x7f7b6f44e470> |
| distro           | arch                                              |
| on darwin        | False                                             |
| on windows       | False                                             |
| on cygwin        | False                                             |
| is superuser     | False                                             |
| default encoding | utf-8                                             |
| xonsh encoding   | utf-8                                             |
| encoding errors  | surrogateescape                                   |
+------------------+---------------------------------------------------+
```

~/.config/xonsh/config.json:
```
{
 "foreign_shells": [
  {
   "shell": "zsh"
  }
 ]
}
```
Does this still occur is you don't use zsh in your foreign shells? What exact arbitrary text are you using and does this appear in your history anywhere?
I have used many example texts for the arbitrary string, some examples would be '1 + 1' which I would expect to be interpreted as python and 'echo foo' which I'd expect to be interpreted as a zsh command.

From further experimentation I found that I didn't actually need to press the up arrow at all for the bug to trigger, just pressing Ctrl-c at the prompt would trigger it even many commands after the erroneous @ substitution.

On the history front history the crash seems to prevent history from being written, though the @ command is included in the history if it terminates normally.

This was all checked after deleting the config file, having no foreign shell didn't effect it. `xonfig` output with removed config:
```
+------------------+---------------------------------------------------+
| xonsh            | 0.5.6                                             |
| Git SHA          | fba07c5                                           |
| Commit Date      | Feb 21 15:50:42 2017                              |
| Python           | 3.6.0                                             |
| PLY              | 3.9                                               |
| have readline    | True                                              |
| prompt toolkit   | None                                              |
| shell type       | readline                                          |
| pygments         | None                                              |
| on posix         | True                                              |
| on linux         | <xonsh.lazyasd.LazyBool object at 0x7f75a92f7438> |
| distro           | arch                                              |
| on darwin        | False                                             |
| on windows       | False                                             |
| on cygwin        | False                                             |
| is superuser     | False                                             |
| default encoding | utf-8                                             |
| xonsh encoding   | utf-8                                             |
| encoding errors  | surrogateescape                                   |
+------------------+---------------------------------------------------+
```
Maybe this is a Python v3.6 issue. Are you able to test it on v3.5 with the same setup?
I can't duplicate this on Python 3.5 but it is definitely an issue on 3.6
I can reproduce this issue. Both Python 3.5 & 3.6 has it. PTK shell does not have such issue, only readline shell does. Reproducing it is very easy with following action:
```
$ @('invalid command')
xonsh: subprocess mode: command not found: invalid command
# Ctrl + C ONCE immediately after `invalid command` finished run (after you see xonsh prompt shows).
$ Traceback (most recent call last):
  File "/usr/local/lib/python3.5/dist-packages/xonsh/__amalgam__.py", line 19413, in main
    return main_xonsh(args)
  File "/usr/local/lib/python3.5/dist-packages/xonsh/__amalgam__.py", line 19442, in main_xonsh
    shell.shell.cmdloop()
  File "/usr/local/lib/python3.5/dist-packages/xonsh/__amalgam__.py", line 16742, in cmdloop
    self._cmdloop(intro=intro)
  File "/usr/local/lib/python3.5/dist-packages/xonsh/__amalgam__.py", line 16702, in _cmdloop
    line = self.singleline()
  File "/usr/local/lib/python3.5/dist-packages/xonsh/__amalgam__.py", line 16560, in singleline
    rtn = input(self.prompt)
  File "/usr/local/lib/python3.5/dist-packages/xonsh/__amalgam__.py", line 11862, in _signal_int
    signal.pthread_kill(threading.get_ident(), signal.SIGINT)
  File "/usr/local/lib/python3.5/dist-packages/xonsh/__amalgam__.py", line 11862, in _signal_int
    signal.pthread_kill(threading.get_ident(), signal.SIGINT)
  File "/usr/local/lib/python3.5/dist-packages/xonsh/__amalgam__.py", line 11862, in _signal_int
    signal.pthread_kill(threading.get_ident(), signal.SIGINT)
  File "/usr/local/lib/python3.5/dist-packages/xonsh/__amalgam__.py", line 11862, in _signal_int
    signal.pthread_kill(threading.get_ident(), signal.SIGINT)
   #  ......( repeated 100x times)
File "/usr/local/lib/python3.5/dist-packages/xonsh/__amalgam__.py", line 11862, in _signal_int
    signal.pthread_kill(threading.get_ident(), signal.SIGINT)
  File "/usr/local/lib/python3.5/dist-packages/xonsh/__amalgam__.py", line 11858, in _signal_int
    self.send_signal(signum)
  File "/usr/local/lib/python3.5/dist-packages/xonsh/__amalgam__.py", line 12003, in send_signal
    while self.proc is None and dt < self.timeout:
RecursionError: maximum recursion depth exceeded in comparison
Xonsh encountered an issue during launch
Failback to /bin/sh
```
Traceback with Python 3.6 is exactly the same as @danielspencer posted.

In the code of `proc.py:PopenThread:_signal_int()`:
```
    def _signal_int(self, signum, frame):
        # ... skipped some code ...
        if on_main_thread():
            signal.pthread_kill(threading.get_ident(), signal.SIGINT)  # causing recursion?
```
Seems inside the signal handler, it will re-singal SIGINT in some situations. That's where the "Recursion" being caused I guess?
Hmm I wonder if PTK and older versions of readline are installing their own signal handlers.  Fixing this is probably a matter of checking to see if this method is also the calling function and not recursing anymore.

Resolving patch:
diff --git a/xonsh/jobs.py b/xonsh/jobs.py
--- a/xonsh/jobs.py
+++ b/xonsh/jobs.py
@@ -133,14 +133,17 @@ def give_terminal_to(pgid):
             return True
     else:
         def give_terminal_to(pgid):
-            oldmask = signal.pthread_sigmask(signal.SIG_BLOCK,
-                                             _block_when_giving)
             if pgid is None:
-                signal.pthread_sigmask(signal.SIG_SETMASK, oldmask)
                 return False
+            oldmask = signal.pthread_sigmask(signal.SIG_BLOCK,
+                                             _block_when_giving)
             try:
                 os.tcsetpgrp(FD_STDERR, pgid)
                 return True
+            except ProcessLookupError:
+                # when the process finished before giving terminal to it,
+                # see issue #2288
+                return False
             except OSError as e:
                 if e.errno == 22:  # [Errno 22] Invalid argument
                     # there are cases that all the processes of pgid have
diff --git a/xonsh/proc.py b/xonsh/proc.py
--- a/xonsh/proc.py
+++ b/xonsh/proc.py
@@ -549,11 +549,17 @@ def __init__(self, *args, stdin=None, stdout=None, stderr=None, **kwargs):
         # start up process
         if ON_WINDOWS and stdout is not None:
             os.set_handle_inheritable(stdout.fileno(), False)
-        self.proc = proc = subprocess.Popen(*args,
-                                            stdin=stdin,
-                                            stdout=stdout,
-                                            stderr=stderr,
-                                            **kwargs)
+
+        try:
+            self.proc = proc = subprocess.Popen(*args,
+                                                stdin=stdin,
+                                                stdout=stdout,
+                                                stderr=stderr,
+                                                **kwargs)
+        except Exception:
+            self._clean_up()
+            raise
+
         self.pid = proc.pid
         self.universal_newlines = uninew = proc.universal_newlines
         if uninew:
@@ -863,10 +869,13 @@ def wait(self, timeout=None):
         if self.old_winch_handler is not None and on_main_thread():
             signal.signal(signal.SIGWINCH, self.old_winch_handler)
             self.old_winch_handler = None
+        self._clean_up()
+        return rtn
+
+    def _clean_up(self):
         self._restore_sigint()
         self._restore_sigtstp()
         self._restore_sigquit()
-        return rtn
 
     @property
     def returncode(self):


Test patch: