Repository: xonsh/xonsh
Pull request: fixes OSError: [Errno 22] Invalid argument
closes #2220 

Problem statement:
Potential OSError: [Errno 22] Invalid argument
In current `proc.py:CommandPipeline` implementation, we built and run procs first, then give controlling terminal to them. There are cases that all the processes have finished, then the "give terminal to them" will raise errors.

```
$ ls
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/xonsh/base_shell.py", line 322, in default
    run_compiled_code(code, self.ctx, None, 'single')
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/xonsh/codecache.py", line 65, in run_compiled_code
    func(code, glb, loc)
  File "<xonsh-code>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/xonsh/built_ins.py", line 858, in subproc_captured_hiddenobject
    return run_subproc(cmds, captured='hiddenobject')
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/xonsh/built_ins.py", line 799, in run_subproc
    command = HiddenCommandPipeline(specs)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/xonsh/proc.py", line 1756, in __init__
    if update_fg_process_group(pipeline_group, background):
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/xonsh/proc.py", line 1691, in update_fg_process_group
    return give_terminal_to(pipeline_group)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/xonsh/jobs.py", line 138, in give_terminal_to
    os.tcsetpgrp(FD_STDERR, pgid)
OSError: [Errno 22] Invalid argument
```

Hints from issue discussion:
I think this just needs a try-except. Thanks for reporting @mitnk 
@scopatz yes, PR is coming.

Commit message: fixes OSError: [Errno 22] Invalid argument

Patch to review:
diff --git a/news/give-term-to-none.rst b/news/give-term-to-none.rst
new file mode 100644
index 0000000000..59016251c7
--- /dev/null
+++ b/news/give-term-to-none.rst
@@ -0,0 +1,14 @@
+**Added:** None
+
+**Changed:** None
+
+**Deprecated:** None
+
+**Removed:** None
+
+**Fixed:**
+
+* Fixed a potential "OSError: [Errno 22] Invalid argument" to increase job
+  control stability.
+
+**Security:** None
diff --git a/xonsh/jobs.py b/xonsh/jobs.py
index 5cd849cda1..7c38364f93 100644
--- a/xonsh/jobs.py
+++ b/xonsh/jobs.py
@@ -135,9 +135,19 @@ def give_terminal_to(pgid):
         def give_terminal_to(pgid):
             oldmask = signal.pthread_sigmask(signal.SIG_BLOCK,
                                              _block_when_giving)
-            os.tcsetpgrp(FD_STDERR, pgid)
-            signal.pthread_sigmask(signal.SIG_SETMASK, oldmask)
-            return True
+            try:
+                os.tcsetpgrp(FD_STDERR, pgid)
+                return True
+            except OSError as e:
+                if getattr(e, 'errno') == 22:  # [Errno 22] Invalid argument
+                    # there are cases that all the processes of pgid have
+                    # finished, then we don't need to do anything here, see
+                    # issue #2220
+                    return False
+                else:
+                    raise
+            finally:
+                signal.pthread_sigmask(signal.SIG_SETMASK, oldmask)
 
     def wait_for_active_job(last_task=None, backgrounded=False):
         """


Code review comments:
diff_hunk: @@ -135,9 +135,19 @@ def give_terminal_to(pgid):
         def give_terminal_to(pgid):
             oldmask = signal.pthread_sigmask(signal.SIG_BLOCK,
                                              _block_when_giving)
-            os.tcsetpgrp(FD_STDERR, pgid)
-            signal.pthread_sigmask(signal.SIG_SETMASK, oldmask)
-            return True
+            try:
+                os.tcsetpgrp(FD_STDERR, pgid)
+                return True
+            except OSError as e:
+                if getattr(e, 'errno') == 22:  # [Errno 22] Invalid argument
path: xonsh/jobs.py
text: @user1:
I think the getattr() call should either have a default value or it should be `e.errno`.  The current method doesn't add any safety and OSErrors should always have the `errno` attribute.

@author:
Ah, right! :)