Skip to content

fix(dash): refuse a busy or out-of-range --port before printing the URL - #736

Open
dchaudhari7177 wants to merge 2 commits into
DobermanCore:mainfrom
dchaudhari7177:fix/714-dash-port-check
Open

dchaudhari7177 wants to merge 2 commits into
DobermanCore:mainfrom
dchaudhari7177:fix/714-dash-port-check

Conversation

@dchaudhari7177

Copy link
Copy Markdown
Contributor

Closes #714 (and its PostHog twin #727).

What changes

doberman dash used to print Dashboard: http://127.0.0.1:<port>/?token=… before uvicorn tried to bind. A busy port gave a dead link followed by uvicorn's [Errno 10048], and --port 70000 a traceback ending in OverflowError.

  1. --port has min=1, max=65535, so an out-of-range port is a Typer usage error (exit 2). That also covers 0 and negatives.
  2. A new _dash_port_is_free(port) does a throwaway bind on (_DASH_HOST, port) and closes it. If the bind fails, dash prints error: port N is already in use, try --port <another> to stderr and exits 1. It doesn't print the URL or call uvicorn.run.
    • The check runs before the heartbeat thread starts, not only before the URL. Otherwise a failed start would touch the heartbeat file and, for about two seconds, route AUTH challenges to a dashboard that never came up.
    • Off Windows the probe sets SO_REUSEADDR, as uvicorn does, so a port only lingering in TIME_WAIT is not reported busy. On Windows the option is left off, because there it would let the probe bind a port that is actually in use.
  3. docs/CLI.md's per-command exit-code table gets a dash 2 row (port out of range), and the existing dash 1 row now also covers a busy port.

Tests (tests/unit/test_dash_serve.py)

  • test_dash_refuses_a_busy_port_before_printing_the_url holds a listening socket on a port and runs dash --port <that port>. It asserts exit 1, the error line, no Dashboard: line, uvicorn.run never called (monkeypatched), and the heartbeat never touched.
  • test_dash_out_of_range_port_is_a_usage_error, parametrized over 0, 70000 and -1: exit 2, no URL, no uvicorn.run.
  • test_dash_disables_uvicorn_access_log used --port 0, which the new range check refuses. It now picks a free port.

On main these give 4 failed, 19 passed. On this branch the file passes 23/23, and pytest tests/unit -k "cli_help or docs or dash or exit_code" gives 342 passed, 1 skipped. ruff check and format are clean. Run on Windows / Python 3.12.

One thing I noticed but didn't change: the "Collision audit" paragraph in docs/CLI.md says grep -c "typer.Exit(code=" src/doberman/cli/main.py returns 67, but main already returns 88 (66 × code=1, 20 × code=2). This PR adds one more code=1. Happy to refresh that paragraph here or in a separate docs PR.

AI assistance: I used an AI assistant while writing this change and the tests. I reviewed the diff and ran the tests above myself.

dash printed the dashboard link before uvicorn tried to bind, and --port
had no range check, so a busy port gave a dead link and a raw bind error,
and --port 70000 a traceback. Bound --port to 1..65535 (exit 2), and probe
the port with a throwaway bind before printing the URL or starting the
heartbeat, exiting 1 with one error line when it is taken.

Closes DobermanCore#714

This branch has not been deployed

No deployments
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.

dash: a busy or out-of-range --port prints a dashboard link that never comes up

1 participant