about summary refs log tree commit diff stats
path: root/ranger
diff options
context:
space:
mode:
authortoonn <toonn@toonn.io>2021-08-08 19:40:15 +0200
committertoonn <toonn@toonn.io>2021-08-08 19:40:15 +0200
commit8bbd1a2ed33d7ba77ee13fc69f7705f6b8b6d440 (patch)
treebc3b29367a43e20cedb60d08593b18e8623c9887 /ranger
parentfc4430de36d033563d21413ae009409fd7cb3a59 (diff)
downloadranger-8bbd1a2ed33d7ba77ee13fc69f7705f6b8b6d440.tar.gz
popen23: Popen context manager wrapper
Diffstat (limited to 'ranger')
-rw-r--r--ranger/ext/popen23.py60
1 files changed, 60 insertions, 0 deletions
diff --git a/ranger/ext/popen23.py b/ranger/ext/popen23.py
new file mode 100644
index 00000000..a0ae86e1
--- /dev/null
+++ b/ranger/ext/popen23.py
@@ -0,0 +1,60 @@
+# This file is part of ranger, the console file manager.
+# License: GNU GPL version 3, see the file "AUTHORS" for details.
+
+from __future__ import absolute_import
+
+from contextlib import contextmanager
+
+from subprocess import Popen, TimeoutExpired
+
+try:
+    from ranger import PY3
+except ImportError:
+    from sys import version_info
+    PY3 = version_info[0] >= 3
+
+
+# COMPAT: Python 2 (and Python <=3.2) subprocess.Popen objects aren't
+#         context managers. We don't care about early Python 3 but we do want
+#         to wrap Python 2's Popen. There's no harm in always using this Popen
+#         but it is only necessary when used with with-statements. This can be
+#         removed once we ditch Python 2 support.
+@contextmanager
+def Popen23(*args, **kwargs):  # pylint: disable=invalid-name
+    if PY3:
+        yield Popen(*args, **kwargs)
+        return
+    else:
+        popen2 = Popen(*args, **kwargs)
+    try:
+        yield popen2
+    finally:
+        # From Lib/subprocess.py Popen.__exit__:
+        if popen2.stdout:
+            popen2.stdout.close()
+        if popen2.stderr:
+            popen2.stderr.close()
+        try:  # Flushing a BufferedWriter may raise an error
+            if popen2.stdin:
+                popen2.stdin.close()
+        except KeyboardInterrupt:
+            # https://bugs.python.org/issue25942
+            # In the case of a KeyboardInterrupt we assume the SIGINT
+            # was also already sent to our child processes.  We can't
+            # block indefinitely as that is not user friendly.
+            # If we have not already waited a brief amount of time in
+            # an interrupted .wait() or .communicate() call, do so here
+            # for consistency.
+            # pylint: disable=protected-access
+            if popen2._sigint_wait_secs > 0:
+                try:
+                    # pylint: disable=no-member
+                    popen2._wait(timeout=popen2._sigint_wait_secs)
+                except TimeoutExpired:
+                    pass
+            popen2._sigint_wait_secs = 0  # Note that this has been done.
+            # pylint: disable=lost-exception
+            return  # resume the KeyboardInterrupt
+        finally:
+            # Wait for the process to terminate, to avoid zombies.
+            popen2.wait()