diff --git a/pylink/jlock.py b/pylink/jlock.py index b08114a..049097b 100644 --- a/pylink/jlock.py +++ b/pylink/jlock.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import psutil +from . import util import errno import tempfile @@ -103,7 +103,7 @@ def acquire(self): # In the case that the lockfile exists, but the pid does not # correspond to a valid process, remove the file. - if not psutil.pid_exists(pid): + if not util.pid_exists(pid): os.remove(self.path) except ValueError as e: diff --git a/pylink/util.py b/pylink/util.py index d9216a3..394c2b4 100644 --- a/pylink/util.py +++ b/pylink/util.py @@ -17,6 +17,8 @@ import platform import sys +import os +import ctypes def is_integer(val): @@ -180,3 +182,64 @@ def calculate_parity(n): y += n & 1 n = n >> 1 return y & 1 + + +def pid_exists_win32(pid): + ERROR_ACCESS_DENIED = 0x5 + ERROR_INVALID_PARAMETER = 0x57 + PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 + STILL_ACTIVE = 0x103 + + if pid == 0: + return True + elif pid == -1: + return False + + hProcess = ctypes.windll.kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid) + if not hProcess: + error = ctypes.windll.kernel32.GetLastError() + if error == ERROR_ACCESS_DENIED: + return True + elif error == ERROR_INVALID_PARAMETER: + return False + else: + raise ctypes.WinError() + + exitCode = ctypes.c_int(0) + pExitCode = ctypes.pointer(exitCode) + if not ctypes.windll.kernel32.GetExitCodeProcess(hProcess, pExitCode): + error = ctypes.windll.kernel32.GetLastError() + if error == ERROR_ACCESS_DENIED: + ctypes.windll.kernel32.CloseHandle(hProcess) + return True + else: + raise ctypes.WinError() + + if exitCode.value == STILL_ACTIVE: + ctypes.windll.kernel32.CloseHandle(hProcess) + return True + + ctypes.windll.kernel32.CloseHandle(hProcess) + return False + + +def pid_exists_posix(pid): + if pid == 0: + return True + elif pid < 0: + return False + + try: + os.kill(pid, 0) + except ProcessLookupError: + return False + except PermissionError: + return True + return True + + +def pid_exists(pid): + if sys.platform.startswith('win') or sys.platform.startswith('cygwin'): + return pid_exists_win32(pid) + else: + return pid_exists_posix(pid) diff --git a/requirements.txt b/requirements.txt index fa7e8ea..fa39b41 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,6 @@ behave == 1.2.5 coverage == 4.4.1 future -psutil >= 5.2.2 pycodestyle >= 2.3.1 six sphinx == 1.4.8 diff --git a/setup.py b/setup.py index bb09e58..aa3d2bd 100644 --- a/setup.py +++ b/setup.py @@ -14,13 +14,6 @@ import sys -# Stub the 'psutil' module that is required by this package in order to enable -# the main module to be imported. -try: - import psutil -except ImportError: - sys.modules['psutil'] = {} - import pylink import os @@ -255,7 +248,6 @@ def long_description(): # Dependencies. install_requires=[ - 'psutil >= 5.2.2', 'future', 'six' ], diff --git a/tests/unit/test_jlock.py b/tests/unit/test_jlock.py index 7913b86..392b8aa 100644 --- a/tests/unit/test_jlock.py +++ b/tests/unit/test_jlock.py @@ -74,7 +74,7 @@ def test_jlock_init_and_delete(self): @mock.patch('os.open') @mock.patch('os.write') @mock.patch('os.remove') - @mock.patch('pylink.jlock.psutil') + @mock.patch('pylink.jlock.util') @mock.patch('pylink.jlock.open') def test_jlock_acquire_exists(self, mock_open, mock_util, mock_rm, mock_wr, mock_op, mock_exists, mock_close): """Tests trying to acquire when the lock exists for an active process. @@ -82,7 +82,7 @@ def test_jlock_acquire_exists(self, mock_open, mock_util, mock_rm, mock_wr, mock Args: self (TestJLock): the ``TestJLock`` instance mock_open (Mock): mocked built-in open method - mock_util (Mock): mocked ``psutil`` module + mock_util (Mock): mocked ``util`` module mock_rm (Mock): mocked os remove method mock_wr (Mock): mocked os write method mock_op (Mock): mocked os open method @@ -122,7 +122,7 @@ def test_jlock_acquire_exists(self, mock_open, mock_util, mock_rm, mock_wr, mock @mock.patch('os.open') @mock.patch('os.write') @mock.patch('os.remove') - @mock.patch('pylink.jlock.psutil') + @mock.patch('pylink.jlock.util') @mock.patch('pylink.jlock.open') def test_jlock_acquire_os_error(self, mock_open, mock_util, mock_rm, mock_wr, mock_op, mock_exists, mock_close): """Tests trying to acquire the lock but generating an os-level error. @@ -130,7 +130,7 @@ def test_jlock_acquire_os_error(self, mock_open, mock_util, mock_rm, mock_wr, mo Args: self (TestJLock): the ``TestJLock`` instance mock_open (Mock): mocked built-in open method - mock_util (Mock): mocked ``psutil`` module + mock_util (Mock): mocked ``util`` module mock_rm (Mock): mocked os remove method mock_wr (Mock): mocked os write method mock_op (Mock): mocked os open method @@ -167,7 +167,7 @@ def test_jlock_acquire_os_error(self, mock_open, mock_util, mock_rm, mock_wr, mo @mock.patch('os.open') @mock.patch('os.write') @mock.patch('os.remove') - @mock.patch('pylink.jlock.psutil') + @mock.patch('pylink.jlock.util') @mock.patch('pylink.jlock.open') def test_jlock_acquire_bad_file(self, mock_open, mock_util, mock_rm, mock_wr, mock_op, mock_exists, mock_close): """Tests acquiring the lockfile when the current lockfile is invallid. @@ -175,7 +175,7 @@ def test_jlock_acquire_bad_file(self, mock_open, mock_util, mock_rm, mock_wr, mo Args: self (TestJLock): the ``TestJLock`` instance mock_open (Mock): mocked built-in open method - mock_util (Mock): mocked ``psutil`` module + mock_util (Mock): mocked ``util`` module mock_rm (Mock): mocked os remove method mock_wr (Mock): mocked os write method mock_op (Mock): mocked os open method @@ -216,7 +216,7 @@ def test_jlock_acquire_bad_file(self, mock_open, mock_util, mock_rm, mock_wr, mo @mock.patch('os.open') @mock.patch('os.write') @mock.patch('os.remove') - @mock.patch('pylink.jlock.psutil') + @mock.patch('pylink.jlock.util') @mock.patch('pylink.jlock.open') def test_jlock_acquire_invalid_pid(self, mock_open, mock_util, mock_rm, mock_wr, mock_op, mock_exists, mock_close): """Tests acquiring the lockfile when the pid in the lockfile is invalid. @@ -224,7 +224,7 @@ def test_jlock_acquire_invalid_pid(self, mock_open, mock_util, mock_rm, mock_wr, Args: self (TestJLock): the ``TestJLock`` instance mock_open (Mock): mocked built-in open method - mock_util (Mock): mocked ``psutil`` module + mock_util (Mock): mocked ``util`` module mock_rm (Mock): mocked os remove method mock_wr (Mock): mocked os write method mock_op (Mock): mocked os open method @@ -263,7 +263,7 @@ def test_jlock_acquire_invalid_pid(self, mock_open, mock_util, mock_rm, mock_wr, @mock.patch('os.open') @mock.patch('os.write') @mock.patch('os.remove') - @mock.patch('pylink.jlock.psutil') + @mock.patch('pylink.jlock.util') @mock.patch('pylink.jlock.open') def test_jlock_acquire_old_pid(self, mock_open, mock_util, mock_rm, mock_wr, mock_op, mock_exists, mock_close): """Tests acquiring when the PID in the lockfile does not exist. @@ -271,7 +271,7 @@ def test_jlock_acquire_old_pid(self, mock_open, mock_util, mock_rm, mock_wr, moc Args: self (TestJLock): the ``TestJLock`` instance mock_open (Mock): mocked built-in open method - mock_util (Mock): mocked ``psutil`` module + mock_util (Mock): mocked ``util`` module mock_rm (Mock): mocked os remove method mock_wr (Mock): mocked os write method mock_op (Mock): mocked os open method