You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by jo...@apache.org on 2017/04/04 23:53:33 UTC

[05/13] mesos git commit: Windows: Added `JobObjectManager` global actor.

Windows: Added `JobObjectManager` global actor.

This commit adds a Windows-specific actor for managing job objects.

A subprocess launched with the `ParentHook::CREATE_JOB()` is created
within the context of a named Windows job object. The `JobObjectManager`
takes ownership of the handle to the job object.

It is necessary to tie the lifetime of the job object to the actor by
ownership of the open handle so that the job object can be queried for
usage information even after the processes that were running within the
job object have ended. These semantics were not changed; previously the
same was achieved by leaking the handle and tying it to the lifetime of
the actual Mesos agent process, and implicitly depending on the
operating system to close the open handle at the death of the process.

We ensure the proper death of the job object process group by defering a
call to `cleanup()` to the process reaper for the given PID. This
function uses the Windows system call to terminate the job object via
`os::kill_job()`.

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


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

Branch: refs/heads/master
Commit: c94041b7cb643d701dfcf6d67c492b53f02f75bd
Parents: 176c09d
Author: Andrew Schwartzmeyer <an...@schwartzmeyer.com>
Authored: Mon Apr 3 12:15:51 2017 -0700
Committer: Joseph Wu <jo...@apache.org>
Committed: Tue Apr 4 16:45:16 2017 -0700

----------------------------------------------------------------------
 .../include/process/windows/jobobject.hpp       | 129 +++++++++++++++++++
 3rdparty/libprocess/src/process.cpp             |  17 +++
 3rdparty/libprocess/src/subprocess.cpp          |  19 ---
 3 files changed, 146 insertions(+), 19 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/c94041b7/3rdparty/libprocess/include/process/windows/jobobject.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/include/process/windows/jobobject.hpp b/3rdparty/libprocess/include/process/windows/jobobject.hpp
new file mode 100644
index 0000000..5fb41c4
--- /dev/null
+++ b/3rdparty/libprocess/include/process/windows/jobobject.hpp
@@ -0,0 +1,129 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License
+
+#ifndef __PROCESS_WINDOWS_JOBOBJECT_HPP__
+#define __PROCESS_WINDOWS_JOBOBJECT_HPP__
+
+#include <map>
+#include <string>
+
+#include <stout/lambda.hpp>
+#include <stout/nothing.hpp>
+#include <stout/windows.hpp> // For `SharedHandle`.
+#include <stout/windows/os.hpp> // For `os` namespace.
+
+#include <process/defer.hpp>
+#include <process/future.hpp>
+#include <process/reap.hpp>
+#include <process/subprocess.hpp>
+#include <process/process.hpp>
+
+#include <glog/logging.h> // For `CHECK` macro.
+
+
+namespace process {
+namespace internal {
+
+class JobObjectManager : public Process<JobObjectManager>
+{
+public:
+  JobObjectManager() : ProcessBase("__job_object_manager") {}
+  virtual ~JobObjectManager() {}
+
+  void manage(
+      const pid_t pid,
+      const std::string& name,
+      const SharedHandle& handle)
+  {
+    jobs.emplace(pid, JobData{name, handle});
+
+    process::reap(pid)
+      .onAny(defer(self(), &Self::cleanup, lambda::_1, pid));
+  }
+
+protected:
+  void cleanup(Future<Option<int>> exit_code, const pid_t pid)
+  {
+    CHECK(!exit_code.isPending());
+    CHECK(!exit_code.isDiscarded());
+
+    Try<Nothing> killJobResult = os::kill_job(jobs.at(pid).handle);
+    CHECK(!killJobResult.isError())
+      << "Failed to kill job object: " << killJobResult.error();
+
+    // Finally, erase the `JobData`, closing the last handle to the job object.
+    // All functionality requiring a live job object handle (but possibly a
+    // dead process) must happen prior to this, e.g. in a another parent hook.
+    jobs.erase(pid);
+  }
+
+private:
+  struct JobData {
+    std::string name;
+    SharedHandle handle;
+  };
+
+  std::map<pid_t, JobData> jobs;
+};
+
+// Global job object manager process. Defined in `process.cpp`.
+extern PID<JobObjectManager> job_object_manager;
+
+} // namespace internal {
+
+inline Subprocess::ParentHook Subprocess::ParentHook::CREATE_JOB() {
+  return Subprocess::ParentHook([](pid_t pid) -> Try<Nothing> {
+    // NOTE: There are two very important parts to this hook. First, Windows
+    // does not have a process hierarchy in the same sense that Unix does, so
+    // in order to be able to kill a task, we have to put it in a job object.
+    // Then, when we terminate the job object, it will terminate all the
+    // processes in the task (including any processes that were subsequently
+    // created by any process in this task). Second, the lifetime of the job
+    // object is greater than the lifetime of the processes it contains. Thus
+    // the job object handle is explicitly owned by the global job object
+    // manager process.
+    Try<std::string> name = os::name_job(pid);
+    if (name.isError()) {
+      return Error(name.error());
+    }
+
+    // This creates a named job object in the Windows kernel.
+    // This handle must remain in scope (and open) until
+    // a running process is assigned to it.
+    Try<SharedHandle> handle = os::create_job(name.get());
+    if (handle.isError()) {
+      return Error(handle.error());
+    }
+
+    // This actually assigns the process `pid` to the job object.
+    Try<Nothing> result = os::assign_job(handle.get(), pid);
+    if (result.isError()) {
+      return Error(result.error());
+    }
+
+    // Save the handle to the job object to ensure the object remains
+    // open for the entire lifetime of the agent process, and is closed
+    // when the process is reaped.
+    dispatch(
+      process::internal::job_object_manager,
+      &process::internal::JobObjectManager::manage,
+      pid,
+      name.get(),
+      handle.get());
+
+    return Nothing();
+  });
+}
+
+} // namespace process {
+
+#endif // __PROCESS_WINDOWS_JOBOBJECT_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/c94041b7/3rdparty/libprocess/src/process.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/src/process.cpp b/3rdparty/libprocess/src/process.cpp
index f6ee24e..d0cba0c 100644
--- a/3rdparty/libprocess/src/process.cpp
+++ b/3rdparty/libprocess/src/process.cpp
@@ -90,6 +90,10 @@
 
 #include <process/ssl/flags.hpp>
 
