Skip to content

Capture and surface game server exit codes in LocalMultiplayerAgent#209

Draft
Copilot wants to merge 3 commits intomainfrom
copilot/add-exit-code-output
Draft

Capture and surface game server exit codes in LocalMultiplayerAgent#209
Copilot wants to merge 3 commits intomainfrom
copilot/add-exit-code-output

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 8, 2026

Game server exit codes were silently discarded. For containers, the code logged the exit code but never returned it. For processes, exit codes were never captured at all. This made it impossible to diagnose crashes or non-zero exits from the game server.

Interface changes

  • IProcessWrapper.WaitForProcessExit: voidint
  • ISessionHostRunner.WaitOnServerExit: TaskTask<int>
  • BaseSessionHostRunner abstract signature updated accordingly

ProcessWrapper: retain Process references to avoid GetProcessById pitfalls

Process.GetProcessById(pid) throws ArgumentException if the process has already exited, and is subject to PID reuse races. Both make exit code retrieval unreliable for short-lived or crashing servers.

ProcessWrapper now tracks started Process objects in a ConcurrentDictionary and retrieves them by ID in WaitForProcessExit, falling back to GetProcessById only for externally-started processes.

if (!_trackedProcesses.TryRemove(id, out Process process))
{
    process = Process.GetProcessById(id);
}
process.WaitForExit();
return process.ExitCode;

Runner implementations

  • ProcessRunner — returns and logs exit code from ProcessWrapper
  • DockerContainerEngine — returns ContainerWaitResponse.StatusCode (was already captured, just discarded)

MultiplayerServerManager

Captures the exit code and writes it to both logger and console:

Game server exited with exit code 1.

- IProcessWrapper.WaitForProcessExit now returns int (exit code)
- ProcessWrapper tracks started processes in ConcurrentDictionary for
  reliable exit code capture even when process crashes early
- ISessionHostRunner.WaitOnServerExit now returns Task<int>
- ProcessRunner and DockerContainerEngine return exit codes
- MultiplayerServerManager logs and prints the exit code to console

Agent-Logs-Url: https://github.com/PlayFab/MpsAgent/sessions/99f02fa0-80b5-470b-afbc-8b85cf45d9fa

Co-authored-by: dgkanatsios <8256138+dgkanatsios@users.noreply.github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the LocalMultiplayerAgent/VmAgent runner abstractions to capture and propagate game server exit codes (both process and container based), so crashes/non-zero exits are surfaced to logs/console instead of being silently discarded.

Changes:

  • Updated runner/process-wrapper interfaces to return exit codes (WaitForProcessExitint, WaitOnServerExitTask<int>).
  • Added process tracking in ProcessWrapper to reliably retrieve exit codes without GetProcessById races.
  • Plumbed exit codes through ProcessRunner and DockerContainerEngine to MultiplayerServerManager for logging/console output.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
VmAgent.Core/Interfaces/ProcessWrapper.cs Track started Process instances and return ExitCode from WaitForProcessExit.
VmAgent.Core/Interfaces/IProcessWrapper.cs Interface updated to return an exit code from WaitForProcessExit.
VmAgent.Core/Interfaces/ProcessRunner.cs Return/log process exit code from WaitOnServerExit.
VmAgent.Core/Interfaces/DockerContainerEngine.cs Return/log container exit code from WaitOnServerExit (previously logged only).
VmAgent.Core/Interfaces/ISessionHostRunner.cs Interface updated: WaitOnServerExit now returns Task<int>.
VmAgent.Core/Interfaces/BaseSessionHostRunner.cs Abstract signature updated to Task<int> WaitOnServerExit(...).
LocalMultiplayerAgent/MultiplayerServerManager.cs Capture returned exit code and write it to logger + console.
Comments suppressed due to low confidence (1)

VmAgent.Core/Interfaces/ProcessWrapper.cs:22

  • Now that ProcessWrapper retains Process objects in _trackedProcesses, Kill(int id) should prefer the tracked Process reference (and remove/dispose it) instead of calling Process.GetProcessById. Using GetProcessById after the original process has exited can hit PID reuse and potentially kill an unrelated process; tracking was introduced specifically to avoid this class of race.
        private readonly ConcurrentDictionary<int, Process> _trackedProcesses = new ConcurrentDictionary<int, Process>();

        public void Kill(int id)
        {
            try
            {
                Process process = Process.GetProcessById(id);
                process.Kill(true);
            }

Comment thread VmAgent.Core/Interfaces/ProcessWrapper.cs Outdated
Comment thread VmAgent.Core/Interfaces/DockerContainerEngine.cs
Comment thread VmAgent.Core.UnitTests/ProcessWrapperTests.cs Fixed
Comment thread VmAgent.Core/Interfaces/ProcessWrapper.cs Fixed
Comment thread VmAgent.Core/Interfaces/ProcessWrapper.cs Fixed
Comment thread VmAgent.Core.UnitTests/ProcessWrapperTests.cs Fixed
Comment thread VmAgent.Core.UnitTests/ProcessWrapperTests.cs Fixed
Comment thread VmAgent.Core/Interfaces/ProcessWrapper.cs Fixed
Comment thread VmAgent.Core/Interfaces/ProcessWrapper.cs Fixed
Comment thread VmAgent.Core.UnitTests/ProcessWrapperTests.cs Fixed
Comment thread VmAgent.Core/Interfaces/ProcessWrapper.cs Fixed
@dgkanatsios dgkanatsios force-pushed the copilot/add-exit-code-output branch from 7eba5f1 to 02576fe Compare April 14, 2026 05:37
- ProcessWrapper.Kill() now uses tracked Process reference via TryRemove
  (prevents dictionary leak in production VmAgent path where
  WaitForProcessExit is never called)
- Both methods now Dispose Process to release native OS handles
- WaitForProcessExit() cancels async output/error reads before Dispose
- WaitForProcessExit() throws if process not tracked (fail fast)
- Start() adds null-check for Process.Start() return value
- ProcessWrapper implements IDisposable for shutdown cleanup
- Add ProcessWrapperTests covering cleanup, exit code, and Dispose
- Add DockerContainerEngine.WaitOnServerExit test for exit code

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dgkanatsios dgkanatsios force-pushed the copilot/add-exit-code-output branch from 02576fe to e610aa6 Compare April 14, 2026 05:37
Comment thread VmAgent.Core/Interfaces/ProcessWrapper.cs Fixed
Comment thread VmAgent.Core/Interfaces/ProcessWrapper.cs Fixed
Comment thread VmAgent.Core/Interfaces/ProcessWrapper.cs Fixed
Comment thread VmAgent.Core/Interfaces/ProcessWrapper.cs
- Kill(): use 'using' statement instead of try/finally+Dispose
- WaitForProcessExit(): use 'using' statement, add Debug.WriteLine
  to empty catch blocks for CancelOutputRead/CancelErrorRead
- Tests: use specific catch types instead of generic catch clause
- Tests: refactor helper methods to expression-bodied ternaries
- Tests: assert InvalidOperationException (not mixed exception types)
  now that WaitForProcessExit throws for untracked processes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dgkanatsios dgkanatsios force-pushed the copilot/add-exit-code-output branch from 1826ab3 to e5f3660 Compare April 14, 2026 06:07
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.

3 participants