What happens
Launching a second StemDeck while one is already running does not start a second backend. The new window ends up driving the already-running instance's backend, including its data directory and library.
Found while testing a portable install in an isolated folder: its window showed the library and jobs of a completely different install on another drive.
Why
reserve_port (desktop/src-tauri/src/main.rs) tries the configured port (default 8000):
if let Ok(listener) = TcpListener::bind(("127.0.0.1", desired)) { ... }
On Windows, binding 127.0.0.1:8000 succeeds even while another process holds 0.0.0.0:8000, because neither socket sets SO_EXCLUSIVEADDRUSE. So the port looks free.
The spawned uvicorn then binds 0.0.0.0:<port> and dies:
ERROR: [Errno 10048] error while attempting to bind on address ('0.0.0.0', 8000):
only one usage of each socket address (protocol/network address/port) is normally permitted
wait_for_health(port, ...) then polls http://127.0.0.1:8000/api/health, gets a 200 from the first instance, and reports success. The shell navigates the webview there and everything looks fine.
The mismatch is that the reservation probes 127.0.0.1 while the child binds 0.0.0.0, and the health check never verifies that the process answering is the child we just spawned.
Reproduce
- Start StemDeck (binds
0.0.0.0:8000).
- Extract a separate portable build elsewhere and start it.
- The second window shows the first install's library.
data/logs/backend.log in the second install contains the 10048 error; no backend process exists for it.
Impact
- Two installs share one backend and one data dir, with no indication anything is wrong.
- The second instance's own backend is dead, so its settings, jobs folder, and device selection are ignored.
- Closing the first window kills the backend the second is still using.
- Anything the second window does writes into the first install's data.
Suggested fix
Any one of these closes it; the third is the real guard:
- Bind the reservation probe to
0.0.0.0, matching what uvicorn actually binds, so a conflict is detected.
- Set
SO_EXCLUSIVEADDRUSE on the probe socket.
- Verify the health responder is ours, e.g. have the backend report its PID on
/api/health (or a nonce passed in via env) and have wait_for_health require a match before proceeding. Without this, any process answering on that port is trusted.
Also worth surfacing the child's exit: start_backend currently reports success while the process it spawned has already died.
Environment
Windows 11, StemDeck portable (Windows x64 CPU build). Unrelated to #421; found while testing that work.
What happens
Launching a second StemDeck while one is already running does not start a second backend. The new window ends up driving the already-running instance's backend, including its data directory and library.
Found while testing a portable install in an isolated folder: its window showed the library and jobs of a completely different install on another drive.
Why
reserve_port(desktop/src-tauri/src/main.rs) tries the configured port (default 8000):On Windows, binding
127.0.0.1:8000succeeds even while another process holds0.0.0.0:8000, because neither socket setsSO_EXCLUSIVEADDRUSE. So the port looks free.The spawned uvicorn then binds
0.0.0.0:<port>and dies:wait_for_health(port, ...)then pollshttp://127.0.0.1:8000/api/health, gets a200from the first instance, and reports success. The shell navigates the webview there and everything looks fine.The mismatch is that the reservation probes
127.0.0.1while the child binds0.0.0.0, and the health check never verifies that the process answering is the child we just spawned.Reproduce
0.0.0.0:8000).data/logs/backend.login the second install contains the10048error; no backend process exists for it.Impact
Suggested fix
Any one of these closes it; the third is the real guard:
0.0.0.0, matching what uvicorn actually binds, so a conflict is detected.SO_EXCLUSIVEADDRUSEon the probe socket./api/health(or a nonce passed in via env) and havewait_for_healthrequire a match before proceeding. Without this, any process answering on that port is trusted.Also worth surfacing the child's exit:
start_backendcurrently reports success while the process it spawned has already died.Environment
Windows 11, StemDeck portable (Windows x64 CPU build). Unrelated to #421; found while testing that work.