Skip to content

Add awaitable UDF server shutdown - #143

Open
ShubMishra wants to merge 1 commit into
singlestore-labs:mainfrom
ShubMishra:skumar/udf-graceful-shutdown
Open

ShubMishra wants to merge 1 commit into
singlestore-labs:mainfrom
ShubMishra:skumar/udf-graceful-shutdown

Conversation

@ShubMishra

@ShubMishra ShubMishra commented Oct 15, 2026 •

Copy link
Copy Markdown

Summary

Add await singlestoredb.apps.shutdown_udf_app(timeout=...) so the notebook runtime can stop the UDF HTTP server before terminating its Python kernel.

This uses Uvicorn's shutdown path to close listeners/idle connections and drain active requests, waits for the serving task, and closes remaining sockets after the drain deadline. The caller supplies the budget; None preserves the existing unbounded in-kernel replacement behavior. No pod grace period or automatic signal hook is introduced.

Test Plan

  • .venv/bin/python -m pytest --noconftest -o addopts="" -q singlestoredb/tests/test_function_url.py singlestoredb/tests/test_udf_event_loop.py — passed: 37 tests and 4 subtests, including real HTTP connections for idle closure, active-call completion, deadline expiry, cancelled waiters, restart, and shutdown during startup.
  • .venv/bin/pre-commit run --files singlestoredb/apps/_python_udfs.py singlestoredb/apps/__init__.py singlestoredb/tests/test_function_url.py ARCHITECTURE.md — passed, including lint and type checks.
  • Pod/Jupyter integration is not included or staging-tested here. Synchronous Python work cannot be forcibly stopped by this API.

Helios follow-up

After this SDK change is released, a separate Helios PR will update the SDK pin/notebook image, make Jupyter await draining before kernel shutdown, and coordinate Nova's pod grace period and network cleanup. Gateway handling of abrupt failures remains separate. This provides the SDK entry point without choosing the production drain budget.

Tracked under MCDB-100416.


Note

Medium Risk
Changes notebook UDF server lifecycle and connection draining at kernel shutdown; behavior is well-tested but affects in-flight UDF HTTP traffic during restarts and pod teardown.

Overview
Adds await singlestoredb.apps.shutdown_udf_app(timeout=...) so notebook runtimes can stop the managed UDF HTTP server on the same asyncio loop as run_udf_app, before tearing down the kernel.

Shutdown drives Uvicorn’s graceful path: listeners and idle connections close immediately, in-flight requests get up to timeout seconds (or unbounded when timeout=None, matching in-kernel server replacement), then remaining sockets are aborted and globals are cleared. run_udf_app now keeps the serving asyncio.Task and delegates existing-server teardown to this API instead of calling server.shutdown() directly.

ARCHITECTURE.md documents host responsibilities (drain budget vs pod deadline; no K8s/Jupyter hooks). Integration tests cover idle shutdown, active-request draining, deadline expiry, cancelled waiters, restart, startup races, and invalid timeouts.

Reviewed by Cursor Bugbot for commit 01e4f68. Bugbot is set up for automated code reviews on this repo. Configure here.

Expose a caller-budgeted shutdown operation for notebook runtime integration. Await the Uvicorn serving task, preserve existing replacement behavior, and keep shutdown coverage with the existing lifecycle tests.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 01e4f68. Configure here.

server.should_exit = True
try:
# Cancelling a caller must not interrupt the server's own drain.
await asyncio.shield(task)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Failed serve task breaks shutdown

Medium Severity

shutdown_udf_app awaits the serving task with asyncio.shield, so a prior serve failure is reraised. After run_udf_app fails during startup, it still leaves _running_server and _running_server_task set, so the next shutdown or replacement start surfaces that leftover error and does not bring a new server up.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 01e4f68. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Cancelling the sole shutdown waiter can permanently skip cleanup and leave startup listeners open.

1 open issue
What changed in this PR

Adds awaitable shutdown for managed notebook UDF servers so hosts can drain requests before terminating the kernel.

Changes:

  • Exports shutdown_udf_app(timeout=...) and tracks the serving task.
  • Uses the shutdown API when replacing an existing server.
  • Documents host responsibilities and adds lifecycle tests.
File Description
singlestoredb/​tests/​test_function_url.py Adds shutdown and connection-draining tests.
singlestoredb/​apps/​_python_udfs.py Implements awaitable shutdown and serving-task tracking.
singlestoredb/​apps/​__init__.py Exports the shutdown API.
ARCHITECTURE.md Documents drain budgets and host coordination.

Review effort: Balanced

Comment on lines +133 to +147
try:
# Cancelling a caller must not interrupt the server's own drain.
await asyncio.shield(task)
finally:
if task.done():
# Uvicorn can skip shutdown when exit was requested during startup.
if server.started:
for listener in server.servers:
listener.close()
# Uvicorn can finish after cancelling requests but before they close.
for connection in list(server.server_state.connections):
connection.transport.abort()
if _running_server is server:
_running_server = None
_running_server_task = None

@kesmit13 kesmit13 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I see any issues, but it might be better for @mgiannakopoulos to approve before we merge.

This branch had an error being deployed

1 failed deployment
Base — 01e4f687 Deployed Oct 16, 2026 by ShubMishra via test-coverage #549
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