Repository: xonsh/xonsh

Issue:
Crash in built_ins._run_binary() with fresh source; works with 0.9.13.dev1 from pypi
Newly-cloned master branch of xonsh/xonsh crashes when trying to execute a command line with a pipeline, if both parts are actual subcommands (not aliases), e'g `ls | wc`, not `cat tttt | wc`.  Same command line works as expected when xonsh is installed from PyPi package.

## Repro (failing)
Start with fresh virtualenv and fresh clone of xonsh/xonsh repo, installed in dev mode.
<details>

```
bob@bob-desktop:~$ virtualenv -p python3.7 testenv
Running virtualenv with interpreter /usr/bin/python3.7
Already using interpreter /usr/bin/python3.7
Using base prefix '/usr'
New python executable in /home/bob/testenv/bin/python3.7
Also creating executable in /home/bob/testenv/bin/python
Installing setuptools, pip, wheel...
done.
bob@bob-desktop:~$ source testenv/bin/activate
(testenv) bob@bob-desktop:~$ git clone https://github.com/xonsh/xonsh.git
Cloning into 'xonsh'...
remote: Enumerating objects: 20, done.
remote: Counting objects: 100% (20/20), done.
remote: Compressing objects: 100% (18/18), done.
remote: Total 39424 (delta 7), reused 11 (delta 2), pack-reused 39404
Receiving objects: 100% (39424/39424), 12.84 MiB | 35.54 MiB/s, done.
Resolving deltas: 100% (28316/28316), done.
(testenv) bob@bob-desktop:~$ cd xonsh
(testenv) bob@bob-desktop:~/xonsh$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean
(testenv) bob@bob-desktop:~/xonsh$ pip install -e .
Obtaining file:///home/bob/xonsh
Installing collected packages: xonsh
  Found existing installation: xonsh 0.9.13.dev1
    Uninstalling xonsh-0.9.13.dev1:
      Successfully uninstalled xonsh-0.9.13.dev1
  Running setup.py develop for xonsh
Successfully installed xonsh
(testenv) bob@bob-desktop:~/xonsh$ pip list
Package    Version Location       
---------- ------- ---------------
pip        19.3.1  
setuptools 44.0.0  
wheel      0.33.6  
xonsh      0.9.13  /home/bob/xonsh

(testenv) bob@bob-desktop:~/xonsh$ xonsh
xonsh: For full traceback set: $XONSH_SHOW_TRACEBACK = True
ModuleNotFoundError: No module named 'prompt_toolkit'
~~~~~~~~~~~~ (testenv) bob@bob-desktop ~/xonsh master|✓ ~~~~~~~~~~~~ 
$ $XONSH_SHOW_TRACEBACK=True
~~~~~~~~~~~~ (testenv) bob@bob-desktop ~/xonsh master|✓ ~~~~~~~~~~~~ 
$ ls | wc
xonsh: To log full traceback to a file set: $XONSH_TRACEBACK_LOGFILE = <filename>
Traceback (most recent call last):
  File "/home/bob/xonsh/xonsh/proc.py", line 1890, in __init__
    proc = spec.run(pipeline_group=pipeline_group)
  File "/home/bob/xonsh/xonsh/built_ins.py", line 549, in run
    p = self._run_binary(kwargs)
  File "/home/bob/xonsh/xonsh/built_ins.py", line 560, in _run_binary
    p = self.cls(self.cmd, bufsize=bufsize, **kwargs)
  File "/home/bob/xonsh/xonsh/proc.py", line 558, in __init__
    self._disable_suspend_keybind()
  File "/home/bob/xonsh/xonsh/proc.py", line 836, in _disable_suspend_keybind
    termios.tcsetattr(0, termios.TCSANOW, mode)
termios.error: (4, 'Interrupted system call')
~~~~~~~~~~~~ (testenv) bob@bob-desktop ~/xonsh master|✓ ~~~~~~~~~~~~ 
$ cat tttt | wc
     32      32     358
~~~~~~~~~~~~ (testenv) bob@bob-desktop ~/xonsh master|✓ ~~~~~~~~~~~~ 
$ ls | cat                                                                                                                              
AUTHORS.rst
CHANGELOG.rst
CONTRIBUTING.rst
MANIFEST.in
Makefile
README.rst
amalgamate.py
appveyor.yml
azure-pipelines.ym
  . . . snip . . .
```
</details>

## Repro (working, with version from PyPi)

Using same virtual environment as established above...

<details>

```
~~~~~~~~~~~~ (testenv) bob@bob-desktop ~/xonsh master|✓ ~~~~~~~~~~~~ 
$ pip uninstall xonsh
Proceed (y/n)? Uninstalling xonsh-0.9.13:
  Would remove:
    /home/bob/testenv/lib/python3.7/site-packages/xonsh.egg-link
y
  Successfully uninstalled xonsh-0.9.13
~~~~~~~~~~~~ (testenv) bob@bob-desktop ~/xonsh master|✓ ~~~~~~~~~~~~ 
$ pip install xonsh
Processing /home/bob/.cache/pip/wheels/5d/4c/65/67c0eb6a1f0149911ca0018bf54c328c93fd5fa5cbf141b54c/xonsh-0.9.13.dev1-cp37-none-any.whl
Installing collected packages: xonsh
Successfully installed xonsh-0.9.13.dev1
~~~~~~~~~~~~ (testenv) bob@bob-desktop ~/xonsh master|✓ ~~~~~~~~~~~~ 
$ exit

(testenv) bob@bob-desktop:~/xonsh$ xonsh

xonsh: For full traceback set: $XONSH_SHOW_TRACEBACK = True
ModuleNotFoundError: No module named 'prompt_toolkit'
~~~~~~~~~~~~ (testenv) bob@bob-desktop ~/xonsh master|✓ ~~~~~~~~~~~~ 
$ $XONSH_SHOW_TRACEBACK = True
~~~~~~~~~~~~ (testenv) bob@bob-desktop ~/xonsh master|✓ ~~~~~~~~~~~~ 
$ ls | wc
     32      32     358
~~~~~~~~~~~~ (testenv) bob@bob-desktop ~/xonsh master|✓ ~~~~~~~~~~~~ 
$ cat tttt | wc
     32      32     358
~~~~~~~~~~~~ (testenv) bob@bob-desktop ~/xonsh master|✓ ~~~~~~~~~~~~ 
$ 

```

</details>

## Comments

* In the above, I don't have Pygments or prompt-toolkit installed, but I get same behavior with (latest versions of) both.  

* Here's another crash stack that is actually what I was first seeing.  I don't have a solid repro for it, but I think it comes up with a virtualenv with all the unit test dependencies, Pygments and prompt-toolkit *and* after I fully install the source tree, e.g. `pip install` instead of `pip install -e`.

<details>
```
~~~~~~~~~~~~ (xonsh-test-37) bob@bob-desktop /mnt/myd/dev/src/xonsh pipe-error|+1…3 ~~~~~~~~~~~~                                        
$ ls | more                                                                                                                             
Traceback (most recent call last):
  File "/var/venv/xonsh-test-37/lib/python3.7/site-packages/xonsh/proc.py", line 1890, in __init__
    proc = spec.run(pipeline_group=pipeline_group)
  File "/var/venv/xonsh-test-37/lib/python3.7/site-packages/xonsh/built_ins.py", line 549, in run
    p = self._run_binary(kwargs)
  File "/var/venv/xonsh-test-37/lib/python3.7/site-packages/xonsh/built_ins.py", line 560, in _run_binary
    p = self.cls(self.cmd, bufsize=bufsize, **kwargs)
  File "/usr/lib/python3.7/subprocess.py", line 800, in __init__
    restore_signals, start_new_session)
  File "/usr/lib/python3.7/subprocess.py", line 1552, in _execute_child
    raise child_exception_type(err_msg)
subprocess.SubprocessError: Exception occurred in preexec_fn.

```
</details>



Discussion:

Resolving patch:
diff --git a/xonsh/built_ins.py b/xonsh/built_ins.py
--- a/xonsh/built_ins.py
+++ b/xonsh/built_ins.py
@@ -601,7 +601,7 @@ def xonsh_preexec_fn():
                 """Preexec function bound to a pipeline group."""
                 os.setpgid(0, pipeline_group)
                 signal.signal(
-                    signal.SIGTERM if ON_WINDOWS else signal.SIGSTP,
+                    signal.SIGTERM if ON_WINDOWS else signal.SIGTSTP,
                     default_signal_pauser,
                 )
 
diff --git a/xonsh/proc.py b/xonsh/proc.py
--- a/xonsh/proc.py
+++ b/xonsh/proc.py
@@ -829,21 +829,20 @@ def _disable_suspend_keybind(self):
             return
         try:
             mode = termios.tcgetattr(0)  # only makes sense for stdin
+            self._tc_cc_vsusp = mode[CC][termios.VSUSP]
+            mode[CC][termios.VSUSP] = b"\x00"  # set ^Z (ie SIGSTOP) to undefined
+            termios.tcsetattr(0, termios.TCSANOW, mode)
         except termios.error:
             return
-        self._tc_cc_vsusp = mode[CC][termios.VSUSP]
-        mode[CC][termios.VSUSP] = b"\x00"  # set ^Z (ie SIGSTOP) to undefined
-        termios.tcsetattr(0, termios.TCSANOW, mode)
 
     def _restore_suspend_keybind(self):
         if ON_WINDOWS:
             return
         try:
             mode = termios.tcgetattr(0)  # only makes sense for stdin
-        except termios.error:
-            return
-        mode[CC][termios.VSUSP] = self._tc_cc_vsusp  # set ^Z (ie SIGSTOP) to original
-        try:
+            mode[CC][
+                termios.VSUSP
+            ] = self._tc_cc_vsusp  # set ^Z (ie SIGSTOP) to original
             # this usually doesn't work in interactive mode,
             # but we should try it anyway.
             termios.tcsetattr(0, termios.TCSANOW, mode)


Test patch:
diff --git a/tests/test_integrations.py b/tests/test_integrations.py
--- a/tests/test_integrations.py
+++ b/tests/test_integrations.py
@@ -547,3 +547,15 @@ def test_xonsh_no_close_fds():
             f.write(makefile)
         out = sp.check_output(["make", "-sj2", "SHELL=xonsh"], universal_newlines=True)
         assert "warning" not in out
+
+
+@pytest.mark.parametrize(
+    "cmd, fmt, exp",
+    [
+        ("ls | wc", lambda x: x > '', True),
+    ],
+)
+def test_pipe_between_subprocs(cmd, fmt, exp):
+    "verify pipe between subprocesses doesn't throw an exception"
+    check_run_xonsh(cmd, fmt, exp)
+