Skip to content

Catalog UI shutdown installs the ui extension from the network to call stop_ui_server() #336

Closed

Description

@kstonekuan

What happens

serve_catalog_ui sets server_started = True immediately after _start_duckdb_ui_server
returns (src/hflow/catalog_ui.py:132-133), and shutdown calls stop_ui_server() on that flag
alone (catalog_ui.py:90-95):

def _stop_duckdb_ui_server(catalog_connection, server_started) -> None:
    try:
        if server_started:
            catalog_connection.execute("CALL stop_ui_server()")

stop_ui_server() is a function the ui extension provides. Calling it on a connection that
never loaded the extension makes DuckDB go and install ui from the network, at shutdown,
to run a no-op: there is no server to stop if the extension was never loaded.

Why it is reachable, and where it bites

tests/test_catalog_ui.py:49 replaces the real starter:

monkeypatch.setattr(catalog_ui, "_start_duckdb_ui_server", record_server_start)

The stub records the call and returns. It never runs INSTALL ui / LOAD ui, but
serve_catalog_ui still sets server_started = True, so every one of these tests reaches
CALL stop_ui_server() with no extension loaded and DuckDB fetches the extension over the
network while the test is tearing down.

That is almost certainly the test_catalog_ui timing flake three separate contributors reported
on 2026-08-31 (#326, #327, #328), all describing a thread-shutdown or timing failure that passed
on a rerun. A network fetch inside a teardown that a test is waiting on with a fixed timeout is
exactly that shape, and it makes the suite depend on network access it otherwise does not need.

What to build

Only call stop_ui_server() when the ui extension is actually loaded on that connection:

def _ui_extension_is_loaded(catalog_connection) -> bool:
    row = catalog_connection.execute(
        "SELECT count(*) FROM duckdb_extensions() WHERE extension_name = 'ui' AND loaded"
    ).fetchone()
    return row is not None and int(row[0]) > 0

and gate the call on server_started and _ui_extension_is_loaded(...).

Definition of done

  1. Shutdown does not call stop_ui_server() when the ui extension is not loaded.
  2. A real UI session still stops its server on shutdown.
  3. A test pins that the stubbed-starter path issues no stop_ui_server() call. Recording the
    statements executed on the connection is enough; asserting DuckDB's install behavior is not
    the point.
  4. Existing catalog UI startup, port-conflict, and refresh behavior is unchanged.

Validation

uv sync --locked --all-extras
uv run ruff check --fix
uv run ruff format
uv run ty check
uv run pytest -q

Run plain uv run pytest -q from the repo root.

Notes

Found by @rakesh0x while working on #291. The fix is written and validated already, it just
needs to travel separately from an examples change.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions