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
--- 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)