Bug 892902 - [mozprocess] Remove dependency of psutil for pid exists check. draft
authorHenrik Skupin <mail@hskupin.info>
Mon, 12 Feb 2018 16:39:57 +0100
changeset 755009 9a838004c10eb1da1e4f82a802d479f78bb01648
parent 754399 38b3c1d03a594664c6b32c35533734283c258f43
child 755010 a3db45a81ea3ad649c421284672c5fe2633c2e8c
child 755091 c041718bc254bf255efccaf49d780cff13ca8c84
child 755097 c637a3c76970e92978a9492912495a91d475b741
push id99096
push userbmo:hskupin@gmail.com
push dateWed, 14 Feb 2018 21:02:06 +0000
bugs892902
milestone60.0a1
Bug 892902 - [mozprocess] Remove dependency of psutil for pid exists check. The psutil package has only been used to check for the existence of a given pid. Given the troubles with getting psutil compiled on Windows, or by supplying the correct wheel, it has been decided to get rid of this dependency. Instead the ProcessHandler class itself now got the feature to determine the existence of a pid by using ctypes to do the necessary Windows API calls. MozReview-Commit-ID: KAiSv0AH8HZ
testing/mozbase/mozprocess/mozprocess/processhandler.py
testing/mozbase/mozprocess/mozprocess/winprocess.py
testing/mozbase/mozprocess/tests/proctest.py
--- a/testing/mozbase/mozprocess/mozprocess/processhandler.py
+++ b/testing/mozbase/mozprocess/mozprocess/processhandler.py
@@ -7,19 +7,21 @@ from __future__ import absolute_import, 
 import errno
 import os
 import signal
 import subprocess
 import sys
 import threading
 import time
 import traceback
+
 from Queue import Queue, Empty
 from datetime import datetime
 
+
 __all__ = ['ProcessHandlerMixin', 'ProcessHandler', 'LogOutput',
            'StoreOutput', 'StreamOutput']
 
 # Set the MOZPROCESS_DEBUG environment variable to 1 to see some debugging output
 MOZPROCESS_DEBUG = os.getenv("MOZPROCESS_DEBUG")
 
 # We dont use mozinfo because it is expensive to import, see bug 933558.
 isWin = os.name == "nt"
@@ -867,16 +869,47 @@ falling back to not using job objects fo
         print("MOZPROCESS WARNING: ProcessHandler.waitForFinish() is deprecated, "
               "use ProcessHandler.wait() instead", file=sys.stderr)
         return self.wait(timeout=timeout)
 
     @property
     def pid(self):
         return self.proc.pid
 
+    @staticmethod
+    def pid_exists(pid):
+        if pid < 0:
+            return False
+
+        if isWin:
+            try:
+                process = winprocess.OpenProcess(
+                    winprocess.PROCESS_QUERY_INFORMATION | winprocess.PROCESS_VM_READ, False, pid)
+                return winprocess.GetExitCodeProcess(process) == winprocess.STILL_ACTIVE
+
+            except WindowsError as e:
+                # no such process
+                if e.winerror == winprocess.ERROR_INVALID_PARAMETER:
+                    return False
+
+                # access denied
+                if e.winerror == winprocess.ERROR_ACCESS_DENIED:
+                    return True
+
+                # re-raise for any other type of exception
+                raise
+
+        elif isPosix:
+            try:
+                os.kill(pid, 0)
+            except OSError as e:
+                return e.errno == errno.EPERM
+            else:
+                return True
+
     @classmethod
     def _getpgid(cls, pid):
         try:
             return os.getpgid(pid)
         except OSError as e:
             # Do not raise for "No such process"
             if e.errno != errno.ESRCH:
                 raise
--- a/testing/mozbase/mozprocess/mozprocess/winprocess.py
+++ b/testing/mozbase/mozprocess/mozprocess/winprocess.py
@@ -160,17 +160,22 @@ class EnvironmentBlock:
                 if isinstance(v, bytes):
                     v = v.decode(fs_encoding, 'replace')
                 values.append("{}={}".format(k, v))
             values.append("")
             self._as_parameter_ = LPCWSTR("\0".join(values))
 
 
 # Error Messages we need to watch for go here
