Skip to content

Commit 0b6892e

Browse files
committed
wip: prevent ghost local events
python/cpython#111963
1 parent f9e0955 commit 0b6892e

File tree

1 file changed

+82
-65
lines changed

1 file changed

+82
-65
lines changed

coverage/pep669_tracer.py

Lines changed: 82 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,30 @@
1515
import sys
1616
import threading
1717
import traceback
18+
import weakref
1819

1920
from types import CodeType, FrameType, ModuleType
2021
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, cast
2122

22-
#from coverage.debug import short_stack
2323
from coverage.types import (
2424
TArc, TFileDisposition, TLineNo, TTraceData, TTraceFileData, TTraceFn,
2525
TTracer, TWarnFn,
2626
)
2727

28-
# When running meta-coverage, this file can try to trace itself, which confuses
29-
# everything. Don't trace ourselves.
3028

31-
THIS_FILE = __file__.rstrip("co")
29+
class LoggingWrapper:
30+
def __init__(self, wrapped, namespace):
31+
self.wrapped = wrapped
32+
self.namespace = namespace
33+
34+
def __getattr__(self, name):
35+
def _wrapped(*args, **kwargs):
36+
log(f"{self.namespace}.{name}{args}{kwargs}")
37+
return getattr(self.wrapped, name)(*args, **kwargs)
38+
return _wrapped
39+
40+
#sys_monitoring = LoggingWrapper(sys.monitoring, "sys.monitoring")
41+
sys_monitoring = getattr(sys, "monitoring", None)
3242

3343
seen_threads = set()
3444

@@ -37,7 +47,7 @@ def log(msg):
3747
# Thread ids are reused across processes? Make a shorter number more likely
3848
# to be unique.
3949
pid = os.getpid()
40-
tid = (os.getpid() * threading.current_thread().ident) % 9_999_991
50+
tid = (pid * threading.current_thread().ident) % 9_999_991
4151
tid = f"{tid:07d}"
4252
if tid not in seen_threads:
4353
seen_threads.add(tid)
@@ -55,7 +65,7 @@ def log(msg):
5565
]
5666
FILENAME_SUBS = []
5767

58-
def fname_repr(filename):
68+
def short_fname(filename):
5969
if not FILENAME_SUBS:
6070
for pathdir in sys.path:
6171
FILENAME_SUBS.append((pathdir, "syspath:"))
@@ -67,38 +77,41 @@ def fname_repr(filename):
6777
filename = re.sub(pat, sub, filename)
6878
for before, after in FILENAME_SUBS:
6979
filename = filename.replace(before, after)
70-
return repr(filename)
80+
return filename
7181

7282
def arg_repr(arg):
7383
if isinstance(arg, CodeType):
74-
arg_repr = f"<name={arg.co_name}, file={fname_repr(arg.co_filename)}#{arg.co_firstlineno}>"
84+
arg_repr = f"<code @{id(arg):#x} name={arg.co_name}, file={short_fname(arg.co_filename)!r}#{arg.co_firstlineno}>"
7585
else:
7686
arg_repr = repr(arg)
7787
return arg_repr
7888

7989
def short_stack(full=True):
8090
stack: Iterable[inspect.FrameInfo] = inspect.stack()[::-1]
81-
return "\n".join(f"{fi.function:>30s} : 0x{id(fi.frame):x} {fi.filename}:{fi.lineno}" for fi in stack)
91+
return "\n".join(f"{fi.function:>30s} : {id(fi.frame):#x} {short_fname(fi.filename)}:{fi.lineno}" for fi in stack)
8292

8393
def panopticon(*names):
8494
def _decorator(meth):
8595
def _wrapped(self, *args):
8696
try:
8797
# log("stack:\n" + short_stack())
88-
# args_reprs = []
89-
# for name, arg in zip(names, args):
90-
# if name is None:
91-
# continue
92-
# args_reprs.append(f"{name}={arg_repr(arg)}")
93-
# log(f"{id(self)}:{meth.__name__}({', '.join(args_reprs)})")
98+
args_reprs = []
99+
for name, arg in zip(names, args):
100+
if name is None:
101+
continue
102+
args_reprs.append(f"{name}={arg_repr(arg)}")
103+
log(f"{id(self):#x}:{meth.__name__}({', '.join(args_reprs)})")
94104
ret = meth(self, *args)
95-
# log(f" end {id(self)}:{meth.__name__}({', '.join(args_reprs)})")
105+
#log(f" end {id(self):#x}:{meth.__name__}({', '.join(args_reprs)})")
96106
return ret
97107
except Exception as exc:
98-
log(f"{exc.__class__.__name__}: {exc}")
99-
with open("/tmp/pan.out", "a") as f:
100-
traceback.print_exception(exc, file=f)
101-
sys.monitoring.set_events(sys.monitoring.COVERAGE_ID, 0)
108+
log(f"!!{exc.__class__.__name__}: {exc}")
109+
log("".join(traceback.format_exception(exc)))
110+
try:
111+
sys_monitoring.set_events(sys.monitoring.COVERAGE_ID, 0)
112+
except ValueError:
113+
# We might have already shut off monitoring.
114+
log(f"oops, shutting off events with disabled tool id")
102115
raise
103116
return _wrapped
104117
return _decorator
@@ -126,7 +139,8 @@ class Pep669Tracer(TTracer):
126139
# One of these will be used across threads. Be careful.
127140

