Skip to content

Commit b42812e

Browse files
committed
Handle process exiting during sampling
1 parent 4fa0832 commit b42812e

File tree

3 files changed

+56
-2
lines changed

3 files changed

+56
-2
lines changed

Lib/profile/sample.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import argparse
22
import _remote_debugging
3+
import os
34
import pstats
45
import statistics
5-
import time
6+
import sys
67
import sysconfig
8+
import time
79
from collections import deque
810
from _colorize import ANSIColors
911

@@ -47,8 +49,14 @@ def sample(self, collector, duration_sec=10):
4749
try:
4850
stack_frames = self.unwinder.get_stack_trace()
4951
collector.collect(stack_frames)
50-
except (RuntimeError, UnicodeDecodeError, OSError):
52+
except ProcessLookupError:
53+
break
54+
except (RuntimeError, UnicodeDecodeError, MemoryError):
5155
errors += 1
56+
except Exception as e:
57+
if not self._is_process_running():
58+
break
59+
raise e from None
5260

5361
# Track actual sampling intervals for real-time stats
5462
if num_samples > 0:
@@ -89,6 +97,22 @@ def sample(self, collector, duration_sec=10):
8997
f"({(expected_samples - num_samples) / expected_samples * 100:.2f}%)"
9098
)
9199

100+
def _is_process_running(self):
101+
if sys.platform == "linux" or sys.platform == "darwin":
102+
try:
103+
os.kill(self.pid, 0)
104+
return True
105+
except ProcessLookupError:
106+
return False
107+
elif sys.platform == "win32":
108+
try:
109+
_remote_debugging.RemoteUnwinder(self.pid)
110+
except Exception:
111+
return False
112+
return True
113+
else:
114+
raise ValueError(f"Unsupported platform: {sys.platform}")
115+
92116
def _print_realtime_stats(self):
93117
"""Print real-time sampling statistics."""
94118
if len(self.sample_intervals) < 2:

Lib/test/test_sample_profiler.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@
2323

2424
try:
2525
from _remote_debugging import PROCESS_VM_READV_SUPPORTED
26+
import _remote_debugging
2627
except ImportError:
2728
raise unittest.SkipTest(
2829
"Test only runs when _remote_debugging is available"
2930
)
3031
else:
3132
import profile.sample
33+
from profile.sample import SampleProfiler
34+
3235

3336

3437
class MockFrameInfo:
@@ -1600,6 +1603,30 @@ def test_invalid_output_format_with_mocked_profiler(self):
16001603
"Invalid output format: unknown_format", str(cm.exception)
16011604
)
16021605

1606+
def test_is_process_running(self):
1607+
with (test_subprocess("import time; time.sleep(0.2)") as proc,
1608+
mock.patch("_remote_debugging.RemoteUnwinder") as mock_unwinder_class):
1609+
mock_unwinder_class.return_value = mock.MagicMock()
1610+
profiler = SampleProfiler(pid=proc.pid, sample_interval_usec=1000, all_threads=False)
1611+
1612+
self.assertTrue(profiler._is_process_running())
1613+
proc.wait()
1614+
self.assertFalse(profiler._is_process_running())
1615+
1616+
@unittest.skipUnless(sys.platform == "linux", "Only valid on Linux")
1617+
def test_esrch_signal_handling(self):
1618+
with test_subprocess("import time; time.sleep(0.1)") as proc:
1619+
unwinder = _remote_debugging.RemoteUnwinder(proc.pid)
1620+
initial_trace = unwinder.get_stack_trace()
1621+
self.assertIsNotNone(initial_trace)
1622+
1623+
# Wait for the process to die and try to get another trace
1624+
proc.wait()
1625+
1626+
with self.assertRaises(ProcessLookupError):
1627+
unwinder.get_stack_trace()
1628+
1629+
16031630

16041631
class TestSampleProfilerCLI(unittest.TestCase):
16051632
def test_cli_collapsed_format_validation(self):

Python/remote_debug.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,9 @@ _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address
982982
return read_remote_memory_fallback(handle, remote_address, len, dst);
983983
}
984984
PyErr_SetFromErrno(PyExc_OSError);
985+
if (errno == ESRCH) {
986+
return -1;
987+
}
985988
_set_debug_exception_cause(PyExc_OSError,
986989
"process_vm_readv failed for PID %d at address 0x%lx "
987990
"(size %zu, partial read %zd bytes): %s",

0 commit comments

Comments
 (0)