Windows: Stout: Rewrote job object wrappers.

`os::create_job` now returns a `Try<SharedHandle>` instead of a raw
`HANDLE`, forcing ownership of the job object handle onto the caller
of the function. `create_job` requires a `std::string name` for the
job object, which is mapped from a PID using `os::name_job`.

The assignment of a process to the job object is now done via
`Try<Nothing> os::assign_job(SharedHandle, pid_t)`.

The equivalent of killing a process tree with job object semantics
is simply to terminate the job object. This is done via
`os::kill_job(SharedHandle)`.

Review: https://reviews.apache.org/r/56364/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/db0f5697
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/db0f5697
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/db0f5697

Branch: refs/heads/master
Commit: db0f5697ed61ba44f0f50fedd55848eaaf50348a
Parents: 476c2f2
Author: Andrew Schwartzmeyer <[email protected]>
Authored: Mon Apr 3 15:07:10 2017 -0700
Committer: Joseph Wu <[email protected]>
Committed: Tue Apr 4 16:45:15 2017 -0700

----------------------------------------------------------------------
 3rdparty/stout/include/stout/windows/os.hpp | 149 +++++++++++++++--------
 1 file changed, 101 insertions(+), 48 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/db0f5697/3rdparty/stout/include/stout/windows/os.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/stout/include/stout/windows/os.hpp 
b/3rdparty/stout/include/stout/windows/os.hpp
index 0bedb2d..f1722d5 100644
--- a/3rdparty/stout/include/stout/windows/os.hpp
+++ b/3rdparty/stout/include/stout/windows/os.hpp
@@ -24,6 +24,7 @@
 #include <codecvt>
 #include <list>
 #include <map>
+#include <memory>
 #include <set>
 #include <string>
 
@@ -34,6 +35,7 @@
 #include <stout/path.hpp>
 #include <stout/strings.hpp>
 #include <stout/try.hpp>
+#include <stout/version.hpp>
 #include <stout/windows.hpp>
 
 #include <stout/os/os.hpp>
@@ -653,84 +655,135 @@ inline int random()
 }
 
 
