Add datasette.add_background_task() with supervised launch after startup - #2889
Open
asg017 wants to merge 5 commits into
Open
Add datasette.add_background_task() with supervised launch after startup#2889asg017 wants to merge 5 commits into
asg017 wants to merge 5 commits into
Conversation
This was referenced Aug 31, 2026
asg017
force-pushed
the
asg017/first-request-3-background-tasks-api
branch
from
August 31, 2026 20:05
ccb6c5b to
d402780
Compare
asg017
force-pushed
the
asg017/first-request-3-background-tasks-api
branch
from
August 31, 2026 20:26
d402780 to
539d766
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2889 +/- ##
======================================
Coverage 0.00% 0.00%
======================================
Files 73 74 +1
Lines 12317 12449 +132
======================================
- Misses 12317 12449 +132 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
simonw
force-pushed
the
asg017/first-request-3-background-tasks-api
branch
from
September 1, 2026 16:32
de31dc2 to
8ad0b10
Compare
simonw
marked this pull request as ready for review
September 1, 2026 16:38
Base automatically changed from
asg017/first-request-2-lifespan-startup
to
main
September 1, 2026 16:39
simonw
force-pushed
the
asg017/first-request-3-background-tasks-api
branch
from
September 1, 2026 16:39
8ad0b10 to
561fc3f
Compare
simonw
reviewed
Sep 1, 2026
| self._setup_db_done = True | ||
| await self.invoke_startup() | ||
|
|
||
| def add_background_task(self, func, name=None) -> BackgroundTask: |
Owner
There was a problem hiding this comment.
I was going to complain about lacking documentation, but that's in a later PR: https://github.com/simonw/datasette/pull/2893/changes#diff-a94fc1e6fae9a7b965684e35350146310ade0b63e5993f513e3a6f1f78056494R1436
Owner
|
I had Codex generate its own docs for the new methods to help me review them: https://gist.github.com/simonw/10f35afc79e3ecb12d0d0c6f80cf873a It wrote this example: async def refresh_cache(datasette):
while True:
# Refresh the cache here
await asyncio.sleep(60)
@hookimpl
def startup(datasette):
datasette.add_background_task(
refresh_cache,
name="refresh-cache",
) |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012U7coQfVu8nK2R4q2mCULA
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012U7coQfVu8nK2R4q2mCULA
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012U7coQfVu8nK2R4q2mCULA
…ion lifecycle Rolled down from the stack's docs-only tip PR so the API lands documented. The lifecycle section here covers only what exists at this point in the stack; the shutdown hook, wrapper-timing guarantee and /-/tasks cross-references are added by the later PRs that introduce those features. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012U7coQfVu8nK2R4q2mCULA
asg017
force-pushed
the
asg017/first-request-3-background-tasks-api
branch
from
September 2, 2026 16:49
9813016 to
6bd0103
Compare
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.
This PR adds a new
datasette.add_background_task()API for plugins. Plugins like cron, litestream, and dozens of others often need to run code "in the background", ie not during an HTTP request or on startup. This is often stuff like montoring code, backups, etc.This was possible before, but required some weird ASGI workarounds and only started after the first HTTP request. But now that #2887 is in, this works as expected.
🤖 Claude-generated PR description
Third PR in the startup/lifecycle stack, on top of #2887.
What this does
Adds
datasette.add_background_task(func, name=None)— supervised background work for plugins, replacing fire-and-forgetasyncio.create_task()calls instartuphooks. Core owns the task references (no silent garbage collection), the launch timing, crash surfacing, and cancellation.Changes
datasette/background_tasks.py:BackgroundTaskhandle (statesregistered→running→completed/crashed/cancelled, with.exception,.started_at,.plugin,.cancel()) andBackgroundTaskSupervisor(strong references for the process lifetime, idempotentlaunch_all(),cancel_all()with a grace period, crash logging with full traceback to thedatasette.background_taskslogger).datasette/app.py:add_background_task()buffers registrations until launch, or starts immediately if launch already happened;start_background_tasks()is a public entry point for embedders and headless CLI uses; private_launch_background_tasks()is wired as the secondon_startupentry in bothAsgiLifespanandAsgiRunOnFirstRequest— after_startup_sequence, so launch happens only once every plugin'sstartuphook has had a chance to register work.datasette/cli.py:--getsuppresses background-task launch — its one-shot in-process request flows through the full ASGI stack but must never start long-lived work.Docs
Rolled down from the former docs-only tip PR (#2893) so the API lands documented:
docs/internals.rst: full reference foradd_background_task(),start_background_tasks()andBackgroundTaskobjects, plus a new "Application lifecycle" section (datasette_lifecycle) describing the startup → launch → serving → shutdown sequence and the three startup trigger paths. The lifecycle text covers only what exists at this point in the stack; the shutdown hook, wrapper-timing guarantee and/-/taskscross-references are woven in by the later PRs that introduce those features.docs/plugin_hooks.rst:startup()now documents the same-event-loop guarantee and the background-task use-case.docs/testing_plugins.rst: how to launch registered tasks in tests viastart_background_tasks().Notes
/-/tasksendpoint later in the stack); a pruning policy can come later if unbounded growth shows up in practice.func.__qualname__with-2,-3… suffixes on collision.Tests
New
tests/test_background_tasks.py(10 tests): launch ordering relative to startup hooks, exactly-once launch across concurrent first requests, post-launch immediate start, cancellation semantics including grace-period stragglers, crash logging, name collisions, and the bare-Datasetteembedder path. Plus atests/test_cli_serve_get.pysentinel test that--getregisters but never launches tasks.Stack created with GitHub Stacks CLI • Give Feedback 💬