Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/core/IronPython/Runtime/PythonContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3849,13 +3849,13 @@ public PythonTracebackListenersDispatcher(PythonContext parent) {
void Debugging.ITraceCallback.OnTraceEvent(Debugging.TraceEventKind kind, string name, string sourceFileName, SourceSpan sourceSpan, Func<IDictionary<object, object>> scopeCallback, object payload, object customPayload) {
var listener = _parent._tracebackListeners.Value;

if (listener == null && (_parent.PythonOptions.Tracing || _parent._tracebackListenersCount > 0)) {
// If tracing is enabled (globally or via SetTrace on any thread), we need to register a dummy
// traceback listener for this thread, because of the FunctionStack handling done there.
if (listener == null) {
// Code compiled for tracing pushes and pops its frames on the FunctionStack only through the listener, so this thread
// needs a dummy listener even without a trace function. This must not depend on whether tracing is on right now.
_parent._tracebackListeners.Value = listener = new PythonTracebackListener(_parent, null, isDummyListener: true);
}

listener?.OnTraceEvent(kind, name, sourceFileName, sourceSpan, scopeCallback, payload, customPayload);
listener.OnTraceEvent(kind, name, sourceFileName, sourceSpan, scopeCallback, payload, customPayload);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/core/IronPython/Runtime/PythonTracebackListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ internal bool ExceptionThrown {
}
}

// dummy listener used to maintain the frame stack
// dummy listener used to maintain the frame stack on a thread without a trace function
internal bool IsDummyListener { get; }

#if PROFILE_SUPPORT
Expand Down Expand Up @@ -135,7 +135,7 @@ public void OnTraceEvent(Debugging.TraceEventKind kind, string name, string sour
pyFrame._lineNo = sourceSpan.Start.Line;
}

if (traceDispatchObject != null && !_exceptionThrown) {
if (traceDispatchObject != null && !_exceptionThrown && !IsDummyListener) {
DispatchTrace(thread, kind, payload, traceDispatch, traceDispatchObject, pyFrame);
}
} finally {
Expand Down
46 changes: 46 additions & 0 deletions tests/suite/modules/system_related/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,52 @@ def f():
out = subprocess.check_output([sys.executable, '-X:Debug', '-c', code])
self.assertEqual(out.strip(), b'True')

def test_settrace_no_frames(self):
# A traced function that calls sys.settrace(None) leaves no frames behind on the frame stack
def stop():
sys.settrace(None)
def f():
stop()

sys.settrace(lambda *args: None)
try:
f()
finally:
sys.settrace(None)
self.assertEqual(sys._getframe(0).f_code.co_name, 'test_settrace_no_frames')

def test_no_trace_events_after_settrace_none(self):
# After sys.settrace(None), no trace events are delivered, not even to frames that have a local trace function,
# also while another thread is still being traced
import threading
events = []
def tracer(frame, event, arg):
events.append((event, frame.f_code.co_name))
return tracer
def stop():
sys.settrace(None)
def f():
stop()
return 1

traced, finished = threading.Event(), threading.Event()
def other():
sys.settrace(lambda *args: None)
traced.set()
finished.wait()
sys.settrace(None)
thread = threading.Thread(target=other)
thread.start()
traced.wait()
sys.settrace(tracer)
try:
f()
finally:
sys.settrace(None)
finished.set()
thread.join()
self.assertEqual(events, [('call', 'f'), ('line', 'f'), ('call', 'stop'), ('line', 'stop')])

@skipUnlessIronPython()
@unittest.expectedFailure # dummy getrefcount removed in https://github.com/IronLanguages/ironpython3/pull/577
def test_getrefcount(self):
Expand Down
Loading