-# See: http://msdn.microsoft.com/en-us/library/ms681388%28v=vs.85%29.aspx
+
+# https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx (0 - 499)
+ERROR_ACCESS_DENIED = 5
+ERROR_INVALID_PARAMETER = 87
+
+# http://msdn.microsoft.com/en-us/library/ms681388%28v=vs.85%29.aspx (500 - 999)
 ERROR_ABANDONED_WAIT_0 = 735
 
 # GetLastError()
 GetLastErrorProto = WINFUNCTYPE(DWORD)  # Return Type
 GetLastErrorFlags = ()
 GetLastError = GetLastErrorProto(("GetLastError", windll.kernel32), GetLastErrorFlags)
 
 # CreateProcess()
@@ -246,16 +251,45 @@ JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS = 8
 JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT = 9
 JOB_OBJECT_MSG_JOB_MEMORY_LIMIT = 10
 
 # See winbase.h
 DEBUG_ONLY_THIS_PROCESS = 0x00000002
 DEBUG_PROCESS = 0x00000001
 DETACHED_PROCESS = 0x00000008
 
+# OpenProcess -
+# https://msdn.microsoft.com/en-us/library/windows/desktop/ms684320(v=vs.85).aspx
+PROCESS_QUERY_INFORMATION = 0x0400
+PROCESS_VM_READ = 0x0010
+
+OpenProcessProto = WINFUNCTYPE(
+    HANDLE,  # Return type
+    DWORD,   # dwDesiredAccess
+    BOOL,    # bInheritHandle
+    DWORD,   # dwProcessId
+)
+
+OpenProcessFlags = (
+    (1, "dwDesiredAccess", 0),
+    (1, "bInheritHandle", False),
+    (1, "dwProcessId", 0),
+)
+
+
+def ErrCheckOpenProcess(result, func, args):
+    ErrCheckBool(result, func, args)
+
+    return AutoHANDLE(result)
+
+
+OpenProcess = OpenProcessProto(("OpenProcess", windll.kernel32),
+                               OpenProcessFlags)
+OpenProcess.errcheck = ErrCheckOpenProcess
+
 # GetQueuedCompletionPortStatus -
 # http://msdn.microsoft.com/en-us/library/aa364986%28v=vs.85%29.aspx
 GetQueuedCompletionStatusProto = WINFUNCTYPE(BOOL,         # Return Type
                                              HANDLE,       # Completion Port
                                              LPDWORD,      # Msg ID
                                              LPULONG,      # Completion Key
                                              # PID Returned from the call (may be null)
                                              LPULONG,
--- a/testing/mozbase/mozprocess/tests/proctest.py
+++ b/testing/mozbase/mozprocess/tests/proctest.py
@@ -1,14 +1,16 @@
 from __future__ import absolute_import
 
 import os
 import sys
 import unittest
-import psutil
+
+from mozprocess import ProcessHandler
+
 
 here = os.path.dirname(os.path.abspath(__file__))
 
 
 class ProcTest(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls):
@@ -22,17 +24,17 @@ class ProcTest(unittest.TestCase):
             proc -- the processhandler instance
             isalive -- Use True to indicate we pass if the process exists; however, by default
                        the test will pass if the process does not exist (isalive == False)
             expectedfail -- Defaults to [], used to indicate a list of fields
                             that are expected to fail
         """
         returncode = proc.proc.returncode
         didtimeout = proc.didTimeout
-        detected = psutil.pid_exists(proc.pid)
+        detected = ProcessHandler.pid_exists(proc.pid)
         output = ''
         # ProcessHandler has output when store_output is set to True in the constructor
         # (this is the default)
         if getattr(proc, 'output'):
             output = proc.output
 
         if 'returncode' in expectedfail:
             self.assertTrue(returncode, "Detected an unexpected return code of: %s" % returncode)