Skip to content

Commit 40bef90

Browse files
authored
fix(profiling): Handle potential attribute errors in profiler (#2028)
We've noticed some edge cases where the api doesn't return the expected `FrameType` or is missing some attributes. There isn't much we can do about this other than to handle the error and drop the sample.
1 parent 6b0ad1a commit 40bef90

File tree

1 file changed

+44
-35
lines changed

1 file changed

+44
-35
lines changed

sentry_sdk/profiler.py

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -262,14 +262,7 @@ def extract_stack(
262262
frames = deque(maxlen=max_stack_depth) # type: Deque[FrameType]
263263

264264
while frame is not None:
265-
try:
266-
f_back = frame.f_back
267-
except AttributeError:
268-
capture_internal_exception(sys.exc_info())
269-
# For some reason, the frame we got isn't a `FrameType` and doesn't
270-
# have a `f_back`. When this happens, we continue with any frames
271-
# that we've managed to extract up to this point.
272-
break
265+
f_back = frame.f_back
273266
frames.append(frame)
274267
frame = f_back
275268

@@ -638,30 +631,35 @@ def write(self, cwd, ts, sample, frame_cache):
638631
elapsed_since_start_ns = str(offset)
639632

640633
for tid, (stack_id, raw_stack, frames) in sample:
641-
# Check if the stack is indexed first, this lets us skip
642-
# indexing frames if it's not necessary
643-
if stack_id not in self.indexed_stacks:
644-
for i, raw_frame in enumerate(raw_stack):
645-
if raw_frame not in self.indexed_frames:
646-
self.indexed_frames[raw_frame] = len(self.indexed_frames)
647-
processed_frame = frame_cache.get(raw_frame)
648-
if processed_frame is None:
649-
processed_frame = extract_frame(frames[i], cwd)
650-
frame_cache[raw_frame] = processed_frame
651-
self.frames.append(processed_frame)
652-
653-
self.indexed_stacks[stack_id] = len(self.indexed_stacks)
654-
self.stacks.append(
655-
[self.indexed_frames[raw_frame] for raw_frame in raw_stack]
634+
try:
635+
# Check if the stack is indexed first, this lets us skip
636+
# indexing frames if it's not necessary
637+
if stack_id not in self.indexed_stacks:
638+
for i, raw_frame in enumerate(raw_stack):
639+
if raw_frame not in self.indexed_frames:
640+
self.indexed_frames[raw_frame] = len(self.indexed_frames)
641+
processed_frame = frame_cache.get(raw_frame)
642+
if processed_frame is None:
643+
processed_frame = extract_frame(frames[i], cwd)
644+
frame_cache[raw_frame] = processed_frame
645+
self.frames.append(processed_frame)
646+
647+
self.indexed_stacks[stack_id] = len(self.indexed_stacks)
648+
self.stacks.append(
649+
[self.indexed_frames[raw_frame] for raw_frame in raw_stack]
650+
)
651+
652+
self.samples.append(
653+
{
654+
"elapsed_since_start_ns": elapsed_since_start_ns,
655+
"thread_id": tid,
656+
"stack_id": self.indexed_stacks[stack_id],
657+
}
656658
)
657-
658-
self.samples.append(
659-
{
660-
"elapsed_since_start_ns": elapsed_since_start_ns,
661-
"thread_id": tid,
662-
"stack_id": self.indexed_stacks[stack_id],
663-
}
664-
)
659+
except AttributeError:
660+
# For some reason, the frame we get doesn't have certain attributes.
661+
# When this happens, we abandon the current sample as it's bad.
662+
capture_internal_exception(sys.exc_info())
665663

666664
def process(self):
667665
# type: () -> ProcessedProfile
@@ -825,10 +823,21 @@ def _sample_stack(*args, **kwargs):
825823

826824
now = nanosecond_time()
827825

828-
raw_sample = {
829-
tid: extract_stack(frame, last_sample[0].get(tid))
830-
for tid, frame in sys._current_frames().items()
831-
}
826+
try:
827+
raw_sample = {
828+
tid: extract_stack(frame, last_sample[0].get(tid))
829+
for tid, frame in sys._current_frames().items()
830+
}
831+
except AttributeError:
832+
# For some reason, the frame we get doesn't have certain attributes.
833+
# When this happens, we abandon the current sample as it's bad.
834+
capture_internal_exception(sys.exc_info())
835+
836+
# make sure to clear the cache if something went wrong when extracting
837+
# the stack so we dont keep a reference to the last stack of frames around
838+
last_sample[0] = {}
839+
840+
return
832841

833842
# make sure to update the last sample so the cache has
834843
# the most recent stack for better cache hits

0 commit comments

Comments
 (0)