mirror of
https://github.com/simonw/datasette.git
synced 2026-09-16 05:24:21 +02:00
Add /-/tasks introspection endpoint for supervised background tasks (#2892)
Co-authored-by: Simon Willison <swillison@gmail.com>
This commit is contained in:
parent
784695aea6
commit
374b194ff5
9 changed files with 218 additions and 52 deletions
|
|
@ -12,6 +12,17 @@ Unreleased
|
|||
- Fixed incorrect counts when clicking **count all** on filtered table pages. The button now uses a new :ref:`POST count endpoint <TableCountView>`. (:issue:`2914`)
|
||||
- Datasette now uses `httpx2 <https://httpx2.pydantic.dev/>`__, the Pydantic-maintained continuation of `httpx <https://www.python-httpx.org/>`__, in place of ``httpx``. The public API is the same, but responses returned by :ref:`internals_datasette_client` are now ``httpx2.Response`` objects rather than ``httpx.Response``. Plugins that use ``isinstance()`` checks against ``httpx.Response`` should be updated to use ``httpx2``. **Plugins that use httpx without explicitly depending on it** will need to add an explicit dependency or switch to `httpx2`.
|
||||
|
||||
Background tasks
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Datasette plugins can now use **background tasks** to run code independent of the Datasette request/response cycle.
|
||||
|
||||
- New :ref:`datasette_add_background_task` API: plugins register supervised, long-lived background work - typically from a ``startup`` hook - and these will be launched after every ``startup`` hook has run. Tasks are cancelled (with a five-second grace period) on shutdown.
|
||||
- New ``/-/tasks`` JSON debug endpoint lists every supervised background task and its state, in the style of ``/-/threads``. See :ref:`JsonDataView_tasks`. It requires the ``permissions-debug`` permission.
|
||||
- New :ref:`plugin_hook_shutdown` plugin hook, called during graceful shutdown (Ctrl-C, ``SIGTERM``) before background tasks are cancelled and before database connections are closed. It is not called on a hard kill (``SIGKILL``).
|
||||
- Plugin ``asgi_wrapper`` middleware now always runs *after* startup has completed.
|
||||
- If your plugin uses ``asgi_wrapper`` to start background tasks on the first incoming request, you should migrate to ``datasette.add_background_task()`` instead. `datasette-cron <https://datasette.io/plugins/datasette-cron>`__ and `datasette-enrichments <https://datasette.io/plugins/datasette-enrichments>`__ are being migrated to this pattern.
|
||||
|
||||
.. _v1_0_a39:
|
||||
|
||||
1.0a39 (2026-09-10)
|
||||
|
|
|
|||
|
|
@ -1440,7 +1440,7 @@ All three paths call the same idempotent internal methods, so it is safe for mor
|
|||
A coroutine function taking one positional argument, the ``Datasette`` instance. Core calls ``await func(datasette)``.
|
||||
|
||||
``name`` - string, optional
|
||||
A name for the task, used to identify it in log messages. Defaults to ``func.__qualname__``. If the resulting name collides with an already-registered task, a ``-2``, ``-3``, ... suffix is appended.
|
||||
A name for the task, used to identify it in the ``/-/tasks`` introspection endpoint (:ref:`JsonDataView_tasks`) and in log messages. Defaults to ``func.__qualname__``. If the resulting name collides with an already-registered task, a ``-2``, ``-3``, ... suffix is appended.
|
||||
|
||||
Registers a piece of supervised, long-lived background work — typically called from a :ref:`plugin_hook_startup` hook, though it can be called at any point after the instance exists, including from a request handler. Returns a :ref:`BackgroundTask <BackgroundTask>` handle.
|
||||
|
||||
|
|
@ -1470,8 +1470,6 @@ Core owns the task for the rest of the process's life:
|
|||
- **A crash is logged, not swallowed.** If ``func`` raises anything other than ``asyncio.CancelledError``, the exception (with its traceback) is logged to the ``datasette.background_tasks`` logger and recorded on the handle's ``.exception``, and the task's ``.state`` becomes ``crashed``.
|
||||
- **Cancellation is coordinated.** On shutdown, every task that is still running is cancelled and given a grace period to stop — see :ref:`datasette_lifecycle`.
|
||||
|
||||
Raw ``asyncio.create_task()`` inside a ``startup`` hook now works correctly, because ``startup`` hooks run on the serving event loop (see the admonition in :ref:`datasette_lifecycle`) — the bug that made this unsafe is fixed. But a task created that way is unsupervised: nothing keeps a reference to it, nothing logs its exceptions, and nothing cancels it on shutdown. Prefer ``add_background_task()`` for anything long-lived.
|
||||
|
||||
Launch matrix
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
|
|
@ -1515,12 +1513,14 @@ BackgroundTask objects
|
|||
``.started_at`` - string or ``None``
|
||||
ISO 8601 UTC timestamp of when the task was launched.
|
||||
|
||||
``.plugin`` - string or ``None``
|
||||
Best-effort name of the plugin that registered the task, resolved from the module ``func`` was defined in. Used in log messages; ``None`` if it cannot be determined.
|
||||
``.function`` - string
|
||||
The callable's dotted module and qualified name, for example ``my_plugin.jobs.poll_for_updates``.
|
||||
|
||||
``.cancel()``
|
||||
Cancel the task. If it has already launched, this cancels the underlying ``asyncio.Task`` — ``.state`` becomes ``cancelled`` once the cancellation is observed. If it has not launched yet, it is removed from the queue so it never runs.
|
||||
|
||||
This is also the shape of each entry returned by the ``/-/tasks`` JSON introspection endpoint — see :ref:`JsonDataView_tasks`.
|
||||
|
||||
.. _datasette_start_background_tasks:
|
||||
|
||||
await .start_background_tasks()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Each of these pages can be viewed in your browser. Add ``.json`` to the URL to g
|
|||
|
||||
JSON responses that return an object include an ``"ok": true`` key, consistent with the rest of the :ref:`JSON API <json_api>`.
|
||||
|
||||
The introspection endpoints documented on this page are covered by the :ref:`JSON API stability promise <json_api_stability>`, with the exception of the debug endpoints ``/-/threads`` and ``/-/actions``, whose shapes may change in future releases.
|
||||
The introspection endpoints documented on this page are covered by the :ref:`JSON API stability promise <json_api_stability>`, with the exception of the debug endpoints ``/-/threads``, ``/-/tasks`` and ``/-/actions``, whose shapes may change in future releases.
|
||||
|
||||
.. _JsonDataView_metadata:
|
||||
|
||||
|
|
@ -278,6 +278,42 @@ Shows details of threads and ``asyncio`` tasks. This endpoint requires the ``per
|
|||
]
|
||||
}
|
||||
|
||||
.. _JsonDataView_tasks:
|
||||
|
||||
/-/tasks
|
||||
--------
|
||||
|
||||
Shows the state of every supervised background task registered with :ref:`datasette.add_background_task() <datasette_add_background_task>`; see also :ref:`BackgroundTask <BackgroundTask>` for what each field below means, and :ref:`datasette_lifecycle` for when tasks are launched. This endpoint requires the ``permissions-debug`` permission, since a crashed task's ``exception`` field can reveal internals such as file paths or query text:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"ok": true,
|
||||
"tasks": [
|
||||
{
|
||||
"name": "my_plugin.poll_for_updates",
|
||||
"state": "running",
|
||||
"function": "my_plugin.poll_for_updates",
|
||||
"started_at": "2026-07-30T12:00:00+00:00",
|
||||
"exception": null
|
||||
},
|
||||
{
|
||||
"name": "my_plugin.broken_task",
|
||||
"state": "crashed",
|
||||
"function": "my_plugin.broken_task",
|
||||
"started_at": "2026-07-30T12:00:00+00:00",
|
||||
"exception": "ValueError('something went wrong')"
|
||||
}
|
||||
],
|
||||
"launched": true
|
||||
}
|
||||
|
||||
Each entry's ``function`` identifies the callable by its dotted module and qualified name.
|
||||
|
||||
Each entry's ``state`` is one of ``registered`` (added but not yet launched), ``running``, ``completed``, ``crashed`` or ``cancelled``. ``exception`` is a one-line ``repr()`` of the exception for a ``crashed`` task, or ``null`` otherwise - the full traceback is written to the ``datasette.background_tasks`` logger instead, to keep this payload skimmable.
|
||||
|
||||
The top-level ``launched`` flag reports whether the instance has run its one-time background task launch (after ``startup`` hooks finish, or via lifespan/first-request/:ref:`start_background_tasks() <datasette_start_background_tasks>`). It distinguishes "no tasks have been registered" (``tasks`` is empty either way) from "tasks are registered but nothing has armed the launch yet" (``launched`` is ``false`` and every task's ``state`` is still ``registered``) - useful when debugging a host that never triggers Datasette's lifespan events.
|
||||
|
||||
.. _JsonDataView_actor:
|
||||
|
||||
/-/actor
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ Some JSON endpoints are **exempt** from this promise:
|
|||
debug playground.
|
||||
- Debug and support endpoints are documented so you can use them, but their
|
||||
JSON shapes are not frozen: :ref:`/-/threads <JsonDataView_threads>`,
|
||||
:ref:`/-/tasks <JsonDataView_tasks>`,
|
||||
:ref:`/-/actions <JsonDataView_actions>`,
|
||||
the :ref:`permission debug endpoints <PermissionsDebugView>`
|
||||
(``/-/allowed``, ``/-/rules``, ``/-/check``) and the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue