Skip to content

fix: stop per-click gc.collect stalling the server; add stall/RSS watchdog - #66

Merged
m-beau merged 1 commit into
m-beau:mainfrom
tabedzki:tabedzki/fix/selection-gc-stall
Oct 16, 2026
Merged

m-beau merged 1 commit into
m-beau:mainfrom
tabedzki:tabedzki/fix/selection-gc-stall

Conversation

@tabedzki

Copy link
Copy Markdown
Collaborator

🤖 AI text below 🤖

Refs tabedzki#6

Problem

The production container (ghcr.io/m-beau/pixelmap) runs for hours and then dies with no traceback. In about 14 h of logs:

  • The 60 s Memory usage log fired 20–90 s late 23 times, nearly always right after Selection changed: [] -> [N]. A late log means the single Bokeh event loop was blocked that long.
  • During one of those stalls the proxy dropped a user's websocket. That produced Failed sending message as connection was closed followed by 236 AssertionError: assert self._session is not None on queued PATCH-DOC messages.
  • RSS sat at about 3.1–3.5 GB, with 50–150 MB swings on each click.

on_electrode_selection ran a full gc.collect() on every tap.

Changes

  • Remove the per-click gc.collect().
  • New pixelmap/gui/watchdog.py, which only logs and never raises or kills anything:
    • Stall watchdog: the event loop beats a heartbeat; a daemon thread logs [watchdog] event loop blocked for X s with the blocked thread's stack (PIXELMAP_STALL_WARN_S, default 5, 0 disables). It logs again with the total duration when the loop recovers.
    • Memory watch: warns once when RSS crosses PIXELMAP_RSS_WARN_MB and re-arms below 90%, so RSS hovering at the limit doesn't flood Slack.
    • Installed once per process from app_util.main(local=False); thresholds set in docker-compose.yml.
  • Regression tests: tests/test_selection_responsiveness.py (no gc on tap, box select or repeated taps; toggle and empty-selection behaviour kept) and tests/test_watchdog.py (threshold edges, report-once-per-stall, recovery and re-arm, stack capture, disabled/0/negative/None configs, idempotent install).

Testing

  • The 3 gc regression tests failed before the fix and pass after.
  • Full suite: 557 passed.
  • Smoke-tested with a real panel serve (very low thresholds): the stall warning with stack, the RSS warning and the recovery line all appear in the server log.

Related

Assisted-by: ClaudeCode:claude-opus-5-5

…chdog

Production logs show the single-threaded Bokeh event loop freezing for
20-90 s after nearly every electrode click: the periodic memory log fires
late right after each "Selection changed". on_electrode_selection ran a full
gc.collect() on every tap, which walks every live object in a multi-GB
process. While frozen, healthchecks fail and the proxy drops websockets
(the burst of 236 AssertionErrors on PATCH-DOC for an already-destroyed
session), and the container eventually dies without a traceback.

- Remove the gc.collect() from on_electrode_selection.
- Add pixelmap/gui/watchdog.py: a heartbeat on the event loop plus a
  daemon thread that logs a "[watchdog]" WARNING with the blocked thread's
  stack when the loop stalls (PIXELMAP_STALL_WARN_S, default 5 s; 0
  disables), logs again on recovery, and warns once when RSS crosses
  PIXELMAP_RSS_WARN_MB (with re-arm hysteresis). It only logs; it never
  raises into the app or kills it.
- Install it once per process from app_util.main(local=False) and set
  the thresholds in docker-compose.yml.
- Regression tests for the per-click gc and for the watchdog.

Assisted-by: ClaudeCode:claude-opus-5-5
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 90.65421% with 10 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@8a792fb). Learn more about missing BASE report.

Files with missing lines Patch % Lines
pixelmap/gui/watchdog.py 92.38% 8 Missing ⚠️
pixelmap/gui/app_util.py 0.00% 2 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@           Coverage Diff           @@
##             main      #66   +/-   ##
=======================================
  Coverage        ?   67.57%           
=======================================
  Files           ?       20           
  Lines           ?     3106           
  Branches        ?        0           
=======================================
  Hits            ?     2099           
  Misses          ?     1007           
  Partials        ?        0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tabedzki
tabedzki marked this pull request as ready for review October 16, 2026 15:51
@m-beau
m-beau merged commit 4dfcfc7 into m-beau:main Oct 16, 2026
1 check passed
tabedzki added a commit to tabedzki/channelmap_generator that referenced this pull request Oct 17, 2026
Release candidate so the Docker image picks up the Panel session-leak fix
(m-beau#65) and the per-click gc.collect stall fix plus watchdog (m-beau#66). The
v1.4.0 image leaks ~27-30 MB per closed session; an overnight scanner
built ~160 sessions and pushed prod RSS to 4.45 GB against a 4500M cap.

CITATION.cff stays on 1.4.0, the archived JOSS release.

Assisted-by: ClaudeCode:claude-opus-5-5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants