fix(core): release tasks.py DB connections on exception + retain fire-and-forget auto-close task (#650)#688
Conversation
…-and-forget auto-close task (#650) - Wrap unguarded DB paths in tasks.py (get, list_tasks, count_by_status, update_depends_on, update_requirement_ids, delete, delete_all) in try/finally so the SQLite connection is released even when an error is raised before the normal conn.close(). - Retain a strong reference to the GitHub auto-close task scheduled on a running loop (_close_issue_background) via a module-level set + a done-callback that discards it, mirroring WebhookNotificationService.send_event_background. asyncio only keeps a weak reference, so an un-retained task could be GC'd mid-flight. - Remove the unused, buggy send_blocker_notification_background webhook wrapper (bare asyncio.create_task, no reference, no no-loop fallback) and its test. Production blocker notifications use the safe send_event_background. Closes #650
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughSeven ChangesDB connection safety & asyncio task retention (issue
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Code ReviewThis is a clean, focused robustness PR. Both fixes are correct and address real bugs. A few observations below. Connection leak fix (tasks.py)Pattern is correct. Placing Implicit rollback on write paths. For Fire-and-forget retention (_background_tasks set)Correct and thread-safe. Since Removal of send_blocker_notification_backgroundCorrect removal. The method called Tests (test_tasks_connection_safety.py)The
SummaryBoth fixes are correct and well-reasoned, the test approach is sound, and the PR is well-scoped. The main improvement opportunity is the |
Address claude-review feedback on PR #688: - Inject faults at execute() so each function body (incl. list_tasks status-filter branch) is reached, not just connection setup. - Add delete/delete_all to the commit-error parametrization (both commit, so a commit failure was also a leak vector). - Snapshot/restore _background_tasks with try/finally so the async test cannot leave the module set dirty for subsequent tests.
|
Thanks for the review. Addressed the test gaps in 0fd0295:
The implicit-rollback-on-close note is acknowledged as correct (SQLite auto-rolls back on close); no explicit rollback handler exists on these paths, so leaving it implicit keeps the diff consistent. |
Follow-up Review (commit 0fd0295)All three gaps flagged in the initial review are addressed — and the implementation in commit 0fd0295 is cleaner than the minimum required. What changedFault injection moved from
One minor note (non-blocking)In SummaryPR is clean. Implementation correct, tests thorough, scope tight. Ready to merge. |
Summary
Fixes two low-severity backend robustness issues flagged in the release-readiness audit (#650).
1. Connection leak-on-exception in
tasks.pySeveral DB paths opened
get_db_connectionand calledconn.close()with notry/finally. An exception raised between open and close leaked a connection (holding a read lock). Now wrapped intry/finally(connection acquisition stays outside thetry, mirroring the already-correct guarded paths at lines 122/208/254/370/576):get(read)list_tasks(read)count_by_status(read)update_depends_on(write)update_requirement_ids(write)delete(write)delete_all(write)2. Fire-and-forget tasks without strong references
_close_issue_background's async branch didloop.create_task(...)without retaining the task. asyncio keeps only a weak reference, so it could be garbage-collected mid-flight. Now retained in a module-levelset()with a done-callback that discards it (and consumes any exception), mirroringWebhookNotificationService.send_event_background.send_blocker_notification_background(bareasyncio.create_task, no reference, no no-loop fallback) plus its test. Production blocker notifications already use the safesend_event_background.Acceptance criteria
tasks.pyDB reads release the connection on exceptionTesting
tests/core/test_tasks_connection_safety.py: parametrized tests force a cursor/commit error and assert every opened connection is closed; async test asserts the auto-close task is retained until completion then discarded. (RED→GREEN confirmed.)ruff checkclean on all changed files.tests/core/test_tasks_edge_cases.py,test_task_dependencies.py,test_task_github_traceability.py,test_task_requirement_ids.py,tests/notifications/(78 passed);tests/ui/test_v2_routers_integration.py+test_github_integrations_v2.py(107 passed).codex review): no actionable issues.Known limitations
_close_issue_background(non-daemon thread) was already correct and is unchanged.send_blocker_notification(the underlying async method) is retained — it is not flagged by the audit and has its own test coverage; only the broken fire-and-forget wrapper was removed.Closes #650
Summary by CodeRabbit