Skip to content

Commit a0c6fde

Browse files
authored
fix: Fix memory leak from usage.py not properly cleaning up call stack (#3371)
* fix: Fix memory leak from usage.py not properly cleaning up call stack context Signed-off-by: Danny Chiao <danny@tecton.ai> * fix Signed-off-by: Danny Chiao <danny@tecton.ai> Signed-off-by: Danny Chiao <danny@tecton.ai>
1 parent 4ebe00f commit a0c6fde

File tree

1 file changed

+24
-14
lines changed

1 file changed

+24
-14
lines changed

sdk/python/feast/usage.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class FnCall:
7878

7979

8080
class Sampler:
81-
def should_record(self, event) -> bool:
81+
def should_record(self) -> bool:
8282
raise NotImplementedError
8383

8484
@property
@@ -87,7 +87,7 @@ def priority(self):
8787

8888

8989
class AlwaysSampler(Sampler):
90-
def should_record(self, event) -> bool:
90+
def should_record(self) -> bool:
9191
return True
9292

9393

@@ -100,7 +100,7 @@ def __init__(self, ratio):
100100
self.total_counter = 0
101101
self.sampled_counter = 0
102102

103-
def should_record(self, event) -> bool:
103+
def should_record(self) -> bool:
104104
self.total_counter += 1
105105
if self.total_counter == self.MAX_COUNTER:
106106
self.total_counter = 1
@@ -176,10 +176,12 @@ def _set_installation_id():
176176

177177

178178
def _export(event: typing.Dict[str, typing.Any]):
179-
_executor.submit(requests.post, USAGE_ENDPOINT, json=event, timeout=30)
179+
_executor.submit(requests.post, USAGE_ENDPOINT, json=event, timeout=2)
180180

181181

182182
def _produce_event(ctx: UsageContext):
183+
if ctx.sampler and not ctx.sampler.should_record():
184+
return
183185
# Cannot check for unittest because typeguard pulls in unittest
184186
is_test = flags_helper.is_test() or bool({"pytest"} & sys.modules.keys())
185187
event = {
@@ -204,10 +206,6 @@ def _produce_event(ctx: UsageContext):
204206
**_constant_attributes,
205207
}
206208
event.update(ctx.attributes)
207-
208-
if ctx.sampler and not ctx.sampler.should_record(event):
209-
return
210-
211209
_export(event)
212210

213211

@@ -262,6 +260,13 @@ def deeply_nested(...):
262260
"""
263261
sampler = attrs.pop("sampler", AlwaysSampler())
264262

263+
def clear_context(ctx):
264+
_context.set(UsageContext()) # reset context to default values
265+
# TODO: Figure out why without this, new contexts.get aren't reset
266+
ctx.call_stack = []
267+
ctx.completed_calls = []
268+
ctx.attributes = {}
269+
265270
def decorator(func):
266271
if not _is_enabled:
267272
return func
@@ -295,17 +300,22 @@ def wrapper(*args, **kwargs):
295300

296301
raise exc
297302
finally:
298-
last_call = ctx.call_stack.pop(-1)
299-
last_call.end = datetime.utcnow()
300-
ctx.completed_calls.append(last_call)
301303
ctx.sampler = (
302304
sampler if sampler.priority > ctx.sampler.priority else ctx.sampler
303305
)
306+
last_call = ctx.call_stack.pop(-1)
307+
last_call.end = datetime.utcnow()
308+
ctx.completed_calls.append(last_call)
304309

305-
if not ctx.call_stack:
306-
# we reached the root of the stack
307-
_context.set(UsageContext()) # reset context to default values
310+
if not ctx.call_stack or (
311+
len(ctx.call_stack) == 1
312+
and "feast.feature_store.FeatureStore.serve"
313+
in str(ctx.call_stack[0].fn_name)
314+
):
315+
# When running `feast serve`, the serve method never exits so it gets
316+
# stuck otherwise
308317
_produce_event(ctx)
318+
clear_context(ctx)
309319

310320
return wrapper
311321

0 commit comments

Comments
 (0)