datasette/datasette
Alex Garcia 0070b1bf13 Propagate otel context across the thread boundaries
Spans created on a worker thread resolve their parent from that thread's
ambient context, so without this every span produced below Database came
back as an unparented root, disconnected from the request that caused it.
Carrying the caller's context across each boundary is also what makes the
thread-pool wait visible: db.query covers the full round trip, the new
db.query.execute covers only the work inside the worker, and the gap
between them is the queueing the old tracer folds invisibly into one
number.

- execute_fn()'s executor.submit() and execute_isolated_fn()'s
  run_in_executor() (immutable databases) now run the callable inside a
  contextvars.copy_context(). A *fresh* copy per submit is required:
  concurrently entering one shared Context raises "RuntimeError: cannot
  enter context ... already entered".
- WriteTask carries the otel Context captured on the event loop at enqueue
  time plus an enqueued_at_ns timestamp (both need __slots__ entries, or
  they fail with AttributeError at runtime). _execute_writes attaches that
  context right after the _SHUTDOWN check and detaches it in a finally
  spanning all three execution branches - the write thread is persistent
  and shared, so a leaked token would grow its context stack for every
  write processed afterwards, and a wrong-token detach only logs rather
  than raising.
- New spans: db.query.execute (read worker thread), db.write.queue_wait
  (explicit start/end timestamps, so its duration is the real enqueue ->
  dequeue wait rather than the microseconds spent building the span) and
  db.write.execute (skipped in the conn_exception branch, where fn never
  runs). db.query.execute honours log_sql_errors for the same reason
  db.query does: facet suggestion probes with log_sql_errors=False and
  would otherwise paint two red spans per text column on every table page.
- The write-thread warm-up prepare_connection is left as a documented
  orphan root - no caller context exists that early.

Tests assert actual parent/child span-id relationships in a shared trace,
not just that spans exist, since an unparented root looks identical to a
correct span if you only check presence.

Note that copy_context() copies every ContextVar, not just OTel's, so
Datasette's own context vars (_skip_permission_checks,
_permission_check_cache, _in_datasette_client) now flow into worker
threads where they previously did not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 14:05:39 -07:00
..
default_permissions Deny SQLite statistics table access through a default hook 2026-09-10 16:52:25 -07:00
publish Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
static Tweak display of count all button, refs #2914 2026-09-15 10:16:16 -07:00
templates Tweak display of count all button, refs #2914 2026-09-15 10:16:16 -07:00
utils Migrate from httpx to httpx2, closes #2879 2026-09-10 19:44:49 -07:00
views Fix infinite loop when streaming CSV on SQL views, fixes #2902 (#2903) 2026-09-15 12:57:09 -07:00
__init__.py Add datasette.add_background_task() with supervised launch after startup (#2889) 2026-09-15 10:55:54 -07:00
__main__.py Add support for running datasette as a module (#556) 2019-07-11 09:07:44 -07:00
_pytest_plugin.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
actor_auth_cookie.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
app.py Add /-/tasks introspection endpoint for supervised background tasks (#2892) 2026-09-15 11:56:53 -07:00
background_tasks.py Add /-/tasks introspection endpoint for supervised background tasks (#2892) 2026-09-15 11:56:53 -07:00
blob_renderer.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
cli.py Add datasette.add_background_task() with supervised launch after startup (#2889) 2026-09-15 10:55:54 -07:00
column_types.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
csrf.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
database.py Propagate otel context across the thread boundaries 2026-09-15 14:05:39 -07:00
default_actions.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
default_column_types.py Normalize URL column schemes consistently 2026-09-08 21:16:35 -07:00
default_database_actions.py No execute-write on immutable databases 2026-05-25 12:46:21 -07:00
default_debug_menu.py Autocomplete widget and /-/debug/autocomplete test page 2026-06-13 22:59:37 -07:00
default_jump_items.py Remove source and source_key columns from JumpSQL 2026-05-23 20:41:32 -07:00
default_magic_parameters.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
default_query_actions.py Web UI to edit and delete stored queries (#2764) 2026-06-08 20:19:47 -07:00
default_table_actions.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
events.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
extras.py Return 400 for unknown _extra names on data formats 2026-07-04 16:16:02 +00:00
facets.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
filters.py Fix float coercion for numeric filter parameters (#2876) 2026-09-15 13:14:29 -07:00
fixtures.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
forbidden.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
handle_exception.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
hookspecs.py Add shutdown() plugin hook with ordered graceful teardown (#2890) 2026-09-15 11:09:57 -07:00
inspect.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
jump.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
permissions.py Match table permission identities using SQLite case semantics 2026-09-09 08:39:03 -07:00
plugins.py Deny SQLite statistics table access through a default hook 2026-09-10 16:52:25 -07:00
renderer.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
resources.py Match table permission identities using SQLite case semantics 2026-09-09 08:39:03 -07:00
sql_functions.py _search= queries now correctly escaped, fixes #651 2019-12-29 18:48:30 +00:00
stored_queries.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
telemetry.py Add opentelemetry-api dependency and datasette/telemetry.py scaffolding 2026-09-15 14:05:39 -07:00
template_contexts.py Clarify template context metadata names 2026-06-23 11:30:30 -07:00
tokens.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
tracer.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
url_builder.py Upgrade to ruff>=0.16.0 (#2857) 2026-07-25 15:47:08 -07:00
version.py Release 1.0a39 2026-09-10 16:52:52 -07:00
write_sql.py Reject untrusted table-valued PRAGMA reads 2026-09-08 21:16:35 -07:00