-
-
Notifications
You must be signed in to change notification settings - Fork 419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
check pid exists when waiting for child #388
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,8 +6,14 @@ | |
import os | ||
import re | ||
import sys | ||
import ctypes | ||
from typing import Union | ||
|
||
# Windows macros | ||
STILL_ACTIVE = 0x103 | ||
ERROR_ACCESS_DENIED = 0x5 | ||
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 | ||
|
||
|
||
def size_fmt(num: Union[int, float], suffix: str = 'B') -> str: | ||
for unit in ['', 'Ki', 'Mi', 'Gi']: | ||
|
@@ -101,7 +107,6 @@ def time_str_to_us(t_s: str) -> float: | |
# https://github.com/giampaolo/psutil | ||
def pid_exists(pid): | ||
"""Check whether pid exists in the current process table. | ||
UNIX only. | ||
""" | ||
if pid < 0: | ||
return False | ||
|
@@ -110,19 +115,58 @@ def pid_exists(pid): | |
# in the process group of the calling process. | ||
# On certain systems 0 is a valid PID but we have no way | ||
# to know that in a portable fashion. | ||
# On Windows, 0 is an idle process buw we don't need to | ||
# check it here | ||
raise ValueError('invalid PID 0') | ||
try: | ||
os.kill(pid, 0) | ||
except OSError as err: | ||
if err.errno == errno.ESRCH: | ||
# ESRCH == No such process | ||
return False | ||
elif err.errno == errno.EPERM: | ||
# EPERM clearly means there's a process to deny access to | ||
# UNIX | ||
if sys.platform != "win32": | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's make the logic "positive" here. Do |
||
try: | ||
os.kill(pid, 0) | ||
except OSError as err: | ||
if err.errno == errno.ESRCH: | ||
# ESRCH == No such process | ||
return False | ||
elif err.errno == errno.EPERM: | ||
# EPERM clearly means there's a process to deny access to | ||
return True | ||
else: # pragma: no cover | ||
# According to "man 2 kill" possible error values are | ||
# (EINVAL, EPERM, ESRCH) | ||
raise | ||
else: | ||
return True | ||
else: # pragma: no cover | ||
# According to "man 2 kill" possible error values are | ||
# (EINVAL, EPERM, ESRCH) | ||
raise | ||
# Windows | ||
else: | ||
return True | ||
kernel32 = ctypes.windll.kernel32 | ||
|
||
process = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid) | ||
if not process: | ||
if kernel32.GetLastError() == ERROR_ACCESS_DENIED: | ||
# Access is denied, which means there's a process. | ||
# Usually it's impossible to run here in viztracer. | ||
return True # pragma: no cover | ||
else: | ||
return False | ||
|
||
exit_code = ctypes.c_ulong() | ||
out = kernel32.GetExitCodeProcess(process, ctypes.byref(exit_code)) | ||
kernel32.CloseHandle(process) | ||
# nonzero return value means the funtion succeeds | ||
if out: | ||
if exit_code.value == STILL_ACTIVE: | ||
# According to documents of GetExitCodeProcess. | ||
# If a thread returns STILL_ACTIVE (259) as an error code, | ||
# then applications that test for that value could interpret | ||
# it to mean that the thread is still running, and continue | ||
# to test for the completion of the thread after the thread | ||
# has terminated, which could put the application into an | ||
# infinite loop. | ||
return True | ||
else: | ||
return False | ||
else: # pragma: no cover | ||
if kernel32.GetLastError() == ERROR_ACCESS_DENIED: | ||
# Access is denied, which means there's a process. | ||
# Usually it's impossible to run here in viztracer. | ||
return True | ||
return False # pragma: no cover |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -384,3 +384,47 @@ def test_escape_string(self): | |
expected_output_file="result.json", | ||
script=issue285_code, | ||
expected_stdout=".*Total Entries:.*") | ||
|
||
|
||
wait_for_child = """ | ||
import time | ||
import multiprocessing | ||
|
||
def target(): | ||
time.sleep(3) | ||
|
||
if __name__ == '__main__': | ||
p = multiprocessing.Process(target=target) | ||
p.start() | ||
multiprocessing.process._children = set() | ||
gaogaotiantian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
time.sleep(1) | ||
""" | ||
|
||
wait_for_terminated_child = """ | ||
import time | ||
import os | ||
import signal | ||
import multiprocessing | ||
|
||
def target(): | ||
time.sleep(3) | ||
os.kill(os.getpid(), signal.SIGTERM) | ||
|
||
if __name__ == '__main__': | ||
p = multiprocessing.Process(target=target) | ||
p.start() | ||
multiprocessing.process._children = set() | ||
time.sleep(1) | ||
""" | ||
|
||
|
||
class TestWaitForChild(CmdlineTmpl): | ||
def test_child_process_exits_normally(self): | ||
self.template(["viztracer", "-o", "result.json", "--dump_raw", "cmdline_test.py"], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did not think too much but is |
||
expected_output_file="result.json", expected_stdout=r"Wait", | ||
script=wait_for_child) | ||
|
||
def test_child_process_exits_abnormally(self): | ||
self.template(["viztracer", "-o", "result.json", "--dump_raw", "cmdline_test.py"], | ||
expected_output_file="result.json", expected_stdout=r"Wait", | ||
script=wait_for_terminated_child) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logic here can be simplified. You don't need to check if
remain_viztmp
is empty if you need to iterate through it. Do the for loop directly and your remaining logic will take care of it.int(f[7:-12])
is a horrible magic number. Yit would be better to use regular expression here because it's self-explainable. Also, if there happen to be a file that does not match the pattern, this implementation will raise an index error or a integer conversion error, which would be a huge confusion to users. For regular expression, if it does not match, you can issue a warning (might be something we change in other places but forgot to match here).You probably do not need a
remain_children
variable, you can iterate throughremain_viztmp
directly.