| Index: chrome/common/process_watcher_mac.cc
|
| diff --git a/chrome/common/process_watcher_mac.cc b/chrome/common/process_watcher_mac.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9e0dcf09cc343375f15374d17b2006fed20f4cb5
|
| --- /dev/null
|
| +++ b/chrome/common/process_watcher_mac.cc
|
| @@ -0,0 +1,136 @@
|
| +// 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 {
|
| +
|
| +// Return true if the given child is dead. This will also reap the process.
|
| +// Doesn't block.
|
| +bool IsChildDead(pid_t child) {
|
| + const pid_t result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG));
|
| + if (result == -1) {
|
| + PLOG(ERROR) << "call to waitpid() failed.";
|
| + } else if (result > 0) {
|
| + // The child has died.
|
| + return true;
|
| + }
|
| +
|
| + return false;
|
| +}
|
| +
|
| +// 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) {
|
| + // If the child is already dead, then there's nothing to do.
|
| + if (IsChildDead(process)) {
|
| + return;
|
| + }
|
| + WaitForChildToDie(process, 2);
|
| +}
|
|
|