From 28251cb4cec0598e5357940b68269f49c328afc9 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Wed, 14 Nov 2007 18:13:46 +0000 Subject: [PATCH 01/20] Import vendor sources from http://svn.smedbergs.us/python-processes/trunk/ r122 cf. article at http://benjamin.smedbergs.us/blog/2006-12-11/killableprocesspy/ git-svn-id: svn+ssh://svn.dlogics.com/svn/dev/Packages/python-processes/vendor/current@22954 a347f3a1-5518-0410-bf23-827ba1738e83 --- killableprocess.py | 0 winprocess.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 killableprocess.py mode change 100644 => 100755 winprocess.py diff --git a/killableprocess.py b/killableprocess.py old mode 100644 new mode 100755 diff --git a/winprocess.py b/winprocess.py old mode 100644 new mode 100755 From a2d203470c6d2f226ace32b3846accc20bb28a2d Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Wed, 14 Nov 2007 18:14:15 +0000 Subject: [PATCH 02/20] Tag r122 gotten from vendor git-svn-id: svn+ssh://svn.dlogics.com/svn/dev/Packages/python-processes/vendor/r122@22955 a347f3a1-5518-0410-bf23-827ba1738e83 From f72289a0e4a2a38a58aed3a833d597abd9402665 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Wed, 14 Nov 2007 18:14:40 +0000 Subject: [PATCH 03/20] Use r122 for trunk. git-svn-id: svn+ssh://svn.dlogics.com/svn/dev/Packages/python-processes/trunk@22956 a347f3a1-5518-0410-bf23-827ba1738e83 From c043d7a7bc15652898c6a3a62c0d2f4eeb2ef370 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Wed, 14 Nov 2007 18:20:14 +0000 Subject: [PATCH 04/20] Give package a name that is a valid package name in Python (no hyphen) git-svn-id: svn+ssh://svn.dlogics.com/svn/dev/Packages/python_processes/trunk@22957 a347f3a1-5518-0410-bf23-827ba1738e83 From 6a262953a0c8a60b7818998cf065b69e29f0780d Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Wed, 14 Nov 2007 20:13:41 +0000 Subject: [PATCH 05/20] Force the timeout value to integer after multiplying by 1000 on Windows. This lets us use floating point timeout values without error. git-svn-id: svn+ssh://svn.dlogics.com/svn/dev/Packages/python_processes/trunk@22963 a347f3a1-5518-0410-bf23-827ba1738e83 --- killableprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/killableprocess.py b/killableprocess.py index 51c53e7..a51f310 100755 --- a/killableprocess.py +++ b/killableprocess.py @@ -190,7 +190,7 @@ def wait(self, timeout=-1, group=True): if mswindows: if timeout != -1: - timeout = timeout * 1000 + timeout = int(timeout * 1000) rc = winprocess.WaitForSingleObject(self._handle, timeout) if rc == winprocess.WAIT_TIMEOUT: self.kill(group) From 37017d195d84700e434518626f8da22c981aed1e Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Wed, 14 Nov 2007 21:36:58 +0000 Subject: [PATCH 06/20] Add an __all__ variable to make this a proper package. git-svn-id: svn+ssh://svn.dlogics.com/svn/dev/Packages/python_processes/trunk@22974 a347f3a1-5518-0410-bf23-827ba1738e83 --- __init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100755 __init__.py diff --git a/__init__.py b/__init__.py new file mode 100755 index 0000000..0069404 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +__all__ = ['killableprocess','winprocess'] From 834633d3b1d64a485759022de542700f61ae7ca0 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Wed, 14 Nov 2007 22:46:46 +0000 Subject: [PATCH 07/20] Fix a race condition in wait with timeout on POSIX: The process might complete in the time it takes us to kill it. So, if kill fails (raises OSError), we try to retrieve the status, and if we do, it's all good. Else, reraise the error. git-svn-id: svn+ssh://svn.dlogics.com/svn/dev/Packages/python_processes/trunk@22979 a347f3a1-5518-0410-bf23-827ba1738e83 --- killableprocess.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/killableprocess.py b/killableprocess.py index a51f310..4157df8 100755 --- a/killableprocess.py +++ b/killableprocess.py @@ -217,7 +217,20 @@ def wait(self, timeout=-1, group=True): newtimeout = timeout - time.time() + starttime time.sleep(newtimeout) - self.kill(group) + try: + self.kill(group) + except OSError: + # Process might have finished since we last checked + # so wait again + pid, sts = os.waitpid(self.pid, os.WNOHANG) + if pid != 0: + self._handle_exitstatus(sts) + signal.signal(signal.SIGCHLD, oldsignal) + return self.returncode + else: + # strange, waiting failed, propagate original exception + raise + signal.signal(signal.SIGCHLD, oldsignal) subprocess.Popen.wait(self) From ec9218c12ad3fd11404d9f4869f8671bfb61b43a Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Wed, 5 Dec 2007 10:38:55 +0000 Subject: [PATCH 08/20] Add timings (rtime = wall clock time, utime = user space CPU time, stime = system CPU time) to Popen object. Currently these work on POSIX systems. git-svn-id: svn+ssh://svn.dlogics.com/svn/dev/Packages/python_processes/trunk@23486 a347f3a1-5518-0410-bf23-827ba1738e83 --- killableprocess.py | 54 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/killableprocess.py b/killableprocess.py index 4157df8..30f2dd7 100755 --- a/killableprocess.py +++ b/killableprocess.py @@ -107,6 +107,12 @@ def setpgid_preexec_fn(): kwargs['preexec_fn'] = setpgid_preexec_fn + # DLADD kam 04Dec07 Add real, user and system timings + self._starttime = time.time() + self.rtime = 0.0 + self.utime = 0.0 + self.stime = 0.0 + subprocess.Popen.__init__(self, *args, **kwargs) if mswindows: @@ -116,6 +122,13 @@ def _execute_child(self, args, executable, preexec_fn, close_fds, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite): + + # DLADD kam 04Dec07 Add real, user and system timings + self._starttime = time.time() + self.rtime = 0.0 + self.utime = 0.0 + self.stime = 0.0 + if not isinstance(args, types.StringTypes): args = subprocess.list2cmdline(args) @@ -179,6 +192,38 @@ def kill(self, group=True): os.kill(self.pid, signal.SIGKILL) self.returncode = -9 + def _wait(self, options): + """Wait for our pid, gathering resource usage if available""" + pid, sts, rusage = os.wait4(self.pid, options) + if pid: + self.rtime = time.time() - self._starttime + self.utime = rusage[0] + self.stime = rusage[1] + return pid, sts + + def _waitforstatus(self): + """Wait for child process to terminate. Returns returncode + attribute.""" + if self.returncode is None: + pid, sts = self._wait(0) + self._handle_exitstatus(sts) + return self.returncode + + if not mswindows: + def poll(self, _deadstate=None): + """Check if child process has terminated. Returns returncode + attribute.""" + if self.returncode is None: + try: + pid, sts = self._wait(os.WNOHANG) + if pid == self.pid: + self._handle_exitstatus(sts) + except os.error: + if _deadstate is not None: + self.returncode = _deadstate + return self.returncode + + def wait(self, timeout=-1, group=True): """Wait for the process to terminate. Returns returncode attribute. If timeout seconds are reached and the process has not terminated, @@ -198,8 +243,7 @@ def wait(self, timeout=-1, group=True): self.returncode = winprocess.GetExitCodeProcess(self._handle) else: if timeout == -1: - subprocess.Popen.wait(self) - return self.returncode + return self._waitforstatus() starttime = time.time() @@ -207,7 +251,7 @@ def wait(self, timeout=-1, group=True): oldsignal = signal.signal(signal.SIGCHLD, DoNothing) while time.time() < starttime + timeout - 0.01: - pid, sts = os.waitpid(self.pid, os.WNOHANG) + pid, sts = self._wait(os.WNOHANG) if pid != 0: self._handle_exitstatus(sts) signal.signal(signal.SIGCHLD, oldsignal) @@ -222,7 +266,7 @@ def wait(self, timeout=-1, group=True): except OSError: # Process might have finished since we last checked # so wait again - pid, sts = os.waitpid(self.pid, os.WNOHANG) + pid, sts = self._wait(os.WNOHANG) if pid != 0: self._handle_exitstatus(sts) signal.signal(signal.SIGCHLD, oldsignal) @@ -232,6 +276,6 @@ def wait(self, timeout=-1, group=True): raise signal.signal(signal.SIGCHLD, oldsignal) - subprocess.Popen.wait(self) + self._waitforstatus() return self.returncode From d240ac9ffcaa62a6bcc1560b7300a0097b860092 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Thu, 6 Dec 2007 21:31:55 +0000 Subject: [PATCH 09/20] Add GetProcessTimes() function. git-svn-id: svn+ssh://svn.dlogics.com/svn/dev/Packages/python_processes/trunk@23519 a347f3a1-5518-0410-bf23-827ba1738e83 --- winprocess.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/winprocess.py b/winprocess.py index 4d867fe..6de2899 100755 --- a/winprocess.py +++ b/winprocess.py @@ -24,7 +24,7 @@ # DEALINGS IN THE SOFTWARE. from ctypes import c_void_p, POINTER, sizeof, Structure, windll, WinError, WINFUNCTYPE -from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LPCWSTR, LPWSTR, UINT, WORD +from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LPCWSTR, LPWSTR, UINT, WORD, ULARGE_INTEGER LPVOID = c_void_p LPBYTE = POINTER(BYTE) @@ -260,3 +260,26 @@ def ErrCheckResumeThread(result, func, args): ("GetExitCodeProcess", windll.kernel32), GetExitCodeProcessFlags) GetExitCodeProcess.errcheck = ErrCheckBool + + +# GetProcessTimes() +LPULARGE_INTEGER = POINTER(ULARGE_INTEGER) + +GetProcessTimesProto = WINFUNCTYPE(BOOL, # Return Type + HANDLE, # hProcess + LPULARGE_INTEGER, # lpCreationTime + LPULARGE_INTEGER, # lpExitTime + LPULARGE_INTEGER, # lpKernelTime + LPULARGE_INTEGER, # lpUserTime + ) +GetProcessTimesFlags = ((1, "hProcess"), + (2, "lpCreationTime"), + (2, "lpExitTime"), + (2, "lpKernelTime"), + (2, "lpUserTime")) + +GetProcessTimes = GetProcessTimesProto( + ("GetProcessTimes", windll.kernel32), + GetProcessTimesFlags) + +GetProcessTimes.errcheck = ErrCheckBool From 54aa4eebc23c6fbe5dfa10a14a437ce5e21cd7e4 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Thu, 6 Dec 2007 21:32:56 +0000 Subject: [PATCH 10/20] Factor out calls to GetExitCodeProcess into _getstatus(), and there also get timings. git-svn-id: svn+ssh://svn.dlogics.com/svn/dev/Packages/python_processes/trunk@23520 a347f3a1-5518-0410-bf23-827ba1738e83 --- killableprocess.py | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/killableprocess.py b/killableprocess.py index 30f2dd7..d9335b9 100755 --- a/killableprocess.py +++ b/killableprocess.py @@ -201,15 +201,38 @@ def _wait(self, options): self.stime = rusage[1] return pid, sts - def _waitforstatus(self): - """Wait for child process to terminate. Returns returncode - attribute.""" - if self.returncode is None: - pid, sts = self._wait(0) - self._handle_exitstatus(sts) - return self.returncode + if mswindows: + def _getstatus(self): + self.returncode = winprocess.GetExitCodeProcess(self._handle) - if not mswindows: + creation_time, exit_time, kernel_time, user_time = winprocess.GetProcessTimes(self._handle) + + print creation_time, exit_time, kernel_time, user_time + + self.rtime = time.time() - self._starttime + # MS Windows times are in 100ns units, convert to seconds + self.utime = user_time / 10000000.0 + self.stime = kernel_time / 10000000.0 + + return self.returncode + else: + def _waitforstatus(self): + """Wait for child process to terminate. Returns returncode + attribute.""" + if self.returncode is None: + pid, sts = self._wait(0) + self._handle_exitstatus(sts) + return self.returncode + + if mswindows: + def poll(self, _deadstate=None): + """Check if child process has terminated. Returns returncode + attribute.""" + if self.returncode is None: + if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0: + self._getstatus() + return self.returncode + else: def poll(self, _deadstate=None): """Check if child process has terminated. Returns returncode attribute.""" @@ -240,7 +263,7 @@ def wait(self, timeout=-1, group=True): if rc == winprocess.WAIT_TIMEOUT: self.kill(group) else: - self.returncode = winprocess.GetExitCodeProcess(self._handle) + self._getstatus() else: if timeout == -1: return self._waitforstatus() From c3c83a44019461830884704c8a4c704bf81a59b6 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Thu, 6 Dec 2007 21:34:54 +0000 Subject: [PATCH 11/20] Remove debugging print statement. git-svn-id: svn+ssh://svn.dlogics.com/svn/dev/Packages/python_processes/trunk@23521 a347f3a1-5518-0410-bf23-827ba1738e83 --- killableprocess.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/killableprocess.py b/killableprocess.py index d9335b9..63a8692 100755 --- a/killableprocess.py +++ b/killableprocess.py @@ -207,8 +207,6 @@ def _getstatus(self): creation_time, exit_time, kernel_time, user_time = winprocess.GetProcessTimes(self._handle) - print creation_time, exit_time, kernel_time, user_time - self.rtime = time.time() - self._starttime # MS Windows times are in 100ns units, convert to seconds self.utime = user_time / 10000000.0 From 2eea0164bbb1ab6fe907308d6377d357a61440fc Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Fri, 14 Dec 2007 19:23:49 +0000 Subject: [PATCH 12/20] - AIX 5 has a bug where wait4() doesn't wait for exited processes by default. wait3() takes whatever options are specified and ORs in the WEXITED option before calling the kwaitpid() function. (kwaitpid() is a syscall that all wait*() functions call). So, we can get around the problem by adding WEXITED ourselves if it exists. We define WEXITED on AIX using the value 4 gotten from sys/wait.h This fix may be necessary on other OSes as well. Fixes a problem with every test on AIX timing out. - Borrow an idea from subprocess.py and make our os.wait4() call try again if interrupted by a signal. git-svn-id: svn+ssh://svn.dlogics.com/svn/dev/Packages/python_processes/trunk@23641 a347f3a1-5518-0410-bf23-827ba1738e83 --- killableprocess.py | 49 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/killableprocess.py b/killableprocess.py index 63a8692..3ac5004 100755 --- a/killableprocess.py +++ b/killableprocess.py @@ -64,11 +64,32 @@ def __str__(self): return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) mswindows = (sys.platform == "win32") +aix5 = (sys.platform == "aix5") + +# WEXITED fix +if aix5: + # AIX 5 has a bug where wait4() doesn't wait for exited processes by + # default. wait3() takes whatever options are specified and ORs in the + # WEXITED option before calling the kwaitpid() function. (kwaitpid() + # is a syscall that all wait*() functions call). + # + # So, we can get around the problem by adding WEXITED ourselves if it + # exists. We define WEXITED on AIX using the value 4 gotten from sys/wait.h + # + # This fix may be necessary on other OSes as well. + WEXITED = 0x04 # From /usr/include/sys/wait.h +else: + try: + # In case this value gets into the os module + WEXITED = os.WEXITED + except AttributeError: + WEXITED = 0 if mswindows: import winprocess else: import signal + import errno def call(*args, **kwargs): waitargs = {} @@ -192,14 +213,26 @@ def kill(self, group=True): os.kill(self.pid, signal.SIGKILL) self.returncode = -9 - def _wait(self, options): - """Wait for our pid, gathering resource usage if available""" - pid, sts, rusage = os.wait4(self.pid, options) - if pid: - self.rtime = time.time() - self._starttime - self.utime = rusage[0] - self.stime = rusage[1] - return pid, sts + if not mswindows: + def _wait(self, options): + """Wait for our pid, gathering resource usage if available""" + pid, sts, rusage = self._wait4_no_intr(self.pid, options) + if pid: + self.rtime = time.time() - self._starttime + self.utime = rusage[0] + self.stime = rusage[1] + return pid, sts + + def _wait4_no_intr(self, pid, options): + """Like os.wait4, but retries on EINTR""" + while True: + try: + return os.wait4(pid, options | WEXITED) + except OSError, e: + if e.errno == errno.EINTR: + continue + else: + raise if mswindows: def _getstatus(self): From 62439381da5cb637d907c3f3c6acbdc47f4dfb20 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Thu, 20 Dec 2007 20:51:00 +0000 Subject: [PATCH 13/20] - If a system doesn't have wait4() (I'm looking at you, HP-UX), use wait3() and verify that we got the subprocess we were waiting for. Good enough for testing. - Replace the way we time out subprocesses. Sometimes, signals aren't delivered to the main thread (I'm looking at you again, HP-UX), and so we can't rely on the interruption of time.sleep. So, instead, we create a thread that waits for the timeout period and then kills the subprocess. The thread is doing the waiting on an Event, so the main thread, on discovering that the subprocess has exited, can unblock the killer thread and let it exit. git-svn-id: svn+ssh://svn.dlogics.com/svn/dev/Packages/python_processes/trunk@23693 a347f3a1-5518-0410-bf23-827ba1738e83 --- killableprocess.py | 68 +++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/killableprocess.py b/killableprocess.py index 3ac5004..fa003bb 100755 --- a/killableprocess.py +++ b/killableprocess.py @@ -48,6 +48,7 @@ import os import time import types +import threading try: from subprocess import CalledProcessError @@ -109,10 +110,6 @@ def check_call(*args, **kwargs): cmd = args[0] raise CalledProcessError(retcode, cmd) -if not mswindows: - def DoNothing(*args): - pass - class Popen(subprocess.Popen): if not mswindows: # Override __init__ to set a preexec_fn @@ -223,11 +220,23 @@ def _wait(self, options): self.stime = rusage[1] return pid, sts + try: + _wait4 = os.wait4 + except AttributeError: + # Don't have wait4 so try wait3 with a check + # this only works if we are waiting for one process + @staticmethod + def _wait4(pid, options): + rpid, status, rusage = os.wait3(options) + if rpid and rpid != pid: + raise Exception("Waiting for pid %d but unexpected pid %d exited" % (pid, rpid)) + return rpid, status, rusage + def _wait4_no_intr(self, pid, options): """Like os.wait4, but retries on EINTR""" while True: try: - return os.wait4(pid, options | WEXITED) + return self._wait4(pid, options | WEXITED) except OSError, e: if e.errno == errno.EINTR: continue @@ -298,38 +307,29 @@ def wait(self, timeout=-1, group=True): else: if timeout == -1: return self._waitforstatus() - - starttime = time.time() - # Make sure there is a signal handler for SIGCHLD installed - oldsignal = signal.signal(signal.SIGCHLD, DoNothing) + # An event indicating that the main thread saw the process exit + doneevent = threading.Event() - while time.time() < starttime + timeout - 0.01: - pid, sts = self._wait(os.WNOHANG) - if pid != 0: - self._handle_exitstatus(sts) - signal.signal(signal.SIGCHLD, oldsignal) - return self.returncode - - # time.sleep is interrupted by signals (good!) - newtimeout = timeout - time.time() + starttime - time.sleep(newtimeout) + def killerthread(): + """Wait for the timeout, then attempt to kill the related process.""" + doneevent.wait(timeout) + try: + self.kill(group) + except OSError: + pass + + # Make a thread that will kill the subprocess after waiting for the timeout. + thd = threading.Thread(target=killerthread) + thd.setDaemon(True) + thd.start() try: - self.kill(group) - except OSError: - # Process might have finished since we last checked - # so wait again - pid, sts = self._wait(os.WNOHANG) - if pid != 0: - self._handle_exitstatus(sts) - signal.signal(signal.SIGCHLD, oldsignal) - return self.returncode - else: - # strange, waiting failed, propagate original exception - raise - - signal.signal(signal.SIGCHLD, oldsignal) - self._waitforstatus() + self._waitforstatus() + finally: + # Set the event, so if the killer thread is still waiting, it will continue through exit + doneevent.set() + # Join up with the thread, which should be exiting now + thd.join() return self.returncode From 2d954a3c3f145fe41af583c6359a7d0c5bf686e5 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Wed, 2 Jan 2008 18:29:24 +0000 Subject: [PATCH 14/20] Make svn ignorant of lots of generated files and directories git-svn-id: svn+ssh://svn.dlogics.com/svn/dev/Packages/python_processes/trunk@23820 a347f3a1-5518-0410-bf23-827ba1738e83 From d3818bda6af31f7f63ee8efa5cc2bd4564cf2043 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Sun, 2 Nov 2008 15:44:05 +0000 Subject: [PATCH 15/20] Apply SVN autoprops to DevTest dir tree - These are the standard DL properties in the SVN config. - Fixes keywords, mime-type, eol-style. - Things like SConstruct files will now have native line endings. - Shell scripts look like they all get LF git-svn-id: svn+ssh://svn.dlogics.com/svn/dev/Packages/python_processes/trunk@31969 a347f3a1-5518-0410-bf23-827ba1738e83 From 7181097e8d229a7ab876d2caf9930f07dc869aac Mon Sep 17 00:00:00 2001 From: "Kevin A. Mitchell" Date: Tue, 15 Dec 2009 12:56:11 -0600 Subject: [PATCH 16/20] Turn this into a distributable Python package. --- setup.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 setup.py diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..3168f3f --- /dev/null +++ b/setup.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +from distutils.core import setup + +setup(name='python_processes', + version='1.0', # A DL-specific version number + description='Extensions to the subprocess module', + author='Benjamin Smedberg', + author_email='benjamin@smedbergs.us', + url='http://benjamin.smedbergs.us/blog/2006-11-09/adventures-in-python-launching-subprocesses/', + package_dir={'python_processes' : ''}, + packages=['python_processes']) From 726d597065713cb491e2c5480ab18d5474c62103 Mon Sep 17 00:00:00 2001 From: "Kevin A. Mitchell" Date: Thu, 17 Dec 2009 17:19:20 -0600 Subject: [PATCH 17/20] Ignore files from builds. --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..31f264e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.pyc +/MANIFEST +/build +/dist From 728549e1c5e22f75eb90d18554a858f8b7c97f2a Mon Sep 17 00:00:00 2001 From: "Kevin A. Mitchell" Date: Thu, 17 Dec 2009 17:20:14 -0600 Subject: [PATCH 18/20] Move Python modules into actual python_processes package directory. - Fixes problems with pip -e installs. - No more silly setup.py tricks. - Incremented version to 1.0.1. --- __init__.py => python_processes/__init__.py | 0 killableprocess.py => python_processes/killableprocess.py | 0 winprocess.py => python_processes/winprocess.py | 0 setup.py | 3 +-- 4 files changed, 1 insertion(+), 2 deletions(-) rename __init__.py => python_processes/__init__.py (100%) rename killableprocess.py => python_processes/killableprocess.py (100%) rename winprocess.py => python_processes/winprocess.py (100%) diff --git a/__init__.py b/python_processes/__init__.py similarity index 100% rename from __init__.py rename to python_processes/__init__.py diff --git a/killableprocess.py b/python_processes/killableprocess.py similarity index 100% rename from killableprocess.py rename to python_processes/killableprocess.py diff --git a/winprocess.py b/python_processes/winprocess.py similarity index 100% rename from winprocess.py rename to python_processes/winprocess.py diff --git a/setup.py b/setup.py index 3168f3f..14ddba5 100644 --- a/setup.py +++ b/setup.py @@ -3,10 +3,9 @@ from distutils.core import setup setup(name='python_processes', - version='1.0', # A DL-specific version number + version='1.0.1', # A DL-specific version number description='Extensions to the subprocess module', author='Benjamin Smedberg', author_email='benjamin@smedbergs.us', url='http://benjamin.smedbergs.us/blog/2006-11-09/adventures-in-python-launching-subprocesses/', - package_dir={'python_processes' : ''}, packages=['python_processes']) From d06221b3f350e69c7974b333793bf9967e44181a Mon Sep 17 00:00:00 2001 From: Rob Boehne Date: Tue, 21 Mar 2017 09:24:05 -0500 Subject: [PATCH 19/20] Add a bit of trickery to adapt to a change in the python subprocess module between Python 2.5 and 2.7 (don't worry about 2.6). Add in the functionality to close file descriptors from python2.7's subprocess module for windows. --- python_processes/killableprocess.py | 81 ++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 14 deletions(-) diff --git a/python_processes/killableprocess.py b/python_processes/killableprocess.py index fa003bb..c6e0611 100755 --- a/python_processes/killableprocess.py +++ b/python_processes/killableprocess.py @@ -134,12 +134,12 @@ def setpgid_preexec_fn(): subprocess.Popen.__init__(self, *args, **kwargs) if mswindows: - def _execute_child(self, args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, startupinfo, - creationflags, shell, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite): + def _internal_execute_child(self, args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, startupinfo, + creationflags, shell, to_close, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite): # DLADD kam 04Dec07 Add real, user and system timings self._starttime = time.time() @@ -165,20 +165,45 @@ def _execute_child(self, args, executable, preexec_fn, close_fds, comspec = os.environ.get("COMSPEC", "cmd.exe") args = comspec + " /c " + args + def _close_in_parent(fd): + fd.Close() + if fd in to_close: + to_close.remove(fd) + # We create a new job for this process, so that we can kill # the process and any sub-processes self._job = winprocess.CreateJobObject() creationflags |= winprocess.CREATE_SUSPENDED creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT - - hp, ht, pid, tid = winprocess.CreateProcess( - executable, args, - None, None, # No special security - 1, # Must inherit handles! - creationflags, - winprocess.EnvironmentBlock(env), - cwd, startupinfo) + try: + hp, ht, pid, tid = winprocess.CreateProcess( + executable, args, + None, None, # No special security + int(not close_fds), + creationflags, + winprocess.EnvironmentBlock(env), + cwd, + startupinfo) + except pywintypes.error, e: + # Translate pywintypes.error to WindowsError, which is + # a subclass of OSError. FIXME: We should really + # translate errno using _sys_errlist (or similar), but + # how can this be done from Python? + raise WindowsError(*e.args) + finally: + # Child is launched. Close the parent's copy of those pipe + # handles that only the child should have open. You need + # to make sure that no handles to the write end of the + # output pipe are maintained in this process or else the + # pipe will not close when the child process exits and the + # ReadFile will hang. + if p2cread is not None: + _close_in_parent(p2cread) + if c2pwrite is not None: + _close_in_parent(c2pwrite) + if errwrite is not None: + _close_in_parent(errwrite) self._child_created = True self._handle = hp @@ -195,6 +220,34 @@ def _execute_child(self, args, executable, preexec_fn, close_fds, if errwrite is not None: errwrite.Close() + if sys.version_info[:2] != (2, 7): + def _execute_child(self, args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, startupinfo, + creationflags, shell, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite): + return self._internal_execute_child(args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, startupinfo, + creationflags, shell, [p2cread, p2cwrite, errwrite], + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) + else: + def _execute_child(self, args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, startupinfo, + creationflags, shell, to_close, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite): + return self._internal_execute_child( args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, startupinfo, + creationflags, shell, to_close, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) + + def kill(self, group=True): """Kill the process. If group=True, all sub-processes will also be killed.""" if mswindows: From 73cc9f5aab8812e8ab3329ec0bf2e35d5c567295 Mon Sep 17 00:00:00 2001 From: Rob Boehne Date: Tue, 21 Mar 2017 21:12:00 -0500 Subject: [PATCH 20/20] Increment the micro version to 1.0.2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 14ddba5..1ac611e 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from distutils.core import setup setup(name='python_processes', - version='1.0.1', # A DL-specific version number + version='1.0.2', # A DL-specific version number description='Extensions to the subprocess module', author='Benjamin Smedberg', author_email='benjamin@smedbergs.us',