128141
def __init__(self) -> None:
129-
log(f"Pep669Tracer.__init__: @{id(self)}\n{short_stack()}")
142+
test_name = os.environ.get("PYTEST_CURRENT_TEST", "no-test")
143+
log(f"Pep669Tracer.__init__: @{id(self):#x} in {test_name}\n{short_stack()}")
130144
# pylint: disable=super-init-not-called
131145
# Attributes set from the collector:
132146
self.data: TTraceData
@@ -137,16 +151,14 @@ def __init__(self) -> None:
137151
self.switch_context: Optional[Callable[[Optional[str]], None]] = None
138152
self.warn: TWarnFn
139153

140-
# The threading module to use, if any.
141-
self.threading: Optional[ModuleType] = None
142-
143154
self.code_infos: Dict[CodeType, CodeInfo] = {}
144155
self.last_lines: Dict[FrameType, int] = {}
156+
self.local_event_codes = None
157+
145158
self.stats = {
146159
"starts": 0,
147160
}
148161

149-
self.thread: Optional[threading.Thread] = None
150162
self.stopped = False
151163
self._activity = False
152164

@@ -155,45 +167,40 @@ def __init__(self) -> None:
155167
atexit.register(setattr, self, "in_atexit", True)
156168

157169
def __repr__(self) -> str:
158-
me = id(self)
159170
points = sum(len(v) for v in self.data.values())
160171
files = len(self.data)
161-
return f"<Pep669Tracer at 0x{me:x}: {points} data points in {files} files>"
172+
return f"<Pep669Tracer at {id(self):#x}: {points} data points in {files} files>"
162173

174+
@panopticon()
163175
def start(self) -> TTraceFn: # TODO: wrong return type
164176
"""Start this Tracer."""
165177
self.stopped = False
166-
if self.threading:
167-
if self.thread is None:
168-
self.thread = self.threading.current_thread()
169-
else:
170-
if self.thread.ident != self.threading.current_thread().ident:
171-
# Re-starting from a different thread!? Don't set the trace
172-
# function, but we are marked as running again, so maybe it
173-
# will be ok?
174-
1/0
175-
return self._cached_bound_method_trace
176178

