summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2017-01-09 16:49:27 +0100
committerGiampaolo Rodola <g.rodola@gmail.com>2017-01-09 16:49:27 +0100
commit250e3a51c13840501e01a7000b476742a220ea46 (patch)
tree23fc655b33a1d99030b456248b8599269491f0a0
parent5bf37636dbaa38a335eaa9df02aa950cfbf4848b (diff)
downloadpsutil-250e3a51c13840501e01a7000b476742a220ea46.tar.gz
#687: [Linux] pid_exists() no longer returns True if passed a process thread ID
-rw-r--r--HISTORY.rst4
-rw-r--r--docs/index.rst8
-rw-r--r--psutil/_pslinux.py28
-rwxr-xr-xpsutil/tests/test_linux.py19
-rwxr-xr-xpsutil/tests/test_process.py10
5 files changed, 56 insertions, 13 deletions
diff --git a/HISTORY.rst b/HISTORY.rst
index 94feab8e..92413ec1 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,8 +3,12 @@
5.0.2
=====
+*XXXX-XX-XX*
+
**Bug fixes**
+- 687_: [Linux] pid_exists() no longer returns True if passed a process thread
+ ID.
- 948_: cannot install psutil with PYTHONOPTIMIZE=2.
diff --git a/docs/index.rst b/docs/index.rst
index 3aed8094..022c6a6b 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -738,10 +738,12 @@ Process class
.. class:: Process(pid=None)
- Represents an OS process with the given *pid*. If *pid* is omitted current
- process *pid* (`os.getpid() <http://docs.python.org/library/os.html#os.getpid>`__)
- is used.
+ Represents an OS process with the given *pid*.
+ If *pid* is omitted current process *pid*
+ (`os.getpid() <http://docs.python.org/library/os.html#os.getpid>`__) is used.
Raise :class:`NoSuchProcess` if *pid* does not exist.
+ On Linux *pid* can also refer to a thread ID (the *id* field returned by
+ :meth:`threads` method).
When accessing methods of this class always be prepared to catch
:class:`NoSuchProcess`, :class:`ZombieProcess` and :class:`AccessDenied`
exceptions.
diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py
index 91fdae4f..fe2f459d 100644
--- a/psutil/_pslinux.py
+++ b/psutil/_pslinux.py
@@ -1070,8 +1070,32 @@ def pids():
def pid_exists(pid):
- """Check For the existence of a unix pid."""
- return _psposix.pid_exists(pid)
+ """Check for the existence of a unix PID."""
+ if not _psposix.pid_exists(pid):
+ return False
+ else:
+ # Linux's apparently does not distinguish between PIDs and TIDs
+ # (thread IDs).
+ # listdir("/proc") won't show any TID (only PIDs) but
+ # os.stat("/proc/{tid}") will succeed if {tid} exists.
+ # os.kill() can also be passed a TID. This is quite confusing.
+ # In here we want to enforce this distinction and support PIDs
+ # only, see:
+ # https://github.com/giampaolo/psutil/issues/687
+ try:
+ # Note: already checked that this is faster than using a
+ # regular expr. Also (a lot) faster than doing
+ # 'return pid in pids()'
+ with open_binary("%s/%s/status" % (get_procfs_path(), pid)) as f:
+ for line in f:
+ if line.startswith(b"Tgid:"):
+ tgid = int(line.split()[1])
+ # If tgid and pid are the same then we're
+ # dealing with a process PID.
+ return tgid == pid
+ raise ValueError("'Tgid' line not found")
+ except (EnvironmentError, ValueError):
+ return pid in pids()
def wrap_exceptions(fun):
diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py
index 871cb74e..028a41d9 100755
--- a/psutil/tests/test_linux.py
+++ b/psutil/tests/test_linux.py
@@ -40,6 +40,7 @@ from psutil.tests import safe_rmpath
from psutil.tests import sh
from psutil.tests import skip_on_not_implemented
from psutil.tests import TESTFN
+from psutil.tests import ThreadTask
from psutil.tests import TRAVIS
from psutil.tests import unittest
from psutil.tests import which
@@ -1004,6 +1005,24 @@ class TestMisc(unittest.TestCase):
importlib.reload(psutil._pslinux)
importlib.reload(psutil)
+ def test_issue_687(self):
+ # In case of thread ID:
+ # - pid_exists() is supposed to return False
+ # - Process(tid) is supposed to work
+ # - pids() should not return the TID
+ # See: https://github.com/giampaolo/psutil/issues/687
+ t = ThreadTask()
+ t.start()
+ try:
+ p = psutil.Process()
+ tid = p.threads()[1].id
+ assert not psutil.pid_exists(tid), tid
+ pt = psutil.Process(tid)
+ pt.as_dict()
+ self.assertNotIn(tid, psutil.pids())
+ finally:
+ t.stop()
+
# =====================================================================
# test process
diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py
index 50e0cc74..d25f4474 100755
--- a/psutil/tests/test_process.py
+++ b/psutil/tests/test_process.py
@@ -501,10 +501,8 @@ class TestProcess(unittest.TestCase):
try:
step2 = p.num_threads()
self.assertEqual(step2, step1 + 1)
- thread.stop()
finally:
- if thread._running:
- thread.stop()
+ thread.stop()
@unittest.skipUnless(WINDOWS, 'WINDOWS only')
def test_num_handles(self):
@@ -524,7 +522,6 @@ class TestProcess(unittest.TestCase):
thread = ThreadTask()
thread.start()
-
try:
step2 = p.threads()
self.assertEqual(len(step2), len(step1) + 1)
@@ -536,11 +533,8 @@ class TestProcess(unittest.TestCase):
self.assertEqual(athread.id, athread[0])
self.assertEqual(athread.user_time, athread[1])
self.assertEqual(athread.system_time, athread[2])
- # test num threads
- thread.stop()
finally:
- if thread._running:
- thread.stop()
+ thread.stop()
@retry_before_failing()
# see: https://travis-ci.org/giampaolo/psutil/jobs/111842553