Bug 1290598: Manage and terminate Windows subprocess trees as a single job object. r=mhowell
authorKris Maglione <maglione.k@gmail.com>
Wed, 03 Aug 2016 09:39:19 -0700
changeset 308302 89df3d72bcf795906bfeb45a7192019dc6ce7a44
parent 308301 ccea65b170672288036c09e43baa418b8d36015f
child 308303 6da2e5dd4ea28edd4257b315d7d4214788ac5dd7
push id31092
push usercbook@mozilla.com
push dateFri, 05 Aug 2016 10:16:59 +0000
treeherderautoland@b97dd7dd3cb9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmhowell
bugs1290598
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1290598: Manage and terminate Windows subprocess trees as a single job object. r=mhowell MozReview-Commit-ID: 80K5YyDWmn3
toolkit/modules/subprocess/subprocess_shared_win.js
toolkit/modules/subprocess/subprocess_win.jsm
toolkit/modules/subprocess/subprocess_worker_win.js
--- a/toolkit/modules/subprocess/subprocess_shared_win.js
+++ b/toolkit/modules/subprocess/subprocess_shared_win.js
@@ -59,20 +59,22 @@ Object.assign(win32, {
 
 Object.assign(win32, {
   LPCSTR: win32.LPSTR,
   LPCWSTR: win32.LPWSTR,
   LPCVOID: win32.LPVOID,
 });
 
 Object.assign(win32, {
+  CREATE_SUSPENDED: 0x00000004,
   CREATE_NEW_CONSOLE: 0x00000010,
   CREATE_UNICODE_ENVIRONMENT: 0x00000400,
+  CREATE_NO_WINDOW: 0x08000000,
+  CREATE_BREAKAWAY_FROM_JOB: 0x01000000,
   EXTENDED_STARTUPINFO_PRESENT: 0x00080000,
-  CREATE_NO_WINDOW: 0x08000000,
 
   STARTF_USESTDHANDLES: 0x0100,
 
   DUPLICATE_CLOSE_SOURCE: 0x01,
   DUPLICATE_SAME_ACCESS: 0x02,
 
   ERROR_HANDLE_EOF: 38,
   ERROR_BROKEN_PIPE: 109,
@@ -151,16 +153,23 @@ Object.assign(win32, {
   STARTUPINFOEXW: new ctypes.StructType("STARTUPINFOEXW", [
     {"StartupInfo": win32.STARTUPINFOW},
     {"lpAttributeList": win32.LPPROC_THREAD_ATTRIBUTE_LIST},
   ]),
 });
 
 
 var libc = new Library("libc", LIBC_CHOICES, {
+  AssignProcessToJobObject: [
+    win32.WINAPI,
+    win32.BOOL,
+    win32.HANDLE, /* hJob */
+    win32.HANDLE, /* hProcess */
+  ],
+
   CloseHandle: [
     win32.WINAPI,
     win32.BOOL,
     win32.HANDLE, /* hObject */
   ],
 
   CreateEventW: [
     win32.WINAPI,
@@ -178,16 +187,23 @@ var libc = new Library("libc", LIBC_CHOI
     win32.DWORD, /* dwDesiredAccess */
     win32.DWORD, /* dwShareMode */
     win32.SECURITY_ATTRIBUTES.ptr, /* opt lpSecurityAttributes */
     win32.DWORD, /* dwCreationDisposition */
     win32.DWORD, /* dwFlagsAndAttributes */
     win32.HANDLE, /* opt hTemplateFile */
   ],
 
+  CreateJobObjectW: [
+    win32.WINAPI,
+    win32.HANDLE,
+    win32.SECURITY_ATTRIBUTES.ptr, /* opt lpJobAttributes */
+    win32.LPWSTR, /* lpName */
+  ],
+
   CreateNamedPipeW: [
     win32.WINAPI,
     win32.HANDLE,
     win32.LPWSTR, /* lpName */
     win32.DWORD, /* dwOpenMode */
     win32.DWORD, /* dwPipeMode */
     win32.DWORD, /* nMaxInstances */
     win32.DWORD, /* nOutBufferSize */
@@ -312,16 +328,29 @@ var libc = new Library("libc", LIBC_CHOI
   ReleaseSemaphore: [
     win32.WINAPI,
     win32.BOOL,
     win32.HANDLE, /* hSemaphore */
     win32.LONG, /* lReleaseCount */
     win32.LONG.ptr, /* opt out lpPreviousCount */
   ],
 
+  ResumeThread: [
+    win32.WINAPI,
+    win32.DWORD,
+    win32.HANDLE, /* hThread */
+  ],
+
+  TerminateJobObject: [
+    win32.WINAPI,
+    win32.BOOL,
+    win32.HANDLE, /* hJob */
+    win32.UINT, /* uExitCode */
+  ],
+
   TerminateProcess: [
     win32.WINAPI,
     win32.BOOL,
     win32.HANDLE, /* hProcess */
     win32.UINT, /* uExitCode */
   ],
 
   UpdateProcThreadAttribute: [
--- a/toolkit/modules/subprocess/subprocess_win.jsm
+++ b/toolkit/modules/subprocess/subprocess_win.jsm
@@ -10,32 +10,34 @@
 /* exported SubprocessImpl */
 
 /* globals BaseProcess, PromiseWorker */
 
 var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 var EXPORTED_SYMBOLS = ["SubprocessImpl"];
 
+Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/ctypes.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/subprocess/subprocess_common.jsm");
 
 Services.scriptloader.loadSubScript("resource://gre/modules/subprocess/subprocess_shared.js", this);
 Services.scriptloader.loadSubScript("resource://gre/modules/subprocess/subprocess_shared_win.js", this);
 
 class WinPromiseWorker extends PromiseWorker {
   constructor(...args) {
     super(...args);
 
     this.signalEvent = libc.CreateSemaphoreW(null, 0, 32, null);
 
     this.call("init", [{
+      breakAwayFromJob: !AppConstants.isPlatformAndVersionAtLeast("win", "6.2"),
       signalEvent: String(ctypes.cast(this.signalEvent, ctypes.uintptr_t).value),
     }]);
   }
 
   signalWorker() {
     libc.ReleaseSemaphore(this.signalEvent, 1, null);
   }
 
--- a/toolkit/modules/subprocess/subprocess_worker_win.js
+++ b/toolkit/modules/subprocess/subprocess_worker_win.js
@@ -331,17 +331,17 @@ class Process extends BaseProcess {
     return this.handle;
   }
 
   /**
    * Forcibly terminates the process.
    */
   kill() {
     this.killed = true;
-    libc.TerminateProcess(this.handle, TERMINATE_EXIT_CODE);
+    libc.TerminateJobObject(this.jobHandle, TERMINATE_EXIT_CODE);
   }
 
   /**
    * Initializes the IO pipes for use as standard input, output, and error
    * descriptors in the spawned process.
    *
    * @returns {win32.Handle[]}
    *          The array of file handles belonging to the spawned process.
@@ -442,18 +442,23 @@ class Process extends BaseProcess {
 
     args = args.map(arg => this.quoteString(arg));
 
     let envp = this.stringList(options.environment);
 
     let handles = this.initPipes(options);
 
     let processFlags = win32.CREATE_NO_WINDOW
+                     | win32.CREATE_SUSPENDED
                      | win32.CREATE_UNICODE_ENVIRONMENT;
 
+    if (io.breakAwayFromJob) {
+      processFlags |= win32.CREATE_BREAKAWAY_FROM_JOB;
+    }
+
     let startupInfoEx = new win32.STARTUPINFOEXW();
     let startupInfo = startupInfoEx.StartupInfo;
 
     startupInfo.cb = win32.STARTUPINFOW.size;
     startupInfo.dwFlags = win32.STARTF_USESTDHANDLES;
 
     startupInfo.hStdInput = handles[0];
     startupInfo.hStdOutput = handles[1];
@@ -469,16 +474,17 @@ class Process extends BaseProcess {
       processFlags |= win32.EXTENDED_STARTUPINFO_PRESENT;
       startupInfo.cb = win32.STARTUPINFOEXW.size;
 
       startupInfoEx.lpAttributeList = threadAttrs;
     }
 
     let procInfo = new win32.PROCESS_INFORMATION();
 
+    let errorMessage = "Failed to create process";
     let ok = libc.CreateProcessW(
       command, args.join(" "),
       null, /* Security attributes */
       null, /* Thread security attributes */
       true, /* Inherits handles */
       processFlags, envp, options.workdir,
       startupInfo.address(),
       procInfo.address());
@@ -486,27 +492,34 @@ class Process extends BaseProcess {
     for (let handle of new Set(handles)) {
       handle.dispose();
     }
 
     if (threadAttrs) {
       libc.DeleteProcThreadAttributeList(threadAttrs);
     }
 
+    if (ok) {
+      this.jobHandle = win32.Handle(libc.CreateJobObjectW(null, null));
+      ok = libc.AssignProcessToJobObject(this.jobHandle, procInfo.hProcess);
+      errorMessage = `Failed to attach process to job object: 0x${(ctypes.winLastError || 0).toString(16)}`;
+    }
+
     if (!ok) {
       for (let pipe of this.pipes) {
         pipe.close();
       }
-      throw new Error("Failed to create process");
+      throw new Error(errorMessage);
     }
 
-    libc.CloseHandle(procInfo.hThread);
-
     this.handle = win32.Handle(procInfo.hProcess);
     this.pid = procInfo.dwProcessId;
+
+    libc.ResumeThread(procInfo.hThread);
+    libc.CloseHandle(procInfo.hThread);
   }
 
   /**
    * Called when our process handle is signaled as active, meaning the process
    * has exited.
    */
   onReady() {
     this.wait();
@@ -536,16 +549,20 @@ class Process extends BaseProcess {
       }
 
       this.resolveExit(exitCode);
       this.exitCode = exitCode;
 
       this.handle.dispose();
       this.handle = null;
 
+      libc.TerminateJobObject(this.jobHandle, TERMINATE_EXIT_CODE);
+      this.jobHandle.dispose();
+      this.jobHandle = null;
+
       for (let pipe of this.pipes) {
         pipe.maybeClose();
       }
 
       io.updatePollEvents();
 
       return exitCode;
     }
@@ -565,16 +582,18 @@ io = {
   running: true,
 
   init(details) {
     let signalEvent = ctypes.cast(ctypes.uintptr_t(details.signalEvent),
                                   win32.HANDLE);
     this.signal = new Signal(signalEvent);
     this.updatePollEvents();
 
+    this.breakAwayFromJob = details.breakAwayFromJob;
+
     setTimeout(this.loop.bind(this), 0);
   },
 
   shutdown() {
     if (this.running) {
       this.running = false;
 
       this.signal.cleanup();