Skip to content

Commit bc209e5

Browse files
authored
fix(profiling): Do not keep reference to frame to prevent memory leak (getsentry#2049)
The profiler can capture frames from it's own thread. When it does so, it holds on to a reference to the frame in the previous sample. One of the frames it holds on it is a frame from the profiler itself, which prevents the references to other frames to other frames from being freed. A consequence of this is that the local variables of those frames are not able to be freed either. This change ensures we do not keep a reference to the profiler around in order to prevent this issue.
1 parent bc55cd3 commit bc209e5

File tree

5 files changed

+283
-110
lines changed

5 files changed

+283
-110
lines changed

mypy.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ ignore_missing_imports = True
5959
[mypy-sentry_sdk._queue]
6060
ignore_missing_imports = True
6161
disallow_untyped_defs = False
62+
[mypy-sentry_sdk._lru_cache]
63+
disallow_untyped_defs = False
6264
[mypy-celery.app.trace]
6365
ignore_missing_imports = True
6466
[mypy-flask.signals]

sentry_sdk/_lru_cache.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
"""
2+
A fork of Python 3.6's stdlib lru_cache (found in Python's 'cpython/Lib/functools.py')
3+
adapted into a data structure for single threaded uses.
4+
5+
https://github.com/python/cpython/blob/v3.6.12/Lib/functools.py
6+
7+
8+
Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
9+
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation;
10+
11+
All Rights Reserved
12+
13+
14+
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
15+
--------------------------------------------
16+
17+
1. This LICENSE AGREEMENT is between the Python Software Foundation
18+
("PSF"), and the Individual or Organization ("Licensee") accessing and
19+
otherwise using this software ("Python") in source or binary form and
20+
its associated documentation.
21+
22+
2. Subject to the terms and conditions of this License Agreement, PSF hereby
23+
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
24+
analyze, test, perform and/or display publicly, prepare derivative works,
25+
distribute, and otherwise use Python alone or in any derivative version,
26+
provided, however, that PSF's License Agreement and PSF's notice of copyright,
27+
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
28+
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation;
29+
All Rights Reserved" are retained in Python alone or in any derivative version
30+
prepared by Licensee.
31+
32+
3. In the event Licensee prepares a derivative work that is based on
33+
or incorporates Python or any part thereof, and wants to make
34+
the derivative work available to others as provided herein, then
35+
Licensee hereby agrees to include in any such work a brief summary of
36+
the changes made to Python.
37+
38+
4. PSF is making Python available to Licensee on an "AS IS"
39+
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
40+
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
41+
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
42+
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
43+
INFRINGE ANY THIRD PARTY RIGHTS.
44+
45+
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
46+
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
47+
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
48+
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
49+
50+
6. This License Agreement will automatically terminate upon a material
51+
breach of its terms and conditions.
52+
53+
7. Nothing in this License Agreement shall be deemed to create any
54+
relationship of agency, partnership, or joint venture between PSF and
55+
Licensee. This License Agreement does not grant permission to use PSF
56+
trademarks or trade name in a trademark sense to endorse or promote
57+
products or services of Licensee, or any third party.
58+
59+
8. By copying, installing or otherwise using Python, Licensee
60+
agrees to be bound by the terms and conditions of this License
61+
Agreement.
62+
63+
"""
64+
65+
SENTINEL = object()
66+
67+
68+
# aliases to the entries in a node
69+
PREV = 0
70+
NEXT = 1
71+
KEY = 2
72+
VALUE = 3
73+
74+
75+
class LRUCache(object):
76+
def __init__(self, max_size):
77+
assert max_size > 0
78+
79+
self.max_size = max_size
80+
self.full = False
81+
82+
self.cache = {}
83+
84+
# root of the circularly linked list to keep track of
85+
# the least recently used key
86+
self.root = [] # type: ignore
87+
# the node looks like [PREV, NEXT, KEY, VALUE]
88+
self.root[:] = [self.root, self.root, None, None]
89+
90+
self.hits = self.misses = 0
91+
92+
def set(self, key, value):
93+
link = self.cache.get(key, SENTINEL)
94+
95+
if link is not SENTINEL:
96+
# have to move the node to the front of the linked list
97+
link_prev, link_next, _key, _value = link
98+
99+
# first remove the node from the lsnked list
100+
link_prev[NEXT] = link_next
101+
link_next[PREV] = link_prev
102+
103+
# insert the node between the root and the last
104+
last = self.root[PREV]
105+
last[NEXT] = self.root[PREV] = link
106+
link[PREV] = last
107+
link[NEXT] = self.root
108+
109+
# update the value
110+
link[VALUE] = value
111+
112+
elif self.full:
113+
# reuse the root node, so update its key/value
114+
old_root = self.root
115+
old_root[KEY] = key
116+
old_root[VALUE] = value
117+
118+
self.root = old_root[NEXT]
119+
old_key = self.root[KEY]
120+
121+
self.root[KEY] = self.root[VALUE] = None
122+
123+
del self.cache[old_key]
124+
125+
self.cache[key] = old_root
126+
127+
else:
128+
# insert new node after last
129+
last = self.root[PREV]
130+
link = [last, self.root, key, value]
131+
last[NEXT] = self.root[PREV] = self.cache[key] = link
132+
self.full = len(self.cache) >= self.max_size
133+
134+
def get(self, key, default=None):
135+
link = self.cache.get(key, SENTINEL)
136+
137+
if link is SENTINEL:
138+
self.misses += 1
139+
return default
140+
141+
# have to move the node to the front of the linked list
142+
link_prev, link_next, _key, _value = link
143+
144+
# first remove the node from the lsnked list
145+
link_prev[NEXT] = link_next
146+
link_next[PREV] = link_prev
147+
148+
# insert the node between the root and the last
149+
last = self.root[PREV]
150+
last[NEXT] = self.root[PREV] = link
151+
link[PREV] = last
152+
link[NEXT] = self.root
153+
154+
self.hits += 1
155+
156+
return link[VALUE]

0 commit comments

Comments
 (0)