Skip to content

Commit 2f74a9e

Browse files
authored
Release RAM and use threads instead of proc (#13)
1 parent 4973363 commit 2f74a9e

File tree

4 files changed

+36
-15
lines changed

4 files changed

+36
-15
lines changed

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ RUN git clone https://github.com/SpikeInterface/spikeinterface.git && \
3131
# Install spikeinterface-gui from source
3232
RUN git clone https://github.com/alejoe91/spikeinterface-gui.git && \
3333
cd spikeinterface-gui && \
34-
git checkout c165cfe91ea8dcfdc69836184db9827304d357f9 && \
34+
git checkout b8d461ce5e5178880a32fd25cb5c87b94dab86e1 && \
3535
pip install . && cd ..
3636

3737

3838
EXPOSE 8000
39-
ENTRYPOINT ["sh", "-c", "panel serve src/aind_ephys_portal/ephys_gui_app.py src/aind_ephys_portal/ephys_portal_app.py src/aind_ephys_portal/ephys_launcher_app.py src/aind_ephys_portal/ephys_monitor_app.py --setup src/aind_ephys_portal/setup.py --static-dirs images=src/aind_ephys_portal/images --address 0.0.0.0 --port 8000 --allow-websocket-origin ${ALLOW_WEBSOCKET_ORIGIN} --index ephys_portal_app.py --check-unused-sessions 2000 --unused-session-lifetime 5000 --num-procs 4"]
39+
ENTRYPOINT ["sh", "-c", "panel serve src/aind_ephys_portal/ephys_gui_app.py src/aind_ephys_portal/ephys_portal_app.py src/aind_ephys_portal/ephys_launcher_app.py src/aind_ephys_portal/ephys_monitor_app.py --setup src/aind_ephys_portal/setup.py --static-dirs images=src/aind_ephys_portal/images --address 0.0.0.0 --port 8000 --allow-websocket-origin ${ALLOW_WEBSOCKET_ORIGIN} --index ephys_portal_app.py --check-unused-sessions 2000 --unused-session-lifetime 5000 --num-threads 8"]

launch.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ panel serve \
1212
--static-dirs images=src/aind_ephys_portal/images \
1313
--check-unused-sessions 2000 \
1414
--unused-session-lifetime 5000 \
15-
--num-procs 4
15+
--num-threads 8

src/aind_ephys_portal/ephys_gui_app.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,7 @@ class Settings(param.Parameterized):
3535
ephys_gui = EphysGuiView(analyzer_path=settings.analyzer_path, recording_path=settings.recording_path, fast_mode=settings.fast_mode)
3636

3737
ephys_gui.panel().servable(title="AIND Ephys GUI")
38+
39+
# Don't keep a module-level reference to the heavy GUI object.
40+
# The servable layout is now owned by Panel/Bokeh's document.
41+
del ephys_gui

src/aind_ephys_portal/panel/ephys_gui.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import ctypes
12
import psutil
23
import param
34
import time
45
import gc
6+
from copy import deepcopy
57

68
import panel as pn
79

@@ -59,6 +61,13 @@
5961
zone8=["correlogram", "metrics", "mainsettings"],
6062
)
6163

64+
def _malloc_trim():
65+
"""Force glibc to return freed memory to the OS (Linux only)."""
66+
try:
67+
ctypes.CDLL("libc.so.6").malloc_trim(0)
68+
except Exception:
69+
pass
70+
6271

6372
class EphysGuiView(param.Parameterized):
6473

@@ -73,6 +82,7 @@ def __init__(self, analyzer_path, recording_path, fast_mode=False, **params):
7382
self.fast_mode = fast_mode
7483
self.analyzer = None
7584
self._cleanup_registered = False
85+
self._init_cb = None
7686

7787
self.spinner = pn.indicators.LoadingSpinner(value=True, sizing_mode="stretch_width")
7888
self.log_output = pn.widgets.TextAreaInput(value="", sizing_mode="stretch_both")
@@ -102,9 +112,15 @@ def _register_cleanup(self):
102112
return
103113
doc = pn.state.curdoc
104114
if doc is not None and hasattr(doc, "on_session_destroyed"):
115+
# Use a weak-like reference pattern: store the id and look up via the doc
116+
# to avoid the closure preventing GC of self
117+
view_ref = [self] # mutable container we can clear
105118

106-
def _on_destroy(session_context, view=self):
107-
view.cleanup()
119+
def _on_destroy(session_context):
120+
view = view_ref[0]
121+
if view is not None:
122+
view.cleanup()
123+
view_ref[0] = None # break the reference
108124

109125
doc.on_session_destroyed(_on_destroy)
110126
self._cleanup_registered = True
@@ -188,7 +204,7 @@ def _set_processed_recording(self):
188204
def _create_main_window(self):
189205
if self.analyzer is not None:
190206
# prepare the curation data using decoder labels
191-
curation_dict = default_curation_dict
207+
curation_dict = deepcopy(default_curation_dict)
192208
curation_dict["unit_ids"] = self.analyzer.unit_ids
193209
if "decoder_label" in self.analyzer.sorting.get_property_keys():
194210
decoder_labels = self.analyzer.get_sorting_property("decoder_label")
@@ -235,19 +251,17 @@ def cleanup(self):
235251

236252
# 1) Release GUI controller references
237253
if self.win is not None:
254+
self.win.controller.analyzer = None
255+
del self.win.controller.analyzer
256+
self.win.controller = None
257+
del self.win.controller
258+
self.win = None
238259
del self.win
239260

240-
# 2) Close zarr store explicitly
261+
# 2) Delete the analyzer reference to release memory-mapped files and other resources
241262
if self.analyzer is not None:
242-
# Try to close the underlying zarr store
243-
zarr_root = getattr(self.analyzer, "sorting_analyzer", None) or self.analyzer
244-
store = getattr(zarr_root, "_root", None)
245-
if store is not None:
246-
inner_store = getattr(store, "store", None)
247-
if inner_store is not None and hasattr(inner_store, "close"):
248-
inner_store.close()
249-
print("Zarr store closed.")
250263
self.analyzer = None
264+
del self.analyzer
251265
print("Analyzer resources released.")
252266

253267
# 3) Clear layout references
@@ -263,6 +277,9 @@ def cleanup(self):
263277
gc.collect()
264278
gc.collect()
265279

280+
# 5) Force glibc to return freed memory to OS
281+
_malloc_trim()
282+
266283

267284
final_mem = psutil.virtual_memory()
268285
current_ram_usage = final_mem.used / (1024**3)

0 commit comments

Comments
 (0)