Fix dashboard backend leaks#33
Merged
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses dashboard backend stability issues by removing long-lived SSE connections that can exhaust waitress worker threads, and by adding request teardown rollback to prevent thread-local DB connections from remaining “idle in transaction”. It also adds localized tooltip titles for the 9 overview stat cards, plus regression tests to lock in the new behavior.
Changes:
- Replaced
/api/eventsSSE streaming with short-polling JSON (GET /api/events?since=<seq>). - Added
@app.teardown_requestrollback to close out any open transaction on reused thread-local connections. - Added
data-i18n-titletooltips for all overview stat cards and corresponding en/zh i18n keys, with regression tests.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
web/app.py |
Replaces SSE with short-polling JSON events endpoint and adds teardown rollback. |
web/static/js/app.js |
Replaces EventSource client logic with periodic polling of /api/events?since=…. |
web/templates/index.html |
Adds data-i18n-title tooltip markers to all 9 overview stat cards. |
web/static/js/i18n.js |
Adds en/zh tooltip translation keys and content for the stat cards. |
tests/test_web_app.py |
Adds regression tests for the polling contract, teardown rollback, EventSource removal, and tooltip/i18n coverage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+86
to
+91
| @app.teardown_request | ||
| def rollback_request_transaction(_exc): | ||
| try: | ||
| db.rollback() | ||
| except Exception: | ||
| pass |
Comment on lines
+1159
to
+1163
| """Short-poll pipeline events without holding a web worker thread.""" | ||
| since = max(0, request.args.get("since", 0, type=int) or 0) | ||
| events = get_events(since) | ||
| payload_events = json.loads(json.dumps(events, ensure_ascii=False, default=str)) | ||
| next_seq = since |
Comment on lines
+179
to
+184
| // ── Event Polling ──────────────────────────────────────────────────── | ||
|
|
||
| let sseRetryDelay = 2000; | ||
|
|
||
| function startSSE() { | ||
| if (eventSource) { | ||
| try { eventSource.close(); } catch(e) {} | ||
| eventSource = null; | ||
| } | ||
| eventSource = new EventSource('/api/events'); | ||
|
|
||
| eventSource.onopen = () => { | ||
| sseRetryDelay = 2000; | ||
| }; | ||
|
|
||
| eventSource.onmessage = (msg) => { | ||
| try { | ||
| const ev = JSON.parse(msg.data); | ||
| async function fetchEvents() { | ||
| try { | ||
| const payload = await api(`/api/events?since=${eventsSince}`); | ||
| eventsSince = payload.next_seq || eventsSince; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #32.
Changed files
web/app.pyweb/static/js/app.jsweb/templates/index.htmlweb/static/js/i18n.jstests/test_web_app.pyWhat changed
/api/eventsSSE streaming with short-polling JSON:GET /api/events?since=<seq>returns once with{events,next_seq}andCache-Control: no-cache.EventSourceclient withfetchEvents()plussetInterval(fetchEvents, 2000), so open tabs no longer hold waitress worker threads.@app.teardown_requestrollback protection so read requests close any open transaction before the thread-local connection is reused.data-i18n-titletooltips to all 9 overview stat cards and added matching English/Chinese.tipi18n keys.Tests added
/api/events?since=returns immediate JSON, preserves datetime serialization, and advancesnext_seq.db.rollback().EventSourceusage.data-i18n-title, and en/zh tooltip keys and copy match the issue table.Tests run
pytest tests/test_web_app.py -qpytestuses Homebrew Python 3.14 withoutflaskinstalled (ModuleNotFoundError: No module named 'flask')..venv/bin/pytest tests/test_web_app.py -q13 passed in 4.53s.venv/bin/pytest tests/test_web_app.py -qsource .venv/bin/activate && pytest tests/test_web_app.py -q16 passed in 3.52s.venv/bin/pytest tests/test_web_app.py -q16 passed in 3.58sPATH=.venv/bin:$PATH python -c "import web.app; print('import web.app ok')"import web.app ok/->200 text/html/api/stats->200 application/json/api/events?since=0->200 application/jsonrg -n "EventSource|text/event-stream|while True:|X-Accel-Buffering|startSSE|sseRetryDelay" web/app.py web/static/js/app.js tests/test_web_app.pyEventSource; application code has no SSE endpoint/client remnants.Before / after
/api/eventsreturnedtext/event-streamfrom an infinite synchronous generator, holding a waitress worker thread for each tab./api/events?since=<seq>returns ordinary JSON immediately, so browser polling does not occupy a web worker between polls.db.rollback()in a defensive try/except, closing read transactions after each request.applyI18nupdates them through the existingdata-i18n-titlesupport.Acceptance evidence
.venv/bin/python -c "from db import database; print(database.describe_backend())"{'backend': 'sqlite', 'database_url_configured': False, 'sqlite_path': '/home/ubuntu/Deepgraph/deepgraph.db', 'sqlite_exists': True, 'target': '/home/ubuntu/Deepgraph/deepgraph.db'}/api/statspressure check with initialized temporary SQLite DB:requests=100 status_200=100 failures=0p50_ms=80.77 p95_ms=254.30 max_ms=412.02DEEPGRAPH_DATABASE_URLis configured,dockeris not installed, andpsqlis not installed.SELECT count(*) FROM pg_stat_activity WHERE state='idle in transaction';Non-goals
agents/,orchestrator/, orcontracts/business logic.