Skip to content

After Restart from a seat's Apollo web UI, two Apollo processes are left running #124

Closed

Description

@vibesoftwarecoder

What happened

On 2026-10-21 I clicked Restart in a seat's Apollo web UI (https://localhost:48101). Two Apollo processes ended up running for that seat.

Host: MultiSeat 0.6.21+f2ebd45e6. This is a hybrid build: 0.6.21 plus #118, #119 and #121. Seat "Gaming", AudioMode PerSession.

What we measured

  • 07:59:23: the old Apollo (pid 35200) exited after starting a child, pid 32444. The child's parent is 35200. It got no new console and inherits the old conhost.
  • 07:59:26: MultiSeat.Service (pid 22784) launched a second Apollo, pid 49948, with a new conhost.
  • Afterwards both ran. Both listened on 48100, 48101 and 48126. Only 49948 held 48095.
  • After I picked Desktop, the stream was served by 32444. The service was tracking 49948.
  • Later, Quit from the web UI closed 32444 only. 49948 stayed up, and its web UI answered 200.
  • The stream and the game worked throughout.

What the code does

Read from src/MultiSeat.Service/Streaming/ApolloManager.cs and src/MultiSeat.Service/Monitoring/SessionHealthCheck.cs. I read it. I did not run it under a debugger.

  • The service records one PID per seat, plus the process start time as an identity (ApolloManager.StartAsync, RestartAsync).
  • IsAlive checks only that recorded PID. It does not look for other Apollo processes or for children of the recorded one.
  • The health check relaunches when IsAlive is false (SessionHealthCheck.cs, around line 513). RestartAsync then launches Apollo into the seat session again (ApolloManager.cs, around line 290). Nothing there checks for an Apollo that is already running on the seat's ports.
  • So a process that restarts itself by starting a child and exiting looks, to the service, like a crash. I found no code that follows the child.
  • The reconnect path calls KillForReconnect first to avoid a port conflict (SessionHealthCheck.cs, around line 300). The crash-restart path has no such step.
  • I did not verify what happens on the next health-check tick after 49948 was launched, or what Apollo does when it finds its ports taken.

Unknown

  • Whether this predates fix(service): start seat Apollo minimized so it opens no visible console window #121. That change only set wShowWindow to SW_SHOWMINNOACTIVE. The creation flags did not change. CREATE_NEW_CONSOLE is kept so GetConsoleWindow() stays non-NULL. Apollo uses that at confighttp.cpp:1417, system_tray.cpp:102 and platform/windows/misc.cpp:1505 to choose restart and quit behaviour.
  • It has not been tested on 0.6.21.

Possible risk

If the service's instance (49948) dies while the other one is serving, the service would launch again into a port conflict. I have not tried this.

Proposed next step

  1. Reproduce on a clean 0.6.21 install: Restart from the seat's web UI, then list Apollo processes with parent PIDs and the listeners on the seat's ports.
  2. If it reproduces on 0.6.21, it predates fix(service): start seat Apollo minimized so it opens no visible console window #121. If it does not, compare with the 0.6.21+fix(service): start seat Apollo minimized so it opens no visible console window #121 build.
  3. Then decide how the service should handle a self-restarted Apollo, for example by tracking the replacement process or by checking the seat's ports before relaunching.

Activity

  1. vibesoftwarecoder commented on Oct 21, 2026

    @vibesoftwarecoder
    OwnerAuthor

    Result: it reproduces on 0.6.21, so it is older than #121.

    On 2026-10-21 I rolled the host back to the 0.6.21 release (0.6.21+2f7c71b95, the release asset, SHA-256 checked) and repeated the test. Same seat, same host, AudioMode PerSession. I clicked Restart in the seat Apollo's web UI (https://localhost:48101) and listed the Apollo processes.

    • 13:02:36: the old Apollo (pid 15732) exited after starting a child, pid 49384. The child's parent is 15732.
    • 13:02:41: MultiSeat.Service (pid 56556) launched a second Apollo, pid 26736. Its parent is the service.
    • Both were still running 40 seconds later. 49384 listened on 48100 and 48101. 26736 listened on 48101 and 48126.
    • The dashboard showed the seat as Ready throughout.

    This is the same shape as the earlier run on 0.6.21 plus #118, #119 and #121. So #121 did not cause it, and the 0.6.22 release does not change it.

    The reading of the code in the issue still stands. The service tracks one process ID per seat. A self-restarting Apollo looks like a crash to it, and the crash path relaunches without checking for an Apollo that is already running on the seat's ports. I have not changed anything yet.

    Not yet checked: what happens if the service's instance dies while the other one is serving a stream. Also not checked: which instance a client reaches when both are up.

    Next step: decide how the service should handle a self-restarted Apollo. Two options are in the issue: follow the replacement process, or check the seat's ports before relaunching. I will write up both with the cost of each before changing code.

  2. added 2 commits that reference this issue on Oct 21, 2026
    4657cac
    c704e3e
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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions