Skip to content

Commit 8b9cecc

Browse files
committed
fix: close a sys.monitoring race condition with free-threading. #1970
1 parent 66e4f8d commit 8b9cecc

File tree

2 files changed

+37
-34
lines changed

2 files changed

+37
-34
lines changed

CHANGES.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@ Unreleased
2727
PYTHONWARNDEFAULTENCODING, fixing `issue 1966`_. Thanks, `Henry Schreiner
2828
<pull 1967_>`_.
2929

30+
- Fixed a race condition when using sys.monitoring with free-threading Python,
31+
closing `issue 1970`_.
32+
3033
.. _issue 1966: https://github.com/nedbat/coveragepy/issues/1966
3134
.. _pull 1967: https://github.com/nedbat/coveragepy/pull/1967
35+
.. _issue 1970: https://github.com/nedbat/coveragepy/issues/1970
3236

3337
.. start-releases
3438

coverage/sysmon.py

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,6 @@ def __init__(self, tool_id: int) -> None:
233233
0,
234234
)
235235

236-
self.stopped = False
237236
self._activity = False
238237

239238
def __repr__(self) -> str:
@@ -244,43 +243,43 @@ def __repr__(self) -> str:
244243
@panopticon()
245244
def start(self) -> None:
246245
"""Start this Tracer."""
247-
self.stopped = False
248-
249-
assert sys_monitoring is not None
250-
sys_monitoring.use_tool_id(self.myid, "coverage.py")
251-
register = functools.partial(sys_monitoring.register_callback, self.myid)
252-
events = sys.monitoring.events
253-
254-
sys_monitoring.set_events(self.myid, events.PY_START)
255-
register(events.PY_START, self.sysmon_py_start)
256-
if self.trace_arcs:
257-
register(events.PY_RETURN, self.sysmon_py_return)
258-
register(events.LINE, self.sysmon_line_arcs)
259-
if env.PYBEHAVIOR.branch_right_left:
260-
register(
261-
events.BRANCH_RIGHT, # type:ignore[attr-defined]
262-
self.sysmon_branch_either,
263-
)
264-
register(
265-
events.BRANCH_LEFT, # type:ignore[attr-defined]
266-
self.sysmon_branch_either,
267-
)
268-
else:
269-
register(events.LINE, self.sysmon_line_lines)
270-
sys_monitoring.restart_events()
271-
self.sysmon_on = True
246+
with self.lock:
247+
assert sys_monitoring is not None
248+
sys_monitoring.use_tool_id(self.myid, "coverage.py")
249+
register = functools.partial(sys_monitoring.register_callback, self.myid)
250+
events = sys.monitoring.events
251+
252+
sys_monitoring.set_events(self.myid, events.PY_START)
253+
register(events.PY_START, self.sysmon_py_start)
254+
if self.trace_arcs:
255+
register(events.PY_RETURN, self.sysmon_py_return)
256+
register(events.LINE, self.sysmon_line_arcs)
257+
if env.PYBEHAVIOR.branch_right_left:
258+
register(
259+
events.BRANCH_RIGHT, # type:ignore[attr-defined]
260+
self.sysmon_branch_either,
261+
)
262+
register(
263+
events.BRANCH_LEFT, # type:ignore[attr-defined]
264+
self.sysmon_branch_either,
265+
)
266+
else:
267+
register(events.LINE, self.sysmon_line_lines)
268+
sys_monitoring.restart_events()
269+
self.sysmon_on = True
272270

273271
@panopticon()
274272
def stop(self) -> None:
275273
"""Stop this Tracer."""
276-
if not self.sysmon_on:
277-
# In forking situations, we might try to stop when we are not
278-
# started. Do nothing in that case.
279-
return
280-
assert sys_monitoring is not None
281-
sys_monitoring.set_events(self.myid, 0)
282-
self.sysmon_on = False
283-
sys_monitoring.free_tool_id(self.myid)
274+
with self.lock:
275+
if not self.sysmon_on:
276+
# In forking situations, we might try to stop when we are not
277+
# started. Do nothing in that case.
278+
return
279+
assert sys_monitoring is not None
280+
sys_monitoring.set_events(self.myid, 0)
281+
self.sysmon_on = False
282+
sys_monitoring.free_tool_id(self.myid)
284283

285284
@panopticon()
286285
def post_fork(self) -> None:

0 commit comments

Comments
 (0)