179+
self.local_event_codes = weakref.WeakSet()
177180
self.myid = sys.monitoring.COVERAGE_ID
178-
sys.monitoring.use_tool_id(self.myid, "coverage.py")
181+
sys_monitoring.use_tool_id(self.myid, "coverage.py")
179182
events = sys.monitoring.events
180-
sys.monitoring.set_events(
183+
sys_monitoring.set_events(
181184
self.myid,
182-
events.PY_START | events.PY_RETURN | events.PY_RESUME | events.PY_YIELD | events.PY_UNWIND,
185+
events.PY_START | events.PY_UNWIND,
183186
)
184-
sys.monitoring.register_callback(self.myid, events.PY_START, self.sysmon_py_start)
185-
sys.monitoring.register_callback(self.myid, events.PY_RESUME, self.sysmon_py_resume)
186-
sys.monitoring.register_callback(self.myid, events.PY_RETURN, self.sysmon_py_return)
187-
sys.monitoring.register_callback(self.myid, events.PY_YIELD, self.sysmon_py_yield)
188-
sys.monitoring.register_callback(self.myid, events.PY_UNWIND, self.sysmon_py_unwind)
189-
sys.monitoring.register_callback(self.myid, events.LINE, self.sysmon_line)
190-
sys.monitoring.register_callback(self.myid, events.BRANCH, self.sysmon_branch)
191-
sys.monitoring.register_callback(self.myid, events.JUMP, self.sysmon_jump)
192-
187+
sys_monitoring.register_callback(self.myid, events.PY_START, self.sysmon_py_start)
188+
sys_monitoring.register_callback(self.myid, events.PY_RESUME, self.sysmon_py_resume)
189+
sys_monitoring.register_callback(self.myid, events.PY_RETURN, self.sysmon_py_return)
190+
sys_monitoring.register_callback(self.myid, events.PY_YIELD, self.sysmon_py_yield)
191+
sys_monitoring.register_callback(self.myid, events.PY_UNWIND, self.sysmon_py_unwind)
192+
sys_monitoring.register_callback(self.myid, events.LINE, self.sysmon_line)
193+
sys_monitoring.register_callback(self.myid, events.BRANCH, self.sysmon_branch)
194+
sys_monitoring.register_callback(self.myid, events.JUMP, self.sysmon_jump)
195+
196+
@panopticon()
193197
def stop(self) -> None:
194198
"""Stop this Tracer."""
195-
sys.monitoring.set_events(self.myid, 0)
196-
sys.monitoring.free_tool_id(self.myid)
199+
sys_monitoring.set_events(self.myid, 0)
200+
for code in self.local_event_codes:
201+
sys_monitoring.set_local_events(self.myid, code, 0)
202+
self.local_event_codes = None
203+
sys_monitoring.free_tool_id(self.myid)
197204

198205
def activity(self) -> bool:
199206
"""Has there been any activity?"""
@@ -255,19 +262,20 @@ def sysmon_py_start(self, code, instruction_offset: int):
255262

256263
if tracing_code:
257264
events = sys.monitoring.events
258-
log(f"set_local_events(code={arg_repr(code)})")
259-
sys.monitoring.set_local_events(
265+
sys_monitoring.set_local_events(
260266
self.myid,
261267
code,
262-
sys.monitoring.events.LINE |
263-
sys.monitoring.events.BRANCH |
264-
sys.monitoring.events.JUMP,
268+
events.PY_RETURN | events.PY_RESUME | events.PY_YIELD |
269+
events.LINE |
270+
events.BRANCH |
271+
events.JUMP,
265272
)
273+
self.local_event_codes.add(code)
266274

267275
if tracing_code:
268276
frame = self.callers_frame()
269277
self.last_lines[frame] = -code.co_firstlineno
270-
log(f" {file_data=}")
278+
#log(f" {file_data=}")
271279

272280
@panopticon("code", "@")
273281
def sysmon_py_resume(self, code, instruction_offset: int):
@@ -282,10 +290,10 @@ def sysmon_py_return(self, code, instruction_offset: int, retval: object):
282290
if self.trace_arcs:
283291
arc = (self.last_lines[frame], -code.co_firstlineno)
284292
cast(Set[TArc], code_info.file_data).add(arc)
285-
log(f" add1({arc=})")
293+
#log(f" add1({arc=})")
286294

287295
# Leaving this function, no need for the frame any more.
288-
log(f" popping frame 0x{id(frame):x}")
296+
#log(f" popping frame {id(frame):#x}")
289297
self.last_lines.pop(frame, None)
290298

291299
@panopticon("code", "@", None)
@@ -300,29 +308,38 @@ def sysmon_py_unwind(self, code, instruction_offset: int, exception):
300308
if self.trace_arcs:
301309
arc = (self.last_lines[frame], -code.co_firstlineno)
302310
cast(Set[TArc], code_info.file_data).add(arc)
303-
log(f" add3({arc=})")
311+
#log(f" add3({arc=})")
304312

305313
# Leaving this function.
306314
self.last_lines.pop(frame, None)
307315

308316
@panopticon("code", "line")
309317
def sysmon_line(self, code, line_number: int):
310-
frame = self.callers_frame()
311318
code_info = self.code_infos[code]
319+
if not code_info.tracing:
320+
log("DISABLE")
321+
return sys.monitoring.DISABLE
312322
if code_info.file_data is not None:
323+
frame = self.callers_frame()
313324
if self.trace_arcs:
314325
arc = (self.last_lines[frame], line_number)
315326
cast(Set[TArc], code_info.file_data).add(arc)
316-
log(f" add4({arc=})")
327+
#log(f" add4({arc=})")
317328
else:
318329
cast(Set[TLineNo], code_info.file_data).add(line_number)
319-
log(f" add5({line_number=})")
330+
#log(f" add5({line_number=})")
320331
self.last_lines[frame] = line_number
321332

322333
@panopticon("code", "from@", "to@")
323334
def sysmon_branch(self, code, instruction_offset: int, destination_offset: int):
324-
...
335+
code_info = self.code_infos[code]
336+
if not code_info.tracing:
337+
log("DISABLE")
338+
return sys.monitoring.DISABLE
325339

326340
@panopticon("code", "from@", "to@")
327341
def sysmon_jump(self, code, instruction_offset: int, destination_offset: int):
328-
...
342+
code_info = self.code_infos[code]
343+
if not code_info.tracing:
344+
log("DISABLE")
345+
return sys.monitoring.DISABLE

0 commit comments

Comments
 (0)