Windowsでプロセスグループを指定して spawn する方法

Linux で プロセスグループを指定して spawn を実行する場合は pgroup を指定しますが、Windowsではこのオプションはサポートされていません。Windowsでプロセスグループを作成するには new_pgroup を指定します。ただし、spawnの記述を間違うとshell経由でプロセスが起動してしまうため、このオプションが機能しなくなるため注意が必要です。

Ruby プログラム(間違い)

spawnの第一引数にコマンドライン引数も含めて渡してしまうとshell経由での起動となるため、Ctrl-Cがshellに伝わってしまってshellごと子プロセスが終了する。

a.rb

spawn "ruby b.rb", new_pgroup: true

loop do
  print "test\n"
  sleep(1)
rescue Interrupt
  exit(false)
end

b.rb

loop do
  print "#{Time.now}\n"
  sleep(1)
end

Ruby プログラム(shellを経由しない場合)

spawnにコマンドと引数を分けて渡してやると、shellを経由しなくなる。複数の引数を渡したい場合は、それぞれを分けて渡すこと。

a.rb

spawn "ruby", "b.rb", new_pgroup: true

loop do
  print "test\n"
  sleep(1)
rescue Interrupt
  exit(false)
end

調査用 Go プログラム (参考)

// +build ignore

package main

import (
    "fmt"
    "os/exec"
    "syscall"
    "time"
)

const (
    CREATE_NEW_PROCESS_GROUP = 0x00000200
)

func main() {
    var cmd *exec.Cmd
    cmd = exec.Command("ruby", "-e", "'sleep'")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        CreationFlags: CREATE_NEW_PROCESS_GROUP,
    }
    cmd.Start()
    time.Sleep(1 * time.Second)
    fmt.Println(cmd.Process.Pid)

    for {
        time.Sleep(1 * time.Second)
    }
}

調査用 C言語 プログラム (参考)

#include <stdio.h>
#include <Windows.h>
#include <string.h>
#include <wchar.h>

void main()
{
    BOOL fRet;
    STARTUPINFO aStartupInfo;
    PROCESS_INFORMATION aProcessInformation;
    SECURITY_ATTRIBUTES sa;
    DWORD dwCreationFlags;
    const WCHAR* prog = NULL;
    WCHAR cmd[] = L"ruby C:\\dev\\ruby\\test_spawn\\b.rb";

    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;

    memset(&aStartupInfo, 0, sizeof(aStartupInfo));
    memset(&aProcessInformation, 0, sizeof(aProcessInformation));
    aStartupInfo.cb = sizeof(aStartupInfo);
    aStartupInfo.dwFlags = STARTF_USESTDHANDLES;
    aStartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
    aStartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    aStartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);

    dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_PROCESS_GROUP;

    printf("start\n");

    fRet = CreateProcess(prog, cmd, &sa, &sa, sa.bInheritHandle, dwCreationFlags, NULL, NULL, &aStartupInfo, &aProcessInformation);

    printf("fRet: %d\n", fRet);

    CloseHandle(aProcessInformation.hThread);

    printf("PID: %d\n", aProcessInformation.dwProcessId);

    WaitForSingleObject(aProcessInformation.hProcess, INFINITE);

    printf("end  \n");
}

参考資料

github.com