+#ifdef __WINDOWS__
+#include <process/windows/jobobject.hpp>
+#endif // __WINDOWS__
+
 #include <stout/duration.hpp>
 #include <stout/flags.hpp>
 #include <stout/foreach.hpp>
@@ -629,8 +633,14 @@ PID<metrics::internal::MetricsProcess> metrics;
 
 namespace internal {
 
+// Global reaper.
 PID<process::internal::ReaperProcess> reaper;
 
+// Global job object manager.
+#ifdef __WINDOWS__
+PID<process::internal::JobObjectManager> job_object_manager;
+#endif // __WINDOWS__
+
 } // namespace internal {
 
 
@@ -1199,6 +1209,7 @@ bool initialize(
   //   |--help
   //   |  |--metrics
   //   |  |  |--system
+  //   |  |  |--job_object_manager (Windows only)
   //   |  |  |--All other processes
   //   |  |
   //   |  |--logging
@@ -1235,6 +1246,12 @@ bool initialize(
   process::internal::reaper =
     spawn(new process::internal::ReaperProcess(), true);
 
+  // Create the global job object manager process.
+#ifdef __WINDOWS__
+  process::internal::job_object_manager =
+    spawn(new process::internal::JobObjectManager(), true);
+#endif // __WINDOWS__
+
   // Initialize the mime types.
   mime::initialize();
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/c94041b7/3rdparty/libprocess/src/subprocess.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/src/subprocess.cpp b/3rdparty/libprocess/src/subprocess.cpp
index 6dfb939..5354274 100644
--- a/3rdparty/libprocess/src/subprocess.cpp
+++ b/3rdparty/libprocess/src/subprocess.cpp
@@ -54,25 +54,6 @@ Subprocess::ParentHook::ParentHook(
   : parent_setup(_parent_setup) {}
 
 
-#ifdef __WINDOWS__
-Subprocess::ParentHook Subprocess::ParentHook::CREATE_JOB()
-{
-  return Subprocess::ParentHook([](pid_t pid) -> Try<Nothing> {
-    // NOTE: The Job Object's handle is not closed here. Although it
-    // looks like we are leaking the handle, we can still retrieve and
-    // close the handle via the `OpenJobObject` Windows API.
-    Try<HANDLE> job = os::create_job(pid);
-
-    if (job.isError()) {
-      return Error(job.error());
-    }
-
-    return Nothing();
-  });
-}
-#endif // __WINDOWS__
-
-
 Subprocess::ChildHook::ChildHook(
     const lambda::function<Try<Nothing>()>& _child_setup)
   : child_setup(_child_setup) {}