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
15 changes: 7 additions & 8 deletions Lib/profiling/sampling/gecko_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ def collect(self, stack_frames):
for thread_info in interpreter_info.threads:
frames = thread_info.frame_info
tid = thread_info.thread_id
gc_collecting = thread_info.gc_collecting

# Initialize thread if needed
if tid not in self.threads:
Expand Down Expand Up @@ -197,16 +196,16 @@ def collect(self, stack_frames):
self._add_marker(tid, "Waiting for GIL", self.gil_wait_start.pop(tid),
current_time, CATEGORY_GIL)

# Track GC events - attribute to all threads that hold the GIL during GC
# (GC is interpreter-wide but runs on whichever thread(s) have the GIL)
# If GIL switches during GC, multiple threads will get GC markers
if gc_collecting and has_gil:
# Start GC marker if not already started for this thread
# Track GC events by detecting <GC> frames in the stack trace
# This leverages the improved GC frame tracking from commit 336366fd7ca
# which precisely identifies the thread that initiated GC collection
has_gc_frame = any(frame[2] == "<GC>" for frame in frames)
if has_gc_frame:
# This thread initiated GC collection
if tid not in self.gc_start_per_thread:
self.gc_start_per_thread[tid] = current_time
elif tid in self.gc_start_per_thread:
# End GC marker if it was running for this thread
# (either GC finished or thread lost GIL)
# End GC marker when no more GC frames are detected
self._add_marker(tid, "GC Collecting", self.gc_start_per_thread.pop(tid),
current_time, CATEGORY_GC)

Expand Down
6 changes: 2 additions & 4 deletions Lib/test/test_profiling/test_sampling_profiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,13 @@ def __repr__(self):
class MockThreadInfo:
"""Mock ThreadInfo for testing since the real one isn't accessible."""

def __init__(self, thread_id, frame_info, status=0, gc_collecting=False): # Default to THREAD_STATE_RUNNING (0)
def __init__(self, thread_id, frame_info, status=0): # Default to THREAD_STATE_RUNNING (0)
self.thread_id = thread_id
self.frame_info = frame_info
self.status = status
self.gc_collecting = gc_collecting

def __repr__(self):
return f"MockThreadInfo(thread_id={self.thread_id}, frame_info={self.frame_info}, status={self.status}, gc_collecting={self.gc_collecting})"
return f"MockThreadInfo(thread_id={self.thread_id}, frame_info={self.frame_info}, status={self.status})"


class MockInterpreterInfo:
Expand Down Expand Up @@ -2742,7 +2741,6 @@ def __init__(self, thread_id, frame_info, status):
self.thread_id = thread_id
self.frame_info = frame_info
self.status = status
self.gc_collecting = False

# Create test data: active thread (HAS_GIL | ON_CPU), idle thread (neither), and another active thread
ACTIVE_STATUS = THREAD_STATUS_HAS_GIL | THREAD_STATUS_ON_CPU # Has GIL and on CPU
Expand Down
11 changes: 0 additions & 11 deletions Modules/_remote_debugging_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,6 @@ static PyStructSequence_Field ThreadInfo_fields[] = {
{"thread_id", "Thread ID"},
{"status", "Thread status (flags: HAS_GIL, ON_CPU, UNKNOWN or legacy enum)"},
{"frame_info", "Frame information"},
{"gc_collecting", "Whether GC is collecting (interpreter-level)"},
{NULL}
};

Expand Down Expand Up @@ -2726,8 +2725,6 @@ unwind_stack_for_thread(
goto error;
}

int gc_collecting = GET_MEMBER(int, gc_state, unwinder->debug_offsets.gc.collecting);

// Calculate thread status using flags (always)
int status_flags = 0;

Expand Down Expand Up @@ -2827,18 +2824,10 @@ unwind_stack_for_thread(
goto error;
}

PyObject *py_gc_collecting = PyBool_FromLong(gc_collecting);
if (py_gc_collecting == NULL) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create gc_collecting");
Py_DECREF(py_status);
goto error;
}

// py_status contains status flags (bitfield)
PyStructSequence_SetItem(result, 0, thread_id);
PyStructSequence_SetItem(result, 1, py_status); // Steals reference
PyStructSequence_SetItem(result, 2, frame_info); // Steals reference
PyStructSequence_SetItem(result, 3, py_gc_collecting); // Steals reference

cleanup_stack_chunks(&chunks);
return result;
Expand Down
Loading