Skip to content
Open
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
7 changes: 4 additions & 3 deletions googlecloudprofiler/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

# setup.py reads the version information from here to set package version
# Glean fork: upstream 4.1.0 + 3.12/3.13 support (vendored as 4.2.0) + the
# SIGPROF frame-walk crash fix (copy-the-chain). Patch bump over the synthetic
# 4.2.0 -- the fix is a backward-compatible bug fix. Upstream has no 4.2.x.
__version__ = '4.2.1'
# SIGPROF frame-walk crash fix (copy-the-chain, 4.2.1) + a bound on the frame
# walk to prevent an infinite loop on a torn/cyclic `previous` chain (4.2.2).
# Backward-compatible bug fixes; upstream has no 4.2.x.
__version__ = '4.2.2'
29 changes: 26 additions & 3 deletions googlecloudprofiler/src/populate_frames.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ bool SafeCopy(void *dst, const void *src, size_t n) {
return got == static_cast<long>(n);
}

// Absolute cap on frames walked per SIGPROF sample. num_frames only advances for
// complete frames, so a cycle of incomplete/stale frames -- e.g. a torn
// `previous` link left by a SIGPROF landing mid frame push/pop -- would otherwise
// spin this walk forever inside the signal handler (GIL held), wedging the
// process. 4x the capture cap leaves headroom for legitimately skipped frames
// (C-stack shims, prologue frames).
static const int kMaxWalkIterations = 4 * kMaxFramesToCapture;

#if PY_VERSION_HEX >= PY_313

/**
Expand Down Expand Up @@ -92,7 +100,9 @@ int PopulateFrames(CallFrame *frames, PyThreadState *ts) {

_PyInterpreterFrame *faddr = ts->current_frame;
int num_frames = 0;
while (faddr != nullptr && num_frames < kMaxFramesToCapture) {
int steps = 0;
while (faddr != nullptr && num_frames < kMaxFramesToCapture &&
++steps <= kMaxWalkIterations) {
_PyInterpreterFrame fr;
if (!SafeCopy(&fr, faddr, sizeof(fr))) {
break; // unreadable frame: stop, keep the frames gathered so far
Expand All @@ -108,6 +118,9 @@ int PopulateFrames(CallFrame *frames, PyThreadState *ts) {
(fr.instr_ptr - _PyCode_CODE(code)) * sizeof(_Py_CODEUNIT));
num_frames++;
}
if (fr.previous == faddr) {
break; // self-referential link: stop instead of spinning
}
faddr = fr.previous;
}
return num_frames;
Expand Down Expand Up @@ -174,7 +187,9 @@ int PopulateFrames(CallFrame *frames, PyThreadState *ts) {
}

int num_frames = 0;
while (faddr != nullptr && num_frames < kMaxFramesToCapture) {
int steps = 0;
while (faddr != nullptr && num_frames < kMaxFramesToCapture &&
++steps <= kMaxWalkIterations) {
_PyInterpreterFrame fr;
if (!SafeCopy(&fr, faddr, sizeof(fr))) {
break; // unreadable frame: stop, keep the frames gathered so far
Expand All @@ -188,6 +203,9 @@ int PopulateFrames(CallFrame *frames, PyThreadState *ts) {
(fr.prev_instr - _PyCode_CODE(fr.f_code)) * sizeof(_Py_CODEUNIT));
num_frames++;
}
if (fr.previous == faddr) {
break; // self-referential link: stop instead of spinning
}
faddr = fr.previous;
}
return num_frames;
Expand Down Expand Up @@ -248,7 +266,9 @@ int PopulateFrames(CallFrame *frames, PyThreadState *ts) {
}

int num_frames = 0;
while (faddr != nullptr && num_frames < kMaxFramesToCapture) {
int steps = 0;
while (faddr != nullptr && num_frames < kMaxFramesToCapture &&
++steps <= kMaxWalkIterations) {
_PyInterpreterFrame fr;
if (!SafeCopy(&fr, faddr, sizeof(fr))) {
break; // unreadable frame: stop, keep the frames gathered so far
Expand All @@ -262,6 +282,9 @@ int PopulateFrames(CallFrame *frames, PyThreadState *ts) {
(fr.prev_instr - _PyCode_CODE(fr.f_code)) * sizeof(_Py_CODEUNIT));
num_frames++;
}
if (fr.previous == faddr) {
break; // self-referential link: stop instead of spinning
}
faddr = fr.previous;
}
return num_frames;
Expand Down