-// `create_job` function creates a job object whose name is derived
-// from the `pid` and associates the process with the job object.
-// Every process started by the `pid` process which is part of the job
-// object becomes part of the job object. The job name should match
-// the name used in `kill_job`.
-inline Try<HANDLE> create_job(pid_t pid)
-{
+// `name_job` maps a `pid` to a `string` name for a job object.
+// Only named job objects are accessible via `OpenJobObject`.
+// Thus all our job objects must be named. This is essentially a shim
+// to map the Linux concept of a process tree's root `pid` to a
+// named job object so that the process group can be treated similarly.
+inline Try<std::string> name_job(pid_t pid) {
   Try<std::string> alpha_pid = strings::internal::format("MESOS_JOB_%X", pid);
   if (alpha_pid.isError()) {
     return Error(alpha_pid.error());
   }
+  return alpha_pid;
+}
 
-  HANDLE process_handle = ::OpenProcess(
-      PROCESS_SET_QUOTA | PROCESS_TERMINATE,
-      false,
-      pid);
 
-  if (process_handle == INVALID_HANDLE_VALUE) {
-    return WindowsError("os::create_job: Call to `OpenProcess` failed");
+// `open_job` returns a safe shared handle to the named job object `name`.
+// `desired_access` is a job object access rights flag.
+// `inherit_handles` if true, processes created by this
+// process will inherit the handle. Otherwise, the processes
+// do not inherit this handle.
+inline Try<SharedHandle> open_job(
+    const DWORD desired_access,
+    BOOL inherit_handles,
+    const std::string& name)
+{
+  SharedHandle jobHandle(
+      ::OpenJobObject(
+          desired_access,
+          inherit_handles,
+          name.c_str()),
+      ::CloseHandle);
+
+  if (jobHandle.get() == nullptr) {
+    return WindowsError(
+        "os::open_job: Call to `OpenJobObject` failed for job: " + name);
   }
 
-  SharedHandle safe_process_handle(process_handle, ::CloseHandle);
+  return jobHandle;
+}
 
-  HANDLE job_handle = ::CreateJobObject(nullptr, alpha_pid.get().c_str());
 
-  if (job_handle == nullptr) {
-    return WindowsError("os::create_job: Call to `CreateJobObject` failed");
+// `create_job` function creates a named job object using `name`.
+// This returns the safe job handle, which closes the job handle
+// when destructed. Because the job is destroyed when its last
+// handle is closed and all associated processes have exited,
+// a running process must be assigned to the created job
+// before the returned handle is closed.
+inline Try<SharedHandle> create_job(const std::string& name)
+{
+  SharedHandle jobHandle(
+      ::CreateJobObject(
+          nullptr,       // Use a default security descriptor, and
+                         // the created handle cannot be inherited.
+          name.c_str()), // The name of the job.
+      ::CloseHandle);
+  // TODO(andschwa): Fix the type of `name` when Unicode is turned on.
+
+  if (jobHandle.get_handle() == nullptr) {
+    return WindowsError(
+        "os::create_job: Call to `CreateJobObject` failed for job: " + name);
   }
 
   JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { { 0 }, 0 };
 
-  jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
-
   // The job object will be terminated when the job handle closes. This allows
   // the job tree to be terminated in case of errors by closing the handle.
-  ::SetInformationJobObject(
-      job_handle,
+  // We set this flag so that the death of the agent process will
+  // always kill any running jobs, as the OS will close the remaining open
+  // handles if all destructors failed to run (catastrophic death).
+  jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+
+  const BOOL setInformationResult = ::SetInformationJobObject(
+      jobHandle.get_handle(),
       JobObjectExtendedLimitInformation,
       &jeli,
       sizeof(jeli));
 
-  if (::AssignProcessToJobObject(
-          job_handle,
-          safe_process_handle.get_handle()) == 0) {
+  if (setInformationResult == FALSE) {
     return WindowsError(
-        "os::create_job: Call to `AssignProcessToJobObject` failed");
-  };
+        "os::create_job: `SetInformationJobObject` failed for job: " + name);
+  }
 
-  return job_handle;
+  return jobHandle;
 }
 
 
-// `kill_job` function assumes the process identified by `pid`
-// is associated with a job object whose name is derived from it.
-// Every process started by the `pid` process which is part of the job
-// object becomes part of the job object. Destroying the task
-// will close all such processes.
-inline Try<Nothing> kill_job(pid_t pid)
-{
-  Try<std::string> alpha_pid = strings::internal::format("MESOS_JOB_%X", pid);
-  if (alpha_pid.isError()) {
-    return Error(alpha_pid.error());
+// `assign_job` assigns a process with `pid` to the job object `jobHandle`.
+// Every process started by the `pid` process using `CreateProcess`
+// will also be owned by the job object.
+inline Try<Nothing> assign_job(SharedHandle jobHandle, pid_t pid) {
+  // Get process handle for `pid`.
+  SharedHandle processHandle(
+      ::OpenProcess(
+          // Required access rights to assign to a Job Object.
+          PROCESS_SET_QUOTA | PROCESS_TERMINATE,
+          false, // Don't inherit handle.
+          pid),
+      ::CloseHandle);
+
+  if (processHandle.get_handle() == nullptr) {
+    return WindowsError(
+        "os::assign_job: Call to `OpenProcess` failed");
   }
 
-  HANDLE job_handle = ::OpenJobObject(
-      JOB_OBJECT_TERMINATE,
-      FALSE,
-      alpha_pid.get().c_str());
+  const BOOL assignResult = ::AssignProcessToJobObject(
+      jobHandle.get_handle(),
+      processHandle.get_handle());
+
+  if (assignResult == FALSE) {
+    return WindowsError(
+        "os::assign_job: Call to `AssignProcessToJobObject` failed");
+  };
+
+  return Nothing();
+}
 
-  if (job_handle == nullptr) {
-    return WindowsError("os::kill_job: Call to `OpenJobObject` failed");
-  }
 
-  SharedHandle safe_job_handle(job_handle, ::CloseHandle);
+// The `kill_job` function wraps the Windows sytem call `TerminateJobObject`
+// for the job object `jobHandle`. This will call `TerminateProcess`
+// for every associated child process.
+inline Try<Nothing> kill_job(SharedHandle jobHandle)
+{
+  const BOOL terminateResult = ::TerminateJobObject(
+      jobHandle.get_handle(),
+      // The exit code to be used by all processes in the job object.
+      1);
 
-  BOOL result = ::TerminateJobObject(safe_job_handle.get_handle(), 1);
-  if (result == 0) {
-    return WindowsError();
+  if (terminateResult == FALSE) {
+    return WindowsError(
+        "os::kill_job: Call to `TerminateJobObject` failed");
   }
 
   return Nothing();

Reply via email to