Skip to content

Commit 11b6a7d

Browse files
authored
Fix: gat tracemalloc and aggressive cleanup behind env vars (#37)
1 parent cfb784b commit 11b6a7d

2 files changed

Lines changed: 44 additions & 23 deletions

File tree

entrypoint.py

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,20 @@
1111
import panel as pn
1212
from tornado.web import RequestHandler
1313

14-
# Start tracemalloc as early as possible so allocation tracebacks include the
15-
# session-loading code paths. Frame depth 10 is enough to identify the call site
16-
# inside spikeinterface/zarr/fsspec without blowing up memory overhead.
17-
tracemalloc.start(10)
18-
_tracemalloc_baseline = tracemalloc.take_snapshot()
14+
# tracemalloc is OFF by default. Recording a per-allocation Python traceback
15+
# adds non-trivial CPU overhead per allocation; on a Panel + spikeinterface
16+
# + zarr workload that slowed Tornado enough for ECS health checks to time
17+
# out, producing exit-137 restart loops. Enable only on diagnostic deploys:
18+
# TRACEMALLOC_ENABLED=1 (and optionally TRACEMALLOC_DEPTH, default 4)
19+
_TRACEMALLOC_ENABLED = os.environ.get("TRACEMALLOC_ENABLED", "0").lower() in ("1", "true", "yes")
20+
_tracemalloc_baseline = None
21+
if _TRACEMALLOC_ENABLED:
22+
_tm_depth = int(os.environ.get("TRACEMALLOC_DEPTH", "4"))
23+
tracemalloc.start(_tm_depth)
24+
_tracemalloc_baseline = tracemalloc.take_snapshot()
25+
print(f"tracemalloc ENABLED with frame depth {_tm_depth}")
26+
else:
27+
print("tracemalloc DISABLED (set TRACEMALLOC_ENABLED=1 to enable)")
1928

2029
# 1. Run setup (replaces --setup flag)
2130
from aind_ephys_portal.setup import * # noqa: F401,F403,E402
@@ -33,11 +42,11 @@
3342
TARGET_INFLATE_DELAY_SECONDS = 90
3443

3544
# Recycle signal: when a task is sitting idle (0 GUI sessions) but its RAM
36-
# stays above this percent, /health returns 503 so the ALB deregisters it and
37-
# the ECS service scheduler replaces it. Baseline (no sessions, fresh start)
38-
# is ~10%, so 40% means tolerating ~30 points of accumulated residue before
39-
# recycling. Only triggers with 0 sessions, so user sessions are never cut.
40-
RECYCLE_RAM_PERCENT_WHEN_IDLE = 40
45+
# stays above this percent, /health returns 503 so the ALB deregisters it
46+
# and the ECS service scheduler replaces it. Overridable via env var so we
47+
# can tune without redeploys once we re-baseline post-rollback. Only
48+
# triggers with 0 sessions, so user sessions are never cut.
49+
RECYCLE_RAM_PERCENT_WHEN_IDLE = int(os.environ.get("RECYCLE_RAM_PERCENT_WHEN_IDLE", "50"))
4150

4251

4352
if LOG_DIR.is_dir():
@@ -212,15 +221,18 @@ def get(self):
212221

213222
# 4) tracemalloc top allocators since boot baseline
214223
tm_lines = []
215-
try:
216-
current = tracemalloc.take_snapshot()
217-
stats = current.compare_to(_tracemalloc_baseline, "lineno")[:25]
218-
for stat in stats:
219-
tm_lines.append(
220-
f" +{stat.size_diff/1024/1024:7.2f} MiB ({stat.count_diff:+d} blocks) {stat.traceback[0]}"
221-
)
222-
except Exception as e:
223-
tm_lines.append(f"tracemalloc unavailable: {e}")
224+
if not _TRACEMALLOC_ENABLED:
225+
tm_lines.append("tracemalloc disabled (set TRACEMALLOC_ENABLED=1 to enable)")
226+
else:
227+
try:
228+
current = tracemalloc.take_snapshot()
229+
stats = current.compare_to(_tracemalloc_baseline, "lineno")[:25]
230+
for stat in stats:
231+
tm_lines.append(
232+
f" +{stat.size_diff/1024/1024:7.2f} MiB ({stat.count_diff:+d} blocks) {stat.traceback[0]}"
233+
)
234+
except Exception as e:
235+
tm_lines.append(f"tracemalloc error: {e}")
224236

225237
self.set_header("Content-Type", "text/plain; charset=utf-8")
226238
out = [

src/aind_ephys_portal/panel/ephys_gui.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ctypes
22
import json
3+
import os
34
import psutil
45
import param
56
import time
@@ -79,6 +80,12 @@
7980
)
8081

8182

83+
# Default OFF until we verify the refcount guard doesn't race with concurrent
84+
# sessions that may grab a cached fsspec FS microseconds after we check. Flip
85+
# via FSSPEC_DROP_INSTANCE_CACHE=1 once we have confidence.
86+
_FSSPEC_DROP_INSTANCE_CACHE = os.environ.get("FSSPEC_DROP_INSTANCE_CACHE", "0").lower() in ("1", "true", "yes")
87+
88+
8289
def _malloc_trim():
8390
"""Force glibc to return freed memory to the OS (Linux only)."""
8491
try:
@@ -599,8 +606,9 @@ def cleanup(self):
599606
def _deferred_gc():
600607
gc.collect()
601608
gc.collect()
602-
_clear_fsspec_instance_caches()
603-
gc.collect()
609+
if _FSSPEC_DROP_INSTANCE_CACHE:
610+
_clear_fsspec_instance_caches()
611+
gc.collect()
604612
_malloc_trim()
605613
final_mem = psutil.virtual_memory()
606614
used = final_mem.used / (1024**3)
@@ -612,8 +620,9 @@ def _deferred_gc():
612620
# Fallback: run immediately if IOLoop is unavailable
613621
gc.collect()
614622
gc.collect()
615-
_clear_fsspec_instance_caches()
616-
gc.collect()
623+
if _FSSPEC_DROP_INSTANCE_CACHE:
624+
_clear_fsspec_instance_caches()
625+
gc.collect()
617626
_malloc_trim()
618627
final_mem = psutil.virtual_memory()
619628
used = final_mem.used / (1024**3)

0 commit comments

Comments
 (0)