diff --git a/runtime/process_window_windows.go b/runtime/process_window_windows.go index 2919a7e..2824bc1 100644 --- a/runtime/process_window_windows.go +++ b/runtime/process_window_windows.go @@ -22,17 +22,23 @@ import ( "golang.org/x/sys/windows" ) -const managedProcessHelperFlag = "--run-managed-process-helper" +const ( + managedProcessHelperFlag = "--run-managed-process-helper" + managedProcessDetachedFlags = windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP | windows.CREATE_BREAKAWAY_FROM_JOB +) func configureManagedProcessCommand(cmd *exec.Cmd) { // Run may itself be launched by a service or task scheduler job that // terminates its process tree on shutdown. The helper is the durable // owner of the game process, so ask Windows to keep it outside that job. // The helper itself does not need a console: its stdout/stderr are already - // durable files. CREATE_NEW_CONSOLE creates a second hidden conhost in a - // non-interactive Task Scheduler session and prevents the child pseudo - // console from initializing on Windows Server. - cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: windows.CREATE_NO_WINDOW | windows.CREATE_BREAKAWAY_FROM_JOB, HideWindow: true} + // durable files. Detaching also prevents an operator Ctrl+C used to restart + // Run from propagating into the helper, cmd wrapper, or game process whose + // logs the next Run instance must resume tailing. + // CREATE_NEW_CONSOLE creates a second hidden conhost in a non-interactive + // Task Scheduler session and prevents the child pseudo console from + // initializing on Windows Server. + cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: managedProcessDetachedFlags, HideWindow: true} } // Windows console output is collected by a child helper that owns the @@ -532,7 +538,7 @@ func runManagedShellWithPipes(command ProcessCommand, stopEventName string, stdo } startup := &windows.StartupInfo{Cb: uint32(unsafe.Sizeof(windows.StartupInfo{})), Flags: windows.STARTF_USESTDHANDLES | windows.STARTF_USESHOWWINDOW, ShowWindow: windows.SW_HIDE, StdInput: inputRead, StdOutput: stdoutWrite, StdErr: stderrWrite} var processInfo windows.ProcessInformation - if err := windows.CreateProcess(applicationName, &commandLine[0], nil, nil, true, windows.CREATE_BREAKAWAY_FROM_JOB|windows.CREATE_NO_WINDOW|windows.CREATE_UNICODE_ENVIRONMENT, environment, workDir, startup, &processInfo); err != nil { + if err := windows.CreateProcess(applicationName, &commandLine[0], nil, nil, true, managedProcessDetachedFlags|windows.CREATE_UNICODE_ENVIRONMENT, environment, workDir, startup, &processInfo); err != nil { closeOnError() return 1, fmt.Errorf("start managed shell: %w", err) } diff --git a/runtime/process_window_windows_test.go b/runtime/process_window_windows_test.go index b173f8a..4c37783 100644 --- a/runtime/process_window_windows_test.go +++ b/runtime/process_window_windows_test.go @@ -12,7 +12,7 @@ import ( "golang.org/x/sys/windows" ) -func TestConfigureManagedProcessCommandPreservesInheritedConsole(t *testing.T) { +func TestConfigureManagedProcessCommandDetachesFromRunConsole(t *testing.T) { cmd := exec.Command("cmd.exe") configureManagedProcessCommand(cmd) if cmd.SysProcAttr == nil { @@ -21,9 +21,9 @@ func TestConfigureManagedProcessCommandPreservesInheritedConsole(t *testing.T) { if !cmd.SysProcAttr.HideWindow { t.Fatal("expected managed console window to stay hidden") } - expected := uint32(windows.CREATE_NO_WINDOW | windows.CREATE_BREAKAWAY_FROM_JOB) + expected := uint32(windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP | windows.CREATE_BREAKAWAY_FROM_JOB) if cmd.SysProcAttr.CreationFlags != expected { - t.Fatalf("expected a hidden breakaway helper without a console, got %#x", cmd.SysProcAttr.CreationFlags) + t.Fatalf("expected a detached breakaway helper isolated from Ctrl+C, got %#x", cmd.SysProcAttr.CreationFlags) } }