@@ -206,16 +206,22 @@ def __init__(
206206 # Try to derive a *dataset-specific* RAM estimate by pre-loading the
207207 # analyzer with no extensions and reading its unit count. This costs
208208 # one S3 round-trip (~1-3s) but lets us accurately predict heavy
209- # sessions instead of relying on a fixed percent. The pre-loaded
210- # analyzer is stashed on self.analyzer so _initialize_analyzer()
211- # below can skip its own si.load() call.
209+ # sessions instead of relying on a fixed percent. We stash the
210+ # pre-loaded analyzer in a SEPARATE attribute (NOT self.analyzer) —
211+ # _create_main_window() below checks `self.analyzer is not None` to
212+ # decide whether to render the real GUI vs the help text, and an
213+ # analyzer without extensions would cause run_mainwindow to fail
214+ # silently, leaving the loading banner stuck forever.
215+ # _initialize_analyzer() transfers _preloaded_analyzer → self.analyzer
216+ # later, after the layout is set up correctly.
217+ self ._preloaded_analyzer = None
212218 estimate_pct = None
213219 estimate_units = None
214220 if self .analyzer_path and self .analyzer_path .endswith ((".zarr" , ".zarr/" )):
215221 try :
216- self .analyzer = si .load (self .analyzer_path , load_extensions = False )
217- estimate_units = len (self .analyzer .unit_ids )
218- est_bytes = estimate_session_ram_bytes (self .analyzer , fast_mode = self .fast_mode )
222+ self ._preloaded_analyzer = si .load (self .analyzer_path , load_extensions = False )
223+ estimate_units = len (self ._preloaded_analyzer .unit_ids )
224+ est_bytes = estimate_session_ram_bytes (self ._preloaded_analyzer , fast_mode = self .fast_mode )
219225 estimate_pct = est_bytes / total_ram_bytes * 100
220226 print (
221227 f"Dynamic per-session RAM estimate: "
@@ -226,7 +232,7 @@ def __init__(
226232 # Pre-load failed (S3 transient, bad path, etc.) — fall back
227233 # to the static estimate. Better to occasionally reject a
228234 # session we could have admitted than to OOM.
229- self .analyzer = None
235+ self ._preloaded_analyzer = None
230236 print (f"Could not pre-load analyzer for size estimate: { e } . Using static fallback." )
231237
232238 # NB: this is the entry point for THIS session, so it isn't counted in
@@ -239,7 +245,7 @@ def __init__(
239245 estimate_pct = estimate_pct ,
240246 ):
241247 # Drop the pre-loaded analyzer so its memory is released
242- self .analyzer = None
248+ self ._preloaded_analyzer = None
243249 # Remove this session from the count — it is being rejected
244250 doc = pn .state .curdoc
245251 route = getattr (doc , "_log_route" , None )
@@ -438,13 +444,19 @@ def _initialize_analyzer(self):
438444 if not self .analyzer_path .endswith ((".zarr" , ".zarr/" )):
439445 raise ValueError ("Only Zarr files are supported for now." )
440446 # The admission step in __init__ may have already pre-loaded the
441- # analyzer (load_extensions=False) to compute a dynamic RAM
442- # estimate. Reuse it if present to avoid a second S3 round-trip.
443- if self .analyzer is None :
447+ # analyzer (load_extensions=False) into self._preloaded_analyzer to
448+ # compute the dynamic RAM estimate. Transfer it now to self.analyzer
449+ # (the canonical attribute the rest of the code reads), and clear
450+ # the temporary handle. If pre-load wasn't done or failed, fall
451+ # back to a fresh si.load() here.
452+ preloaded = getattr (self , "_preloaded_analyzer" , None )
453+ if preloaded is not None :
454+ print (f"Reusing pre-loaded analyzer (skipped re-fetching from S3)." )
455+ self .analyzer = preloaded
456+ self ._preloaded_analyzer = None
457+ else :
444458 print (f"Loading analyzer..." )
445459 self .analyzer = si .load (self .analyzer_path , load_extensions = False )
446- else :
447- print (f"Reusing pre-loaded analyzer (skipped re-fetching from S3)." )
448460 print (f"Analyzer loaded: { self .analyzer } " )
449461
450462 def _set_processed_recording (self ):
@@ -526,6 +538,10 @@ def cleanup(self):
526538 self .curation_listener = None
527539 self .fullscreen_listener = None
528540 self .submit_trigger = None
541+ # Pre-loaded analyzer should already have been transferred to
542+ # self.analyzer by _initialize_analyzer, but null it explicitly
543+ # in case the session was rejected or failed before _initialize.
544+ self ._preloaded_analyzer = None
529545
530546 # 2) Release GUI controller and all its data
531547 sigui_win = getattr (self , "sigui_win" , None )
0 commit comments