3535from spikeinterface .core .zarrextractors import super_zarr_open
3636from spikeinterface .curation import validate_curation_dict
3737
38- from aind_ephys_portal .panel . logging import (
38+ from aind_ephys_portal .session_logging import (
3939 setup_logging ,
4040 local_log_context ,
4141 can_admit_new_session ,
4747 get_ecs_task_id ,
4848 get_container_total_memory ,
4949 get_container_used_memory ,
50+ record_session_rejection ,
51+ record_pending_load ,
52+ clear_pending_load ,
53+ record_admission ,
54+ admission_cooldown_remaining ,
5055 remove_session ,
5156)
5257from aind_ephys_portal .panel .utils import PostMessageListener , FullscreenResizeHandler
@@ -211,6 +216,34 @@ def __init__(
211216 ram_percent = get_container_used_memory () / get_container_total_memory () * 100
212217 total_ram_bytes = get_container_total_memory ()
213218
219+ # Admission cooldown: if another session was admitted within the
220+ # last few seconds, soft-reject this one. The previous admission's
221+ # pending-load entry needs a moment to register so concurrent
222+ # admissions don't all read the same stale `used + pending` sum.
223+ # NB: this is sequencing, not real capacity pressure — we do NOT
224+ # call record_session_rejection() so /health doesn't inflate.
225+ cooldown_remaining = admission_cooldown_remaining ()
226+ if cooldown_remaining > 0 :
227+ print (
228+ f"Soft-rejecting (admission cooldown): { cooldown_remaining :.1f} s remaining. "
229+ f"Task ID: { task_id } "
230+ )
231+ doc = pn .state .curdoc
232+ route = getattr (doc , "_log_route" , None )
233+ session_id = getattr (doc , "_log_session_id" , None )
234+ if route and session_id :
235+ remove_session (route , session_id )
236+ self .layout = pn .Column (
237+ header ,
238+ pn .pane .Markdown (
239+ f"⏱️ Another session is being admitted — please retry in "
240+ f"a few seconds. (Task: `{ task_id } `)" ,
241+ sizing_mode = "stretch_both" ,
242+ ),
243+ sizing_mode = "stretch_both" ,
244+ )
245+ return
246+
214247 # Try to derive a *dataset-specific* RAM estimate by reading the unit counts
215248 # unit count. This costs one S3 round-trip (~1-3s) but lets us accurately predict heavy
216249 # sessions instead of relying on a fixed percent.
@@ -227,6 +260,11 @@ def __init__(
227260 f"{ est_bytes / (1024 ** 3 ):.2f} GB ({ estimate_pct :.1f} %) "
228261 f"for { num_units } units, fast_mode={ self .fast_mode } "
229262 )
263+ # Teach /health what the largest recent session looked like
264+ # — its admission predicate uses this so it can fire the
265+ # inflate signal when a *heavy* session arrives instead of
266+ # relying on the static fallback percent.
267+ record_session_estimate (estimate_pct )
230268 except Exception as e :
231269 # Pre-load failed (S3 transient, bad path, etc.) — fall back
232270 # to the static estimate. Better to occasionally reject a
@@ -240,6 +278,11 @@ def __init__(
240278 used_pct = ram_percent ,
241279 estimate_pct = estimate_pct ,
242280 ):
281+ # Tell /health a rejection just happened so it can fire the
282+ # inflate / scale-up signal on its next tick. Without this,
283+ # /health would see the task's current RAM and think there's
284+ # still room — even though admission just turned a session away.
285+ record_session_rejection ()
243286 # Remove this session from the count — it is being rejected
244287 doc = pn .state .curdoc
245288 route = getattr (doc , "_log_route" , None )
@@ -269,6 +312,19 @@ def __init__(
269312 sizing_mode = "stretch_both" ,
270313 )
271314 elif self .analyzer_path != "" :
315+ # Admission succeeded — register this session's estimated RAM so
316+ # later admission attempts (within the same ~10s loading window)
317+ # see it as "pending" instead of reading a stale-low used_pct from
318+ # cgroup. Falls back to the static estimate if the dynamic one
319+ # could not be computed. The pending entry is cleared in cleanup()
320+ # or expires after PENDING_LOAD_TTL (60s).
321+ self ._pending_session_id = getattr (pn .state .curdoc , "_log_session_id" , None )
322+ pending_pct = estimate_pct if estimate_pct is not None else get_per_session_estimate_pct ()
323+ record_pending_load (self ._pending_session_id , pending_pct )
324+ # Start the admission-cooldown clock so the next arrival waits
325+ # for THIS pending-load entry to be visible.
326+ record_admission ()
327+
272328 self .layout = pn .Column (
273329 header ,
274330 self ._create_main_window (),
@@ -517,6 +573,13 @@ def cleanup(self):
517573 current_ram_usage = initial_mem .used / (1024 ** 3 )
518574 print (f"\n RAM Usage before cleanup: { current_ram_usage :.2f} / { total_ram :.2f} GB\n " )
519575
576+ # 0) Release this session's pending-load reservation so the next
577+ # admission attempt sees accurate RAM headroom. (The actual cgroup
578+ # `memory.current` won't drop until the deferred GC further down
579+ # runs malloc_trim — but the pending entry was overestimating, so
580+ # dropping it now lets other admissions proceed sooner.)
581+ clear_pending_load (getattr (self , "_pending_session_id" , None ))
582+
520583 # 1) Clear postMessage listeners and submit trigger (they hold bound-method back-refs to self)
521584 self .curation_listener = None
522585 self .fullscreen_listener = None
0 commit comments