Skip to content

Commit ec58d3c

Browse files
gitcommit90claude
andcommitted
fix(serializer): avoid copying large Mapping locals before trimming
Limit the temporary copy to at most remaining_breadth+1 pairs when breadth is bounded, keeping the existing full-copy path for unbounded breadth. Also added a regression test with a counting Mapping implementation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e2dd162 commit ec58d3c

2 files changed

Lines changed: 36 additions & 2 deletions

File tree

‎sentry_sdk/serializer.py‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from array import array
44
from collections.abc import Mapping
55
from datetime import datetime
6+
from itertools import islice
67
from typing import TYPE_CHECKING
78

89
from sentry_sdk.utils import (
@@ -301,14 +302,18 @@ def _serialize_node_impl(
301302
elif isinstance(obj, Mapping):
302303
# Create temporary copy here to avoid calling too much code that
303304
# might mutate our dictionary while we're still iterating over it.
304-
obj = dict(obj.items())
305+
obj_len = len(obj)
306+
if isinstance(remaining_breadth, int):
307+
obj = dict(islice(obj.items(), remaining_breadth + 1))
308+
else:
309+
obj = dict(obj.items())
305310

306311
rv_dict: "Dict[str, Any]" = {}
307312
i = 0
308313

309314
for k, v in obj.items():
310315
if remaining_breadth is not None and i >= remaining_breadth:
311-
self._annotate(len=len(obj))
316+
self._annotate(len=obj_len)
312317
break
313318

314319
str_k = str(k)

‎tests/test_serializer.py‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import gc
22
import re
33
from array import array
4+
from collections.abc import Mapping
45

56
import pytest
67

@@ -153,6 +154,34 @@ def test_trim_databag_breadth(body_normalizer):
153154
assert data.get(key) == value
154155

155156

157+
def test_trim_databag_breadth_does_not_copy_entire_mapping(body_normalizer):
158+
class CountingMapping(Mapping):
159+
def __init__(self, size):
160+
self.data = {"key{}".format(i): "value{}".format(i) for i in range(size)}
161+
self.iterated = 0
162+
self.lookups = 0
163+
164+
def __len__(self):
165+
return len(self.data)
166+
167+
def __iter__(self):
168+
for key in self.data:
169+
self.iterated += 1
170+
yield key
171+
172+
def __getitem__(self, key):
173+
self.lookups += 1
174+
return self.data[key]
175+
176+
data = CountingMapping(MAX_DATABAG_BREADTH + 5)
177+
178+
result = body_normalizer(data)
179+
180+
assert len(result) == MAX_DATABAG_BREADTH
181+
assert data.iterated == MAX_DATABAG_BREADTH + 1
182+
assert data.lookups == MAX_DATABAG_BREADTH + 1
183+
184+
156185
def test_no_trimming_if_max_request_body_size_is_always(body_normalizer):
157186
data = {
158187
"key{}".format(i): "value{}".format(i) for i in range(MAX_DATABAG_BREADTH + 10)

0 commit comments

Comments
 (0)