summaryrefslogtreecommitdiffstats
path: root/chrome/common/process_watcher_mac.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/common/process_watcher_mac.cc')
-rw-r--r--chrome/common/process_watcher_mac.cc118
1 files changed, 118 insertions, 0 deletions
diff --git a/chrome/common/process_watcher_mac.cc b/chrome/common/process_watcher_mac.cc
new file mode 100644
index 0000000..2d5d185
--- /dev/null
+++ b/chrome/common/process_watcher_mac.cc
@@ -0,0 +1,118 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/process_watcher.h"
+
+#include <errno.h>
+#include <signal.h>
+#include <sys/event.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "base/eintr_wrapper.h"
+#include "base/file_util.h"
+#include "base/time.h"
+
+namespace {
+
+// Reap |child| process.
+// This call blocks until completion.
+void BlockingReap(pid_t child) {
+ const pid_t result = HANDLE_EINTR(waitpid(child, NULL, 0));
+ if (result == -1) {
+ PLOG(ERROR) << "waitpid(" << child << ")";
+ NOTREACHED();
+ }
+}
+
+// Waits for |timeout| seconds for the given |child| to exit and reap it.
+// If the child doesn't exit within a couple of seconds, kill it.
+void WaitForChildToDie(pid_t child, unsigned timeout) {
+ int kq = HANDLE_EINTR(kqueue());
+ file_util::ScopedFD auto_close(&kq);
+ if (kq == -1) {
+ PLOG(ERROR) << "Failed to create kqueue";
+ return;
+ }
+
+ struct kevent event_to_add = {0};
+ EV_SET(&event_to_add, child, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
+ // Register interest with kqueue.
+ int result = HANDLE_EINTR(kevent(kq, &event_to_add, 1, NULL, 0, NULL));
+ if (result == -1 && errno == ESRCH) {
+ // A "No Such Process" error is fine, the process may have died already
+ // and been reaped by someone else.
+ return;
+ }
+
+ if (result == -1) {
+ PLOG(ERROR) << "Failed to register event to listen for death of pid "
+ << child;
+ return;
+ }
+
+ struct kevent event = {0};
+
+ DCHECK(timeout != 0);
+
+ int num_processes_that_died = -1;
+ using base::Time;
+ using base::TimeDelta;
+ // We need to keep track of the elapsed time - if kevent() returns
+ // EINTR in the middle of blocking call we want to make up what's left
+ // of the timeout.
+ TimeDelta time_left = TimeDelta::FromSeconds(timeout);
+ Time wakeup = Time::Now() + time_left;
+ while(time_left.InMilliseconds() > 0) {
+ const struct timespec timeout = time_left.ToTimeSpec();
+ num_processes_that_died = kevent(kq, NULL, 0, &event, 1, &timeout);
+ if (num_processes_that_died >= 0)
+ break;
+ if (num_processes_that_died == -1 && errno == EINTR) {
+ time_left = wakeup - Time::Now();
+ continue;
+ }
+
+ // If we got here, kevent() must have returned -1.
+ PLOG(ERROR) << "kevent() failed";
+ break;
+ }
+
+ if (num_processes_that_died == -1) {
+ PLOG(ERROR) << "kevent failed";
+ return;
+ }
+ if (num_processes_that_died == 1) {
+ if (event.fflags & NOTE_EXIT &&
+ event.ident == static_cast<uintptr_t>(child)) {
+ // Process died, it's safe to make a blocking call here since the
+ // kqueue() notification occurs when the process is already zombified.
+ BlockingReap(child);
+ return;
+ } else {
+ PLOG(ERROR) << "kevent() returned unexpected result - ke.fflags ="
+ << event.fflags
+ << " ke.ident ="
+ << event.ident
+ << " while listening for pid="
+ << child;
+ }
+ }
+
+ // If we got here the child is still alive so kill it...
+ if (kill(child, SIGKILL) == 0) {
+ // SIGKILL is uncatchable. Since the signal was delivered, we can
+ // just wait for the process to die now in a blocking manner.
+ BlockingReap(child);
+ } else {
+ PLOG(ERROR) << "While waiting for " << child << " to terminate we"
+ << " failed to deliver a SIGKILL signal";
+ }
+}
+
+} // namespace
+
+void ProcessWatcher::EnsureProcessTerminated(base::ProcessHandle process) {
+ WaitForChildToDie(process, 2);
+}