Skip to content

fix: hold references to asyncio tasks#9261

Merged
akshayka merged 2 commits intomainfrom
aka/fix-background-task-refs
Apr 20, 2026
Merged

fix: hold references to asyncio tasks#9261
akshayka merged 2 commits intomainfrom
aka/fix-background-task-refs

Conversation

@akshayka
Copy link
Copy Markdown
Contributor

@akshayka akshayka commented Apr 18, 2026

References to tasks scheduled with asyncio.create_task() must be held; if they aren't held, tasks may disappear mid-execution.

https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task

This PR also enables RUF006 to ensure that this does not happen again.

References to tasks scheduled with asyncio.create_task() must be held;
if they aren't held, tasks may disappear mid-execution.

https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task
Copilot AI review requested due to automatic review settings April 18, 2026 15:48
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Apr 18, 2026 3:49pm

Request Review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses asyncio “fire-and-forget” task lifetime issues by ensuring tasks created via asyncio.create_task() are strongly referenced, and it enables Ruff’s RUF006 rule to prevent regressions.

Changes:

  • Enable Ruff lint rule RUF006 (store a reference to the return value of asyncio.create_task).
  • Keep strong references to background tasks in several runtime/server components to prevent premature garbage collection.
  • Introduce module-level background task registries for certain WebSocket-related fire-and-forget tasks.

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
pyproject.toml Enables Ruff RUF006 to enforce holding references to asyncio.create_task() results.
marimo/_utils/file_watcher.py Stores the polling watcher task on the instance to prevent GC mid-poll.
marimo/_server/api/middleware.py Stores a reference to the inactivity monitor task to prevent GC.
marimo/_server/api/endpoints/ws_endpoint.py Tracks background close tasks in a module-level set to prevent GC mid-flight.
marimo/_server/api/endpoints/ws/ws_kernel_ready.py Tracks background RTC doc creation tasks in a module-level set to prevent GC mid-flight.
Comments suppressed due to low confidence (1)

marimo/_utils/file_watcher.py:80

  • PollingFileWatcher now keeps a strong reference to the polling task, but stop() only flips _running and never cancels/clears the task. If _poll() raises (e.g., callback error), the exception may be silently hidden indefinitely because the task is never awaited/GC’d. Consider adding a done-callback to (1) log/retrieve exceptions and (2) clear self._task, and cancel the task in stop() when it’s still running.
    def start(self) -> None:
        self._running = True
        # Hold a strong reference so the task isn't GC'd mid-poll.
        self._task = self.loop.create_task(self._poll())

    def stop(self) -> None:
        self._running = False

Comment on lines 696 to 700
self.timeout_duration_minutes = timeout_duration_minutes

asyncio.create_task(self.monitor())
# Hold a strong reference so the monitor task isn't GC'd.
self._monitor_task = asyncio.create_task(self.monitor())

Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TimeoutMiddleware holds onto the monitor task, but the task is never awaited and no done-callback retrieves/logs exceptions. With a strong reference, a failure inside monitor() can become silent (no “Task exception was never retrieved” warning). Consider adding a done-callback that logs task.exception() (handling CancelledError) and/or cancelling the task during shutdown/lifespan teardown.

Copilot uses AI. Check for mistakes.
Comment on lines +506 to +512
task = asyncio.create_task(
self._safe_close(
WebSocketCodes.NORMAL_CLOSE, "MARIMO_SHUTDOWN"
)
)
_background_tasks.add(task)
task.add_done_callback(_background_tasks.discard)
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The done-callback only discards the task from _background_tasks. If _safe_close() raises, the exception will remain unobserved and may later emit a noisy “Task exception was never retrieved” warning. Consider a done-callback that both discards the task and explicitly retrieves/logs task.exception() (ignoring cancellations).

Copilot uses AI. Check for mistakes.
Comment on lines +190 to +194
task = asyncio.create_task(
doc_manager.create_doc(file_key, cell_ids, codes)
)
_background_tasks.add(task)
task.add_done_callback(_background_tasks.discard)
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The background task is discarded on completion, but exceptions aren’t retrieved/logged. If doc_manager.create_doc(...) fails, this can surface as a late “Task exception was never retrieved” warning with little context. Consider adding a done-callback that discards the task and logs task.exception() (handling cancellations).

Copilot uses AI. Check for mistakes.
@akshayka akshayka added the bug Something isn't working label Apr 18, 2026
@akshayka akshayka merged commit f6f5753 into main Apr 20, 2026
36 of 54 checks passed
@akshayka akshayka deleted the aka/fix-background-task-refs branch April 20, 2026 21:20
@github-actions
Copy link
Copy Markdown

🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.2-dev70

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants