diff options
author | rsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-10 18:36:09 +0000 |
---|---|---|
committer | rsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-10 18:36:09 +0000 |
commit | 307af2131ca46613b150c07b4b1b4a529faf2ece (patch) | |
tree | f7527104a9e682d9d5ad5a27821ceb6d470538c4 /base | |
parent | 79e30dda9361e31cd08c4ad42e31747cdd228f82 (diff) | |
download | chromium_src-307af2131ca46613b150c07b4b1b4a529faf2ece.zip chromium_src-307af2131ca46613b150c07b4b1b4a529faf2ece.tar.gz chromium_src-307af2131ca46613b150c07b4b1b4a529faf2ece.tar.bz2 |
Split out process killing functions from base/process_util.h into base/process/kill.h.
BUG=242290
R=brettw@chromium.org
Review URL: https://codereview.chromium.org/18555002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@210893 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base')
-rw-r--r-- | base/base.gypi | 7 | ||||
-rw-r--r-- | base/process/kill.cc (renamed from base/process_util.cc) | 9 | ||||
-rw-r--r-- | base/process/kill.h | 144 | ||||
-rw-r--r-- | base/process/kill_mac.cc | 173 | ||||
-rw-r--r-- | base/process/kill_posix.cc | 493 | ||||
-rw-r--r-- | base/process/kill_win.cc | 256 | ||||
-rw-r--r-- | base/process_util.h | 122 | ||||
-rw-r--r-- | base/process_util_mac.mm | 155 | ||||
-rw-r--r-- | base/process_util_posix.cc | 464 | ||||
-rw-r--r-- | base/process_util_win.cc | 223 |
10 files changed, 1079 insertions, 967 deletions
diff --git a/base/base.gypi b/base/base.gypi index fc7cf04..0935b14 100644 --- a/base/base.gypi +++ b/base/base.gypi @@ -385,7 +385,6 @@ 'process_info_win.cc', 'process_linux.cc', 'process_posix.cc', - 'process_util.cc', 'process_util.h', 'process_util_freebsd.cc', 'process_util_ios.mm', @@ -397,6 +396,11 @@ 'process_win.cc', 'process/internal_linux.cc', 'process/internal_linux.h', + 'process/kill.cc', + 'process/kill.h', + 'process/kill_mac.cc', + 'process/kill_posix.cc', + 'process/kill_win.cc', 'process/memory.h', 'process/memory_linux.cc', 'process/memory_mac.mm', @@ -694,6 +698,7 @@ 'path_service.cc', 'platform_file_posix.cc', 'posix/unix_domain_socket_linux.cc', + 'process/kill_posix.cc', 'process_posix.cc', 'process_util.cc', 'process_util_posix.cc', diff --git a/base/process_util.cc b/base/process/kill.cc index 9b89c8f..caca348 100644 --- a/base/process_util.cc +++ b/base/process/kill.cc @@ -1,12 +1,15 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2013 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 "base/process_util.h" +#include "base/process/kill.h" + +#include "base/process/process_iterator.h" namespace base { -bool KillProcesses(const FilePath::StringType& executable_name, int exit_code, +bool KillProcesses(const FilePath::StringType& executable_name, + int exit_code, const ProcessFilter* filter) { bool result = true; NamedProcessIterator iter(executable_name, filter); diff --git a/base/process/kill.h b/base/process/kill.h new file mode 100644 index 0000000..f7ee14c --- /dev/null +++ b/base/process/kill.h @@ -0,0 +1,144 @@ +// Copyright (c) 2013 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. + +// This file contains routines to kill processes and get the exit code and +// termination status. + +#ifndef BASE_PROCESS_KILL_H_ +#define BASE_PROCESS_KILL_H_ + +#include "base/files/file_path.h" +#include "base/process.h" +#include "base/time/time.h" + +namespace base { + +class ProcessFilter; + +// Return status values from GetTerminationStatus. Don't use these as +// exit code arguments to KillProcess*(), use platform/application +// specific values instead. +enum TerminationStatus { + TERMINATION_STATUS_NORMAL_TERMINATION, // zero exit status + TERMINATION_STATUS_ABNORMAL_TERMINATION, // non-zero exit status + TERMINATION_STATUS_PROCESS_WAS_KILLED, // e.g. SIGKILL or task manager kill + TERMINATION_STATUS_PROCESS_CRASHED, // e.g. Segmentation fault + TERMINATION_STATUS_STILL_RUNNING, // child hasn't exited yet + TERMINATION_STATUS_MAX_ENUM +}; + +// Attempts to kill all the processes on the current machine that were launched +// from the given executable name, ending them with the given exit code. If +// filter is non-null, then only processes selected by the filter are killed. +// Returns true if all processes were able to be killed off, false if at least +// one couldn't be killed. +BASE_EXPORT bool KillProcesses(const FilePath::StringType& executable_name, + int exit_code, + const ProcessFilter* filter); + +// Attempts to kill the process identified by the given process +// entry structure, giving it the specified exit code. If |wait| is true, wait +// for the process to be actually terminated before returning. +// Returns true if this is successful, false otherwise. +BASE_EXPORT bool KillProcess(ProcessHandle process, int exit_code, bool wait); + +#if defined(OS_POSIX) +// Attempts to kill the process group identified by |process_group_id|. Returns +// true on success. +BASE_EXPORT bool KillProcessGroup(ProcessHandle process_group_id); +#endif // defined(OS_POSIX) + +#if defined(OS_WIN) +BASE_EXPORT bool KillProcessById(ProcessId process_id, + int exit_code, + bool wait); +#endif // defined(OS_WIN) + +// Get the termination status of the process by interpreting the +// circumstances of the child process' death. |exit_code| is set to +// the status returned by waitpid() on POSIX, and from +// GetExitCodeProcess() on Windows. |exit_code| may be NULL if the +// caller is not interested in it. Note that on Linux, this function +// will only return a useful result the first time it is called after +// the child exits (because it will reap the child and the information +// will no longer be available). +BASE_EXPORT TerminationStatus GetTerminationStatus(ProcessHandle handle, + int* exit_code); + +#if defined(OS_POSIX) +// Wait for the process to exit and get the termination status. See +// GetTerminationStatus for more information. On POSIX systems, we can't call +// WaitForExitCode and then GetTerminationStatus as the child will be reaped +// when WaitForExitCode return and this information will be lost. +BASE_EXPORT TerminationStatus WaitForTerminationStatus(ProcessHandle handle, + int* exit_code); +#endif // defined(OS_POSIX) + +// Waits for process to exit. On POSIX systems, if the process hasn't been +// signaled then puts the exit code in |exit_code|; otherwise it's considered +// a failure. On Windows |exit_code| is always filled. Returns true on success, +// and closes |handle| in any case. +BASE_EXPORT bool WaitForExitCode(ProcessHandle handle, int* exit_code); + +// Waits for process to exit. If it did exit within |timeout_milliseconds|, +// then puts the exit code in |exit_code|, and returns true. +// In POSIX systems, if the process has been signaled then |exit_code| is set +// to -1. Returns false on failure (the caller is then responsible for closing +// |handle|). +// The caller is always responsible for closing the |handle|. +BASE_EXPORT bool WaitForExitCodeWithTimeout(ProcessHandle handle, + int* exit_code, + base::TimeDelta timeout); + +// Wait for all the processes based on the named executable to exit. If filter +// is non-null, then only processes selected by the filter are waited on. +// Returns after all processes have exited or wait_milliseconds have expired. +// Returns true if all the processes exited, false otherwise. +BASE_EXPORT bool WaitForProcessesToExit( + const FilePath::StringType& executable_name, + base::TimeDelta wait, + const ProcessFilter* filter); + +// Wait for a single process to exit. Return true if it exited cleanly within +// the given time limit. On Linux |handle| must be a child process, however +// on Mac and Windows it can be any process. +BASE_EXPORT bool WaitForSingleProcess(ProcessHandle handle, + base::TimeDelta wait); + +// Waits a certain amount of time (can be 0) for all the processes with a given +// executable name to exit, then kills off any of them that are still around. +// If filter is non-null, then only processes selected by the filter are waited +// on. Killed processes are ended with the given exit code. Returns false if +// any processes needed to be killed, true if they all exited cleanly within +// the wait_milliseconds delay. +BASE_EXPORT bool CleanupProcesses(const FilePath::StringType& executable_name, + base::TimeDelta wait, + int exit_code, + const ProcessFilter* filter); + +// This method ensures that the specified process eventually terminates, and +// then it closes the given process handle. +// +// It assumes that the process has already been signalled to exit, and it +// begins by waiting a small amount of time for it to exit. If the process +// does not appear to have exited, then this function starts to become +// aggressive about ensuring that the process terminates. +// +// On Linux this method does not block the calling thread. +// On OS X this method may block for up to 2 seconds. +// +// NOTE: The process handle must have been opened with the PROCESS_TERMINATE +// and SYNCHRONIZE permissions. +// +BASE_EXPORT void EnsureProcessTerminated(ProcessHandle process_handle); + +#if defined(OS_POSIX) && !defined(OS_MACOSX) +// The nicer version of EnsureProcessTerminated() that is patient and will +// wait for |process_handle| to finish and then reap it. +BASE_EXPORT void EnsureProcessGetsReaped(ProcessHandle process_handle); +#endif + +} // namespace base + +#endif // BASE_PROCESS_KILL_H_ diff --git a/base/process/kill_mac.cc b/base/process/kill_mac.cc new file mode 100644 index 0000000..5ebca5d --- /dev/null +++ b/base/process/kill_mac.cc @@ -0,0 +1,173 @@ +// Copyright (c) 2013 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 "base/process/kill.h" + +#include <signal.h> +#include <sys/event.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +namespace base { + +namespace { + +const int kWaitBeforeKillSeconds = 2; + +// 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) { + DPLOG(ERROR) << "waitpid(" << child << ", NULL, 0)"; + } +} + +// Waits for |timeout| seconds for the given |child| to exit and reap it. If +// the child doesn't exit within the time specified, kills it. +// +// This function takes two approaches: first, it tries to use kqueue to +// observe when the process exits. kevent can monitor a kqueue with a +// timeout, so this method is preferred to wait for a specified period of +// time. Once the kqueue indicates the process has exited, waitpid will reap +// the exited child. If the kqueue doesn't provide an exit event notification, +// before the timeout expires, or if the kqueue fails or misbehaves, the +// process will be mercilessly killed and reaped. +// +// A child process passed to this function may be in one of several states: +// running, terminated and not yet reaped, and (apparently, and unfortunately) +// terminated and already reaped. Normally, a process will at least have been +// asked to exit before this function is called, but this is not required. +// If a process is terminating and unreaped, there may be a window between the +// time that kqueue will no longer recognize it and when it becomes an actual +// zombie that a non-blocking (WNOHANG) waitpid can reap. This condition is +// detected when kqueue indicates that the process is not running and a +// non-blocking waitpid fails to reap the process but indicates that it is +// still running. In this event, a blocking attempt to reap the process +// collects the known-dying child, preventing zombies from congregating. +// +// In the event that the kqueue misbehaves entirely, as it might under a +// EMFILE condition ("too many open files", or out of file descriptors), this +// function will forcibly kill and reap the child without delay. This +// eliminates another potential zombie vector. (If you're out of file +// descriptors, you're probably deep into something else, but that doesn't +// mean that zombies be allowed to kick you while you're down.) +// +// The fact that this function seemingly can be called to wait on a child +// that's not only already terminated but already reaped is a bit of a +// problem: a reaped child's pid can be reclaimed and may refer to a distinct +// process in that case. The fact that this function can seemingly be called +// to wait on a process that's not even a child is also a problem: kqueue will +// work in that case, but waitpid won't, and killing a non-child might not be +// the best approach. +void WaitForChildToDie(pid_t child, int timeout) { + DCHECK(child > 0); + DCHECK(timeout > 0); + + // DON'T ADD ANY EARLY RETURNS TO THIS FUNCTION without ensuring that + // |child| has been reaped. Specifically, even if a kqueue, kevent, or other + // call fails, this function should fall back to the last resort of trying + // to kill and reap the process. Not observing this rule will resurrect + // zombies. + + int result; + + int kq = HANDLE_EINTR(kqueue()); + if (kq == -1) { + DPLOG(ERROR) << "kqueue()"; + } else { + file_util::ScopedFD auto_close_kq(&kq); + + struct kevent change = {0}; + EV_SET(&change, child, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); + result = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL)); + + if (result == -1) { + if (errno != ESRCH) { + DPLOG(ERROR) << "kevent (setup " << child << ")"; + } else { + // At this point, one of the following has occurred: + // 1. The process has died but has not yet been reaped. + // 2. The process has died and has already been reaped. + // 3. The process is in the process of dying. It's no longer + // kqueueable, but it may not be waitable yet either. Mark calls + // this case the "zombie death race". + + result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG)); + + if (result != 0) { + // A positive result indicates case 1. waitpid succeeded and reaped + // the child. A result of -1 indicates case 2. The child has already + // been reaped. In both of these cases, no further action is + // necessary. + return; + } + + // |result| is 0, indicating case 3. The process will be waitable in + // short order. Fall back out of the kqueue code to kill it (for good + // measure) and reap it. + } + } else { + // Keep track of the elapsed time to be able to restart kevent if it's + // interrupted. + TimeDelta remaining_delta = TimeDelta::FromSeconds(timeout); + TimeTicks deadline = TimeTicks::Now() + remaining_delta; + result = -1; + struct kevent event = {0}; + while (remaining_delta.InMilliseconds() > 0) { + const struct timespec remaining_timespec = remaining_delta.ToTimeSpec(); + result = kevent(kq, NULL, 0, &event, 1, &remaining_timespec); + if (result == -1 && errno == EINTR) { + remaining_delta = deadline - TimeTicks::Now(); + result = 0; + } else { + break; + } + } + + if (result == -1) { + DPLOG(ERROR) << "kevent (wait " << child << ")"; + } else if (result > 1) { + DLOG(ERROR) << "kevent (wait " << child << "): unexpected result " + << result; + } else if (result == 1) { + if ((event.fflags & NOTE_EXIT) && + (event.ident == static_cast<uintptr_t>(child))) { + // The process is dead or dying. This won't block for long, if at + // all. + BlockingReap(child); + return; + } else { + DLOG(ERROR) << "kevent (wait " << child + << "): unexpected event: fflags=" << event.fflags + << ", ident=" << event.ident; + } + } + } + } + + // The child is still alive, or is very freshly dead. Be sure by sending it + // a signal. This is safe even if it's freshly dead, because it will be a + // zombie (or on the way to zombiedom) and kill will return 0 even if the + // signal is not delivered to a live process. + result = kill(child, SIGKILL); + if (result == -1) { + DPLOG(ERROR) << "kill(" << child << ", SIGKILL)"; + } else { + // The child is definitely on the way out now. BlockingReap won't need to + // wait for long, if at all. + BlockingReap(child); + } +} + +} // namespace + +void EnsureProcessTerminated(ProcessHandle process) { + WaitForChildToDie(process, kWaitBeforeKillSeconds); +} + +} // namespace base diff --git a/base/process/kill_posix.cc b/base/process/kill_posix.cc new file mode 100644 index 0000000..61a6020 --- /dev/null +++ b/base/process/kill_posix.cc @@ -0,0 +1,493 @@ +// Copyright (c) 2013 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 "base/process/kill.h" + +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/process_iterator.h" +#include "base/process_util.h" +#include "base/synchronization/waitable_event.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/platform_thread.h" + +namespace base { + +namespace { + +int WaitpidWithTimeout(ProcessHandle handle, + int64 wait_milliseconds, + bool* success) { + // This POSIX version of this function only guarantees that we wait no less + // than |wait_milliseconds| for the process to exit. The child process may + // exit sometime before the timeout has ended but we may still block for up + // to 256 milliseconds after the fact. + // + // waitpid() has no direct support on POSIX for specifying a timeout, you can + // either ask it to block indefinitely or return immediately (WNOHANG). + // When a child process terminates a SIGCHLD signal is sent to the parent. + // Catching this signal would involve installing a signal handler which may + // affect other parts of the application and would be difficult to debug. + // + // Our strategy is to call waitpid() once up front to check if the process + // has already exited, otherwise to loop for wait_milliseconds, sleeping for + // at most 256 milliseconds each time using usleep() and then calling + // waitpid(). The amount of time we sleep starts out at 1 milliseconds, and + // we double it every 4 sleep cycles. + // + // usleep() is speced to exit if a signal is received for which a handler + // has been installed. This means that when a SIGCHLD is sent, it will exit + // depending on behavior external to this function. + // + // This function is used primarily for unit tests, if we want to use it in + // the application itself it would probably be best to examine other routes. + int status = -1; + pid_t ret_pid = HANDLE_EINTR(waitpid(handle, &status, WNOHANG)); + static const int64 kMaxSleepInMicroseconds = 1 << 18; // ~256 milliseconds. + int64 max_sleep_time_usecs = 1 << 10; // ~1 milliseconds. + int64 double_sleep_time = 0; + + // If the process hasn't exited yet, then sleep and try again. + TimeTicks wakeup_time = TimeTicks::Now() + + TimeDelta::FromMilliseconds(wait_milliseconds); + while (ret_pid == 0) { + TimeTicks now = TimeTicks::Now(); + if (now > wakeup_time) + break; + // Guaranteed to be non-negative! + int64 sleep_time_usecs = (wakeup_time - now).InMicroseconds(); + // Sleep for a bit while we wait for the process to finish. + if (sleep_time_usecs > max_sleep_time_usecs) + sleep_time_usecs = max_sleep_time_usecs; + + // usleep() will return 0 and set errno to EINTR on receipt of a signal + // such as SIGCHLD. + usleep(sleep_time_usecs); + ret_pid = HANDLE_EINTR(waitpid(handle, &status, WNOHANG)); + + if ((max_sleep_time_usecs < kMaxSleepInMicroseconds) && + (double_sleep_time++ % 4 == 0)) { + max_sleep_time_usecs *= 2; + } + } + + if (success) + *success = (ret_pid != -1); + + return status; +} + +TerminationStatus GetTerminationStatusImpl(ProcessHandle handle, + bool can_block, + int* exit_code) { + int status = 0; + const pid_t result = HANDLE_EINTR(waitpid(handle, &status, + can_block ? 0 : WNOHANG)); + if (result == -1) { + DPLOG(ERROR) << "waitpid(" << handle << ")"; + if (exit_code) + *exit_code = 0; + return TERMINATION_STATUS_NORMAL_TERMINATION; + } else if (result == 0) { + // the child hasn't exited yet. + if (exit_code) + *exit_code = 0; + return TERMINATION_STATUS_STILL_RUNNING; + } + + if (exit_code) + *exit_code = status; + + if (WIFSIGNALED(status)) { + switch (WTERMSIG(status)) { + case SIGABRT: + case SIGBUS: + case SIGFPE: + case SIGILL: + case SIGSEGV: + return TERMINATION_STATUS_PROCESS_CRASHED; + case SIGINT: + case SIGKILL: + case SIGTERM: + return TERMINATION_STATUS_PROCESS_WAS_KILLED; + default: + break; + } + } + + if (WIFEXITED(status) && WEXITSTATUS(status) != 0) + return TERMINATION_STATUS_ABNORMAL_TERMINATION; + + return TERMINATION_STATUS_NORMAL_TERMINATION; +} + +} // namespace + +// Attempts to kill the process identified by the given process +// entry structure. Ignores specified exit_code; posix can't force that. +// Returns true if this is successful, false otherwise. +bool KillProcess(ProcessHandle process_id, int exit_code, bool wait) { + DCHECK_GT(process_id, 1) << " tried to kill invalid process_id"; + if (process_id <= 1) + return false; + bool result = kill(process_id, SIGTERM) == 0; + if (result && wait) { + int tries = 60; + + if (RunningOnValgrind()) { + // Wait for some extra time when running under Valgrind since the child + // processes may take some time doing leak checking. + tries *= 2; + } + + unsigned sleep_ms = 4; + + // The process may not end immediately due to pending I/O + bool exited = false; + while (tries-- > 0) { + pid_t pid = HANDLE_EINTR(waitpid(process_id, NULL, WNOHANG)); + if (pid == process_id) { + exited = true; + break; + } + if (pid == -1) { + if (errno == ECHILD) { + // The wait may fail with ECHILD if another process also waited for + // the same pid, causing the process state to get cleaned up. + exited = true; + break; + } + DPLOG(ERROR) << "Error waiting for process " << process_id; + } + + usleep(sleep_ms * 1000); + const unsigned kMaxSleepMs = 1000; + if (sleep_ms < kMaxSleepMs) + sleep_ms *= 2; + } + + // If we're waiting and the child hasn't died by now, force it + // with a SIGKILL. + if (!exited) + result = kill(process_id, SIGKILL) == 0; + } + + if (!result) + DPLOG(ERROR) << "Unable to terminate process " << process_id; + + return result; +} + +bool KillProcessGroup(ProcessHandle process_group_id) { + bool result = kill(-1 * process_group_id, SIGKILL) == 0; + if (!result) + DPLOG(ERROR) << "Unable to terminate process group " << process_group_id; + return result; +} + +TerminationStatus GetTerminationStatus(ProcessHandle handle, int* exit_code) { + return GetTerminationStatusImpl(handle, false /* can_block */, exit_code); +} + +TerminationStatus WaitForTerminationStatus(ProcessHandle handle, + int* exit_code) { + return GetTerminationStatusImpl(handle, true /* can_block */, exit_code); +} + +bool WaitForExitCode(ProcessHandle handle, int* exit_code) { + int status; + if (HANDLE_EINTR(waitpid(handle, &status, 0)) == -1) { + NOTREACHED(); + return false; + } + + if (WIFEXITED(status)) { + *exit_code = WEXITSTATUS(status); + return true; + } + + // If it didn't exit cleanly, it must have been signaled. + DCHECK(WIFSIGNALED(status)); + return false; +} + +bool WaitForExitCodeWithTimeout(ProcessHandle handle, + int* exit_code, + base::TimeDelta timeout) { + bool waitpid_success = false; + int status = WaitpidWithTimeout(handle, timeout.InMilliseconds(), + &waitpid_success); + if (status == -1) + return false; + if (!waitpid_success) + return false; + if (WIFSIGNALED(status)) { + *exit_code = -1; + return true; + } + if (WIFEXITED(status)) { + *exit_code = WEXITSTATUS(status); + return true; + } + return false; +} + +bool WaitForProcessesToExit(const FilePath::StringType& executable_name, + base::TimeDelta wait, + const ProcessFilter* filter) { + bool result = false; + + // TODO(port): This is inefficient, but works if there are multiple procs. + // TODO(port): use waitpid to avoid leaving zombies around + + base::TimeTicks end_time = base::TimeTicks::Now() + wait; + do { + NamedProcessIterator iter(executable_name, filter); + if (!iter.NextProcessEntry()) { + result = true; + break; + } + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); + } while ((end_time - base::TimeTicks::Now()) > base::TimeDelta()); + + return result; +} + +#if defined(OS_MACOSX) +// Using kqueue on Mac so that we can wait on non-child processes. +// We can't use kqueues on child processes because we need to reap +// our own children using wait. +static bool WaitForSingleNonChildProcess(ProcessHandle handle, + base::TimeDelta wait) { + DCHECK_GT(handle, 0); + DCHECK(wait.InMilliseconds() == base::kNoTimeout || wait > base::TimeDelta()); + + int kq = kqueue(); + if (kq == -1) { + DPLOG(ERROR) << "kqueue"; + return false; + } + file_util::ScopedFD kq_closer(&kq); + + struct kevent change = {0}; + EV_SET(&change, handle, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); + int result = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL)); + if (result == -1) { + if (errno == ESRCH) { + // If the process wasn't found, it must be dead. + return true; + } + + DPLOG(ERROR) << "kevent (setup " << handle << ")"; + return false; + } + + // Keep track of the elapsed time to be able to restart kevent if it's + // interrupted. + bool wait_forever = wait.InMilliseconds() == base::kNoTimeout; + base::TimeDelta remaining_delta; + base::TimeTicks deadline; + if (!wait_forever) { + remaining_delta = wait; + deadline = base::TimeTicks::Now() + remaining_delta; + } + + result = -1; + struct kevent event = {0}; + + while (wait_forever || remaining_delta > base::TimeDelta()) { + struct timespec remaining_timespec; + struct timespec* remaining_timespec_ptr; + if (wait_forever) { + remaining_timespec_ptr = NULL; + } else { + remaining_timespec = remaining_delta.ToTimeSpec(); + remaining_timespec_ptr = &remaining_timespec; + } + + result = kevent(kq, NULL, 0, &event, 1, remaining_timespec_ptr); + + if (result == -1 && errno == EINTR) { + if (!wait_forever) { + remaining_delta = deadline - base::TimeTicks::Now(); + } + result = 0; + } else { + break; + } + } + + if (result < 0) { + DPLOG(ERROR) << "kevent (wait " << handle << ")"; + return false; + } else if (result > 1) { + DLOG(ERROR) << "kevent (wait " << handle << "): unexpected result " + << result; + return false; + } else if (result == 0) { + // Timed out. + return false; + } + + DCHECK_EQ(result, 1); + + if (event.filter != EVFILT_PROC || + (event.fflags & NOTE_EXIT) == 0 || + event.ident != static_cast<uintptr_t>(handle)) { + DLOG(ERROR) << "kevent (wait " << handle + << "): unexpected event: filter=" << event.filter + << ", fflags=" << event.fflags + << ", ident=" << event.ident; + return false; + } + + return true; +} +#endif // OS_MACOSX + +bool WaitForSingleProcess(ProcessHandle handle, base::TimeDelta wait) { + ProcessHandle parent_pid = GetParentProcessId(handle); + ProcessHandle our_pid = Process::Current().handle(); + if (parent_pid != our_pid) { +#if defined(OS_MACOSX) + // On Mac we can wait on non child processes. + return WaitForSingleNonChildProcess(handle, wait); +#else + // Currently on Linux we can't handle non child processes. + NOTIMPLEMENTED(); +#endif // OS_MACOSX + } + + bool waitpid_success; + int status = -1; + if (wait.InMilliseconds() == base::kNoTimeout) { + waitpid_success = (HANDLE_EINTR(waitpid(handle, &status, 0)) != -1); + } else { + status = WaitpidWithTimeout( + handle, wait.InMilliseconds(), &waitpid_success); + } + + if (status != -1) { + DCHECK(waitpid_success); + return WIFEXITED(status); + } else { + return false; + } +} + +bool CleanupProcesses(const FilePath::StringType& executable_name, + base::TimeDelta wait, + int exit_code, + const ProcessFilter* filter) { + bool exited_cleanly = WaitForProcessesToExit(executable_name, wait, filter); + if (!exited_cleanly) + KillProcesses(executable_name, exit_code, filter); + return exited_cleanly; +} + +#if !defined(OS_MACOSX) + +namespace { + +// Return true if the given child is dead. This will also reap the process. +// Doesn't block. +static bool IsChildDead(pid_t child) { + const pid_t result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG)); + if (result == -1) { + DPLOG(ERROR) << "waitpid(" << child << ")"; + NOTREACHED(); + } else if (result > 0) { + // The child has died. + return true; + } + + return false; +} + +// A thread class which waits for the given child to exit and reaps it. +// If the child doesn't exit within a couple of seconds, kill it. +class BackgroundReaper : public PlatformThread::Delegate { + public: + BackgroundReaper(pid_t child, unsigned timeout) + : child_(child), + timeout_(timeout) { + } + + // Overridden from PlatformThread::Delegate: + virtual void ThreadMain() OVERRIDE { + WaitForChildToDie(); + delete this; + } + + void WaitForChildToDie() { + // Wait forever case. + if (timeout_ == 0) { + pid_t r = HANDLE_EINTR(waitpid(child_, NULL, 0)); + if (r != child_) { + DPLOG(ERROR) << "While waiting for " << child_ + << " to terminate, we got the following result: " << r; + } + return; + } + + // There's no good way to wait for a specific child to exit in a timed + // fashion. (No kqueue on Linux), so we just loop and sleep. + + // Wait for 2 * timeout_ 500 milliseconds intervals. + for (unsigned i = 0; i < 2 * timeout_; ++i) { + PlatformThread::Sleep(TimeDelta::FromMilliseconds(500)); + if (IsChildDead(child_)) + return; + } + + 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. + if (HANDLE_EINTR(waitpid(child_, NULL, 0)) < 0) + DPLOG(WARNING) << "waitpid"; + } else { + DLOG(ERROR) << "While waiting for " << child_ << " to terminate we" + << " failed to deliver a SIGKILL signal (" << errno << ")."; + } + } + + private: + const pid_t child_; + // Number of seconds to wait, if 0 then wait forever and do not attempt to + // kill |child_|. + const unsigned timeout_; + + DISALLOW_COPY_AND_ASSIGN(BackgroundReaper); +}; + +} // namespace + +void EnsureProcessTerminated(ProcessHandle process) { + // If the child is already dead, then there's nothing to do. + if (IsChildDead(process)) + return; + + const unsigned timeout = 2; // seconds + BackgroundReaper* reaper = new BackgroundReaper(process, timeout); + PlatformThread::CreateNonJoinable(0, reaper); +} + +void EnsureProcessGetsReaped(ProcessHandle process) { + // If the child is already dead, then there's nothing to do. + if (IsChildDead(process)) + return; + + BackgroundReaper* reaper = new BackgroundReaper(process, 0); + PlatformThread::CreateNonJoinable(0, reaper); +} + +#endif // !defined(OS_MACOSX) + +} // namespace base diff --git a/base/process/kill_win.cc b/base/process/kill_win.cc new file mode 100644 index 0000000..929ec4f --- /dev/null +++ b/base/process/kill_win.cc @@ -0,0 +1,256 @@ +// Copyright (c) 2013 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 "base/process/kill.h" + +#include <io.h> +#include <windows.h> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/process/process_iterator.h" +#include "base/process_util.h" +#include "base/win/object_watcher.h" + +namespace base { + +namespace { + +// Exit codes with special meanings on Windows. +const DWORD kNormalTerminationExitCode = 0; +const DWORD kDebuggerInactiveExitCode = 0xC0000354; +const DWORD kKeyboardInterruptExitCode = 0xC000013A; +const DWORD kDebuggerTerminatedExitCode = 0x40010004; + +// This exit code is used by the Windows task manager when it kills a +// process. It's value is obviously not that unique, and it's +// surprising to me that the task manager uses this value, but it +// seems to be common practice on Windows to test for it as an +// indication that the task manager has killed something if the +// process goes away. +const DWORD kProcessKilledExitCode = 1; + +// Maximum amount of time (in milliseconds) to wait for the process to exit. +static const int kWaitInterval = 2000; + +class TimerExpiredTask : public win::ObjectWatcher::Delegate { + public: + explicit TimerExpiredTask(ProcessHandle process); + ~TimerExpiredTask(); + + void TimedOut(); + + // MessageLoop::Watcher ----------------------------------------------------- + virtual void OnObjectSignaled(HANDLE object); + + private: + void KillProcess(); + + // The process that we are watching. + ProcessHandle process_; + + win::ObjectWatcher watcher_; + + DISALLOW_COPY_AND_ASSIGN(TimerExpiredTask); +}; + +TimerExpiredTask::TimerExpiredTask(ProcessHandle process) : process_(process) { + watcher_.StartWatching(process_, this); +} + +TimerExpiredTask::~TimerExpiredTask() { + TimedOut(); + DCHECK(!process_) << "Make sure to close the handle."; +} + +void TimerExpiredTask::TimedOut() { + if (process_) + KillProcess(); +} + +void TimerExpiredTask::OnObjectSignaled(HANDLE object) { + CloseHandle(process_); + process_ = NULL; +} + +void TimerExpiredTask::KillProcess() { + // Stop watching the process handle since we're killing it. + watcher_.StopWatching(); + + // OK, time to get frisky. We don't actually care when the process + // terminates. We just care that it eventually terminates, and that's what + // TerminateProcess should do for us. Don't check for the result code since + // it fails quite often. This should be investigated eventually. + base::KillProcess(process_, kProcessKilledExitCode, false); + + // Now, just cleanup as if the process exited normally. + OnObjectSignaled(process_); +} + +} // namespace + +bool KillProcess(ProcessHandle process, int exit_code, bool wait) { + bool result = (TerminateProcess(process, exit_code) != FALSE); + if (result && wait) { + // The process may not end immediately due to pending I/O + if (WAIT_OBJECT_0 != WaitForSingleObject(process, 60 * 1000)) + DLOG_GETLASTERROR(ERROR) << "Error waiting for process exit"; + } else if (!result) { + DLOG_GETLASTERROR(ERROR) << "Unable to terminate process"; + } + return result; +} + +// Attempts to kill the process identified by the given process +// entry structure, giving it the specified exit code. +// Returns true if this is successful, false otherwise. +bool KillProcessById(ProcessId process_id, int exit_code, bool wait) { + HANDLE process = OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, + FALSE, // Don't inherit handle + process_id); + if (!process) { + DLOG_GETLASTERROR(ERROR) << "Unable to open process " << process_id; + return false; + } + bool ret = KillProcess(process, exit_code, wait); + CloseHandle(process); + return ret; +} + +TerminationStatus GetTerminationStatus(ProcessHandle handle, int* exit_code) { + DWORD tmp_exit_code = 0; + + if (!::GetExitCodeProcess(handle, &tmp_exit_code)) { + DLOG_GETLASTERROR(FATAL) << "GetExitCodeProcess() failed"; + if (exit_code) { + // This really is a random number. We haven't received any + // information about the exit code, presumably because this + // process doesn't have permission to get the exit code, or + // because of some other cause for GetExitCodeProcess to fail + // (MSDN docs don't give the possible failure error codes for + // this function, so it could be anything). But we don't want + // to leave exit_code uninitialized, since that could cause + // random interpretations of the exit code. So we assume it + // terminated "normally" in this case. + *exit_code = kNormalTerminationExitCode; + } + // Assume the child has exited normally if we can't get the exit + // code. + return TERMINATION_STATUS_NORMAL_TERMINATION; + } + if (tmp_exit_code == STILL_ACTIVE) { + DWORD wait_result = WaitForSingleObject(handle, 0); + if (wait_result == WAIT_TIMEOUT) { + if (exit_code) + *exit_code = wait_result; + return TERMINATION_STATUS_STILL_RUNNING; + } + + if (wait_result == WAIT_FAILED) { + DLOG_GETLASTERROR(ERROR) << "WaitForSingleObject() failed"; + } else { + DCHECK_EQ(WAIT_OBJECT_0, wait_result); + + // Strange, the process used 0x103 (STILL_ACTIVE) as exit code. + NOTREACHED(); + } + + return TERMINATION_STATUS_ABNORMAL_TERMINATION; + } + + if (exit_code) + *exit_code = tmp_exit_code; + + switch (tmp_exit_code) { + case kNormalTerminationExitCode: + return TERMINATION_STATUS_NORMAL_TERMINATION; + case kDebuggerInactiveExitCode: // STATUS_DEBUGGER_INACTIVE. + case kKeyboardInterruptExitCode: // Control-C/end session. + case kDebuggerTerminatedExitCode: // Debugger terminated process. + case kProcessKilledExitCode: // Task manager kill. + return TERMINATION_STATUS_PROCESS_WAS_KILLED; + default: + // All other exit codes indicate crashes. + return TERMINATION_STATUS_PROCESS_CRASHED; + } +} + +bool WaitForExitCode(ProcessHandle handle, int* exit_code) { + bool success = WaitForExitCodeWithTimeout( + handle, exit_code, base::TimeDelta::FromMilliseconds(INFINITE)); + CloseProcessHandle(handle); + return success; +} + +bool WaitForExitCodeWithTimeout(ProcessHandle handle, + int* exit_code, + base::TimeDelta timeout) { + if (::WaitForSingleObject(handle, timeout.InMilliseconds()) != WAIT_OBJECT_0) + return false; + DWORD temp_code; // Don't clobber out-parameters in case of failure. + if (!::GetExitCodeProcess(handle, &temp_code)) + return false; + + *exit_code = temp_code; + return true; +} + +bool WaitForProcessesToExit(const FilePath::StringType& executable_name, + base::TimeDelta wait, + const ProcessFilter* filter) { + const ProcessEntry* entry; + bool result = true; + DWORD start_time = GetTickCount(); + + NamedProcessIterator iter(executable_name, filter); + while ((entry = iter.NextProcessEntry())) { + DWORD remaining_wait = std::max<int64>( + 0, wait.InMilliseconds() - (GetTickCount() - start_time)); + HANDLE process = OpenProcess(SYNCHRONIZE, + FALSE, + entry->th32ProcessID); + DWORD wait_result = WaitForSingleObject(process, remaining_wait); + CloseHandle(process); + result = result && (wait_result == WAIT_OBJECT_0); + } + + return result; +} + +bool WaitForSingleProcess(ProcessHandle handle, base::TimeDelta wait) { + int exit_code; + if (!WaitForExitCodeWithTimeout(handle, &exit_code, wait)) + return false; + return exit_code == 0; +} + +bool CleanupProcesses(const FilePath::StringType& executable_name, + base::TimeDelta wait, + int exit_code, + const ProcessFilter* filter) { + bool exited_cleanly = WaitForProcessesToExit(executable_name, wait, filter); + if (!exited_cleanly) + KillProcesses(executable_name, exit_code, filter); + return exited_cleanly; +} + +void EnsureProcessTerminated(ProcessHandle process) { + DCHECK(process != GetCurrentProcess()); + + // If already signaled, then we are done! + if (WaitForSingleObject(process, 0) == WAIT_OBJECT_0) { + CloseHandle(process); + return; + } + + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&TimerExpiredTask::TimedOut, + base::Owned(new TimerExpiredTask(process))), + base::TimeDelta::FromMilliseconds(kWaitInterval)); +} + +} // namespace base diff --git a/base/process_util.h b/base/process_util.h index fdc9cf9..08add29 100644 --- a/base/process_util.h +++ b/base/process_util.h @@ -37,6 +37,7 @@ typedef struct _malloc_zone_t malloc_zone_t; #include "base/files/file_path.h" #include "base/process.h" #include "base/process/memory.h" +#include "base/process/kill.h" #include "base/process/process_iterator.h" #include "base/process/process_metrics.h" @@ -48,18 +49,6 @@ class CommandLine; namespace base { -// Return status values from GetTerminationStatus. Don't use these as -// exit code arguments to KillProcess*(), use platform/application -// specific values instead. -enum TerminationStatus { - TERMINATION_STATUS_NORMAL_TERMINATION, // zero exit status - TERMINATION_STATUS_ABNORMAL_TERMINATION, // non-zero exit status - TERMINATION_STATUS_PROCESS_WAS_KILLED, // e.g. SIGKILL or task manager kill - TERMINATION_STATUS_PROCESS_CRASHED, // e.g. Segmentation fault - TERMINATION_STATUS_STILL_RUNNING, // child hasn't exited yet - TERMINATION_STATUS_MAX_ENUM -}; - #if defined(OS_WIN) // Output multi-process printf, cout, cerr, etc to the cmd.exe console that ran // chrome. This is not thread-safe: only call from main thread. @@ -345,115 +334,6 @@ BASE_EXPORT bool GetAppOutputWithExitCode(const CommandLine& cl, std::string* output, int* exit_code); #endif // defined(OS_POSIX) -// Attempts to kill all the processes on the current machine that were launched -// from the given executable name, ending them with the given exit code. If -// filter is non-null, then only processes selected by the filter are killed. -// Returns true if all processes were able to be killed off, false if at least -// one couldn't be killed. -BASE_EXPORT bool KillProcesses(const FilePath::StringType& executable_name, - int exit_code, const ProcessFilter* filter); - -// Attempts to kill the process identified by the given process -// entry structure, giving it the specified exit code. If |wait| is true, wait -// for the process to be actually terminated before returning. -// Returns true if this is successful, false otherwise. -BASE_EXPORT bool KillProcess(ProcessHandle process, int exit_code, bool wait); - -#if defined(OS_POSIX) -// Attempts to kill the process group identified by |process_group_id|. Returns -// true on success. -BASE_EXPORT bool KillProcessGroup(ProcessHandle process_group_id); -#endif // defined(OS_POSIX) - -#if defined(OS_WIN) -BASE_EXPORT bool KillProcessById(ProcessId process_id, int exit_code, - bool wait); -#endif // defined(OS_WIN) - -// Get the termination status of the process by interpreting the -// circumstances of the child process' death. |exit_code| is set to -// the status returned by waitpid() on POSIX, and from -// GetExitCodeProcess() on Windows. |exit_code| may be NULL if the -// caller is not interested in it. Note that on Linux, this function -// will only return a useful result the first time it is called after -// the child exits (because it will reap the child and the information -// will no longer be available). -BASE_EXPORT TerminationStatus GetTerminationStatus(ProcessHandle handle, - int* exit_code); - -#if defined(OS_POSIX) -// Wait for the process to exit and get the termination status. See -// GetTerminationStatus for more information. On POSIX systems, we can't call -// WaitForExitCode and then GetTerminationStatus as the child will be reaped -// when WaitForExitCode return and this information will be lost. -BASE_EXPORT TerminationStatus WaitForTerminationStatus(ProcessHandle handle, - int* exit_code); -#endif // defined(OS_POSIX) - -// Waits for process to exit. On POSIX systems, if the process hasn't been -// signaled then puts the exit code in |exit_code|; otherwise it's considered -// a failure. On Windows |exit_code| is always filled. Returns true on success, -// and closes |handle| in any case. -BASE_EXPORT bool WaitForExitCode(ProcessHandle handle, int* exit_code); - -// Waits for process to exit. If it did exit within |timeout_milliseconds|, -// then puts the exit code in |exit_code|, and returns true. -// In POSIX systems, if the process has been signaled then |exit_code| is set -// to -1. Returns false on failure (the caller is then responsible for closing -// |handle|). -// The caller is always responsible for closing the |handle|. -BASE_EXPORT bool WaitForExitCodeWithTimeout(ProcessHandle handle, - int* exit_code, - base::TimeDelta timeout); - -// Wait for all the processes based on the named executable to exit. If filter -// is non-null, then only processes selected by the filter are waited on. -// Returns after all processes have exited or wait_milliseconds have expired. -// Returns true if all the processes exited, false otherwise. -BASE_EXPORT bool WaitForProcessesToExit( - const FilePath::StringType& executable_name, - base::TimeDelta wait, - const ProcessFilter* filter); - -// Wait for a single process to exit. Return true if it exited cleanly within -// the given time limit. On Linux |handle| must be a child process, however -// on Mac and Windows it can be any process. -BASE_EXPORT bool WaitForSingleProcess(ProcessHandle handle, - base::TimeDelta wait); - -// Waits a certain amount of time (can be 0) for all the processes with a given -// executable name to exit, then kills off any of them that are still around. -// If filter is non-null, then only processes selected by the filter are waited -// on. Killed processes are ended with the given exit code. Returns false if -// any processes needed to be killed, true if they all exited cleanly within -// the wait_milliseconds delay. -BASE_EXPORT bool CleanupProcesses(const FilePath::StringType& executable_name, - base::TimeDelta wait, - int exit_code, - const ProcessFilter* filter); - -// This method ensures that the specified process eventually terminates, and -// then it closes the given process handle. -// -// It assumes that the process has already been signalled to exit, and it -// begins by waiting a small amount of time for it to exit. If the process -// does not appear to have exited, then this function starts to become -// aggressive about ensuring that the process terminates. -// -// On Linux this method does not block the calling thread. -// On OS X this method may block for up to 2 seconds. -// -// NOTE: The process handle must have been opened with the PROCESS_TERMINATE -// and SYNCHRONIZE permissions. -// -BASE_EXPORT void EnsureProcessTerminated(ProcessHandle process_handle); - -#if defined(OS_POSIX) && !defined(OS_MACOSX) -// The nicer version of EnsureProcessTerminated() that is patient and will -// wait for |process_handle| to finish and then reap it. -BASE_EXPORT void EnsureProcessGetsReaped(ProcessHandle process_handle); -#endif - // If supported on the platform, and the user has sufficent rights, increase // the current process's scheduling priority to a high priority. BASE_EXPORT void RaiseProcessToHighPriority(); diff --git a/base/process_util_mac.mm b/base/process_util_mac.mm index 183463e..c82cd50 100644 --- a/base/process_util_mac.mm +++ b/base/process_util_mac.mm @@ -70,159 +70,4 @@ ProcessId GetParentProcessId(ProcessHandle process) { return info.kp_eproc.e_ppid; } -namespace { - -const int kWaitBeforeKillSeconds = 2; - -// 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) { - DPLOG(ERROR) << "waitpid(" << child << ", NULL, 0)"; - } -} - -// Waits for |timeout| seconds for the given |child| to exit and reap it. If -// the child doesn't exit within the time specified, kills it. -// -// This function takes two approaches: first, it tries to use kqueue to -// observe when the process exits. kevent can monitor a kqueue with a -// timeout, so this method is preferred to wait for a specified period of -// time. Once the kqueue indicates the process has exited, waitpid will reap -// the exited child. If the kqueue doesn't provide an exit event notification, -// before the timeout expires, or if the kqueue fails or misbehaves, the -// process will be mercilessly killed and reaped. -// -// A child process passed to this function may be in one of several states: -// running, terminated and not yet reaped, and (apparently, and unfortunately) -// terminated and already reaped. Normally, a process will at least have been -// asked to exit before this function is called, but this is not required. -// If a process is terminating and unreaped, there may be a window between the -// time that kqueue will no longer recognize it and when it becomes an actual -// zombie that a non-blocking (WNOHANG) waitpid can reap. This condition is -// detected when kqueue indicates that the process is not running and a -// non-blocking waitpid fails to reap the process but indicates that it is -// still running. In this event, a blocking attempt to reap the process -// collects the known-dying child, preventing zombies from congregating. -// -// In the event that the kqueue misbehaves entirely, as it might under a -// EMFILE condition ("too many open files", or out of file descriptors), this -// function will forcibly kill and reap the child without delay. This -// eliminates another potential zombie vector. (If you're out of file -// descriptors, you're probably deep into something else, but that doesn't -// mean that zombies be allowed to kick you while you're down.) -// -// The fact that this function seemingly can be called to wait on a child -// that's not only already terminated but already reaped is a bit of a -// problem: a reaped child's pid can be reclaimed and may refer to a distinct -// process in that case. The fact that this function can seemingly be called -// to wait on a process that's not even a child is also a problem: kqueue will -// work in that case, but waitpid won't, and killing a non-child might not be -// the best approach. -void WaitForChildToDie(pid_t child, int timeout) { - DCHECK(child > 0); - DCHECK(timeout > 0); - - // DON'T ADD ANY EARLY RETURNS TO THIS FUNCTION without ensuring that - // |child| has been reaped. Specifically, even if a kqueue, kevent, or other - // call fails, this function should fall back to the last resort of trying - // to kill and reap the process. Not observing this rule will resurrect - // zombies. - - int result; - - int kq = HANDLE_EINTR(kqueue()); - if (kq == -1) { - DPLOG(ERROR) << "kqueue()"; - } else { - file_util::ScopedFD auto_close_kq(&kq); - - struct kevent change = {0}; - EV_SET(&change, child, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); - result = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL)); - - if (result == -1) { - if (errno != ESRCH) { - DPLOG(ERROR) << "kevent (setup " << child << ")"; - } else { - // At this point, one of the following has occurred: - // 1. The process has died but has not yet been reaped. - // 2. The process has died and has already been reaped. - // 3. The process is in the process of dying. It's no longer - // kqueueable, but it may not be waitable yet either. Mark calls - // this case the "zombie death race". - - result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG)); - - if (result != 0) { - // A positive result indicates case 1. waitpid succeeded and reaped - // the child. A result of -1 indicates case 2. The child has already - // been reaped. In both of these cases, no further action is - // necessary. - return; - } - - // |result| is 0, indicating case 3. The process will be waitable in - // short order. Fall back out of the kqueue code to kill it (for good - // measure) and reap it. - } - } else { - // Keep track of the elapsed time to be able to restart kevent if it's - // interrupted. - TimeDelta remaining_delta = TimeDelta::FromSeconds(timeout); - TimeTicks deadline = TimeTicks::Now() + remaining_delta; - result = -1; - struct kevent event = {0}; - while (remaining_delta.InMilliseconds() > 0) { - const struct timespec remaining_timespec = remaining_delta.ToTimeSpec(); - result = kevent(kq, NULL, 0, &event, 1, &remaining_timespec); - if (result == -1 && errno == EINTR) { - remaining_delta = deadline - TimeTicks::Now(); - result = 0; - } else { - break; - } - } - - if (result == -1) { - DPLOG(ERROR) << "kevent (wait " << child << ")"; - } else if (result > 1) { - DLOG(ERROR) << "kevent (wait " << child << "): unexpected result " - << result; - } else if (result == 1) { - if ((event.fflags & NOTE_EXIT) && - (event.ident == static_cast<uintptr_t>(child))) { - // The process is dead or dying. This won't block for long, if at - // all. - BlockingReap(child); - return; - } else { - DLOG(ERROR) << "kevent (wait " << child - << "): unexpected event: fflags=" << event.fflags - << ", ident=" << event.ident; - } - } - } - } - - // The child is still alive, or is very freshly dead. Be sure by sending it - // a signal. This is safe even if it's freshly dead, because it will be a - // zombie (or on the way to zombiedom) and kill will return 0 even if the - // signal is not delivered to a live process. - result = kill(child, SIGKILL); - if (result == -1) { - DPLOG(ERROR) << "kill(" << child << ", SIGKILL)"; - } else { - // The child is definitely on the way out now. BlockingReap won't need to - // wait for long, if at all. - BlockingReap(child); - } -} - -} // namespace - -void EnsureProcessTerminated(ProcessHandle process) { - WaitForChildToDie(process, kWaitBeforeKillSeconds); -} - } // namespace base diff --git a/base/process_util_posix.cc b/base/process_util_posix.cc index 83afe44..e0a9169 100644 --- a/base/process_util_posix.cc +++ b/base/process_util_posix.cc @@ -74,67 +74,6 @@ void SetEnvironment(char** env) { #endif } -int WaitpidWithTimeout(ProcessHandle handle, int64 wait_milliseconds, - bool* success) { - // This POSIX version of this function only guarantees that we wait no less - // than |wait_milliseconds| for the process to exit. The child process may - // exit sometime before the timeout has ended but we may still block for up - // to 256 milliseconds after the fact. - // - // waitpid() has no direct support on POSIX for specifying a timeout, you can - // either ask it to block indefinitely or return immediately (WNOHANG). - // When a child process terminates a SIGCHLD signal is sent to the parent. - // Catching this signal would involve installing a signal handler which may - // affect other parts of the application and would be difficult to debug. - // - // Our strategy is to call waitpid() once up front to check if the process - // has already exited, otherwise to loop for wait_milliseconds, sleeping for - // at most 256 milliseconds each time using usleep() and then calling - // waitpid(). The amount of time we sleep starts out at 1 milliseconds, and - // we double it every 4 sleep cycles. - // - // usleep() is speced to exit if a signal is received for which a handler - // has been installed. This means that when a SIGCHLD is sent, it will exit - // depending on behavior external to this function. - // - // This function is used primarily for unit tests, if we want to use it in - // the application itself it would probably be best to examine other routes. - int status = -1; - pid_t ret_pid = HANDLE_EINTR(waitpid(handle, &status, WNOHANG)); - static const int64 kMaxSleepInMicroseconds = 1 << 18; // ~256 milliseconds. - int64 max_sleep_time_usecs = 1 << 10; // ~1 milliseconds. - int64 double_sleep_time = 0; - - // If the process hasn't exited yet, then sleep and try again. - TimeTicks wakeup_time = TimeTicks::Now() + - TimeDelta::FromMilliseconds(wait_milliseconds); - while (ret_pid == 0) { - TimeTicks now = TimeTicks::Now(); - if (now > wakeup_time) - break; - // Guaranteed to be non-negative! - int64 sleep_time_usecs = (wakeup_time - now).InMicroseconds(); - // Sleep for a bit while we wait for the process to finish. - if (sleep_time_usecs > max_sleep_time_usecs) - sleep_time_usecs = max_sleep_time_usecs; - - // usleep() will return 0 and set errno to EINTR on receipt of a signal - // such as SIGCHLD. - usleep(sleep_time_usecs); - ret_pid = HANDLE_EINTR(waitpid(handle, &status, WNOHANG)); - - if ((max_sleep_time_usecs < kMaxSleepInMicroseconds) && - (double_sleep_time++ % 4 == 0)) { - max_sleep_time_usecs *= 2; - } - } - - if (success) - *success = (ret_pid != -1); - - return status; -} - #if !defined(OS_LINUX) || \ (!defined(__i386__) && !defined(__x86_64__) && !defined(__arm__)) void ResetChildSignalHandlersToDefaults() { @@ -221,50 +160,6 @@ void ResetChildSignalHandlersToDefaults(void) { #endif // !defined(OS_LINUX) || // (!defined(__i386__) && !defined(__x86_64__) && !defined(__arm__)) -TerminationStatus GetTerminationStatusImpl(ProcessHandle handle, - bool can_block, - int* exit_code) { - int status = 0; - const pid_t result = HANDLE_EINTR(waitpid(handle, &status, - can_block ? 0 : WNOHANG)); - if (result == -1) { - DPLOG(ERROR) << "waitpid(" << handle << ")"; - if (exit_code) - *exit_code = 0; - return TERMINATION_STATUS_NORMAL_TERMINATION; - } else if (result == 0) { - // the child hasn't exited yet. - if (exit_code) - *exit_code = 0; - return TERMINATION_STATUS_STILL_RUNNING; - } - - if (exit_code) - *exit_code = status; - - if (WIFSIGNALED(status)) { - switch (WTERMSIG(status)) { - case SIGABRT: - case SIGBUS: - case SIGFPE: - case SIGILL: - case SIGSEGV: - return TERMINATION_STATUS_PROCESS_CRASHED; - case SIGINT: - case SIGKILL: - case SIGTERM: - return TERMINATION_STATUS_PROCESS_WAS_KILLED; - default: - break; - } - } - - if (WIFEXITED(status) && WEXITSTATUS(status) != 0) - return TERMINATION_STATUS_ABNORMAL_TERMINATION; - - return TERMINATION_STATUS_NORMAL_TERMINATION; -} - } // anonymous namespace ProcessId GetCurrentProcId() { @@ -305,68 +200,6 @@ ProcessId GetProcId(ProcessHandle process) { return process; } -// Attempts to kill the process identified by the given process -// entry structure. Ignores specified exit_code; posix can't force that. -// Returns true if this is successful, false otherwise. -bool KillProcess(ProcessHandle process_id, int exit_code, bool wait) { - DCHECK_GT(process_id, 1) << " tried to kill invalid process_id"; - if (process_id <= 1) - return false; - bool result = kill(process_id, SIGTERM) == 0; - if (result && wait) { - int tries = 60; - - if (RunningOnValgrind()) { - // Wait for some extra time when running under Valgrind since the child - // processes may take some time doing leak checking. - tries *= 2; - } - - unsigned sleep_ms = 4; - - // The process may not end immediately due to pending I/O - bool exited = false; - while (tries-- > 0) { - pid_t pid = HANDLE_EINTR(waitpid(process_id, NULL, WNOHANG)); - if (pid == process_id) { - exited = true; - break; - } - if (pid == -1) { - if (errno == ECHILD) { - // The wait may fail with ECHILD if another process also waited for - // the same pid, causing the process state to get cleaned up. - exited = true; - break; - } - DPLOG(ERROR) << "Error waiting for process " << process_id; - } - - usleep(sleep_ms * 1000); - const unsigned kMaxSleepMs = 1000; - if (sleep_ms < kMaxSleepMs) - sleep_ms *= 2; - } - - // If we're waiting and the child hasn't died by now, force it - // with a SIGKILL. - if (!exited) - result = kill(process_id, SIGKILL) == 0; - } - - if (!result) - DPLOG(ERROR) << "Unable to terminate process " << process_id; - - return result; -} - -bool KillProcessGroup(ProcessHandle process_group_id) { - bool result = kill(-1 * process_group_id, SIGKILL) == 0; - if (!result) - DPLOG(ERROR) << "Unable to terminate process group " << process_group_id; - return result; -} - // A class to handle auto-closing of DIR*'s. class ScopedDIRClose { public: @@ -776,174 +609,6 @@ void RaiseProcessToHighPriority() { // setpriority() or sched_getscheduler, but these all require extra rights. } -TerminationStatus GetTerminationStatus(ProcessHandle handle, int* exit_code) { - return GetTerminationStatusImpl(handle, false /* can_block */, exit_code); -} - -TerminationStatus WaitForTerminationStatus(ProcessHandle handle, - int* exit_code) { - return GetTerminationStatusImpl(handle, true /* can_block */, exit_code); -} - -bool WaitForExitCode(ProcessHandle handle, int* exit_code) { - int status; - if (HANDLE_EINTR(waitpid(handle, &status, 0)) == -1) { - NOTREACHED(); - return false; - } - - if (WIFEXITED(status)) { - *exit_code = WEXITSTATUS(status); - return true; - } - - // If it didn't exit cleanly, it must have been signaled. - DCHECK(WIFSIGNALED(status)); - return false; -} - -bool WaitForExitCodeWithTimeout(ProcessHandle handle, int* exit_code, - base::TimeDelta timeout) { - bool waitpid_success = false; - int status = WaitpidWithTimeout(handle, timeout.InMilliseconds(), - &waitpid_success); - if (status == -1) - return false; - if (!waitpid_success) - return false; - if (WIFSIGNALED(status)) { - *exit_code = -1; - return true; - } - if (WIFEXITED(status)) { - *exit_code = WEXITSTATUS(status); - return true; - } - return false; -} - -#if defined(OS_MACOSX) -// Using kqueue on Mac so that we can wait on non-child processes. -// We can't use kqueues on child processes because we need to reap -// our own children using wait. -static bool WaitForSingleNonChildProcess(ProcessHandle handle, - base::TimeDelta wait) { - DCHECK_GT(handle, 0); - DCHECK(wait.InMilliseconds() == base::kNoTimeout || wait > base::TimeDelta()); - - int kq = kqueue(); - if (kq == -1) { - DPLOG(ERROR) << "kqueue"; - return false; - } - file_util::ScopedFD kq_closer(&kq); - - struct kevent change = {0}; - EV_SET(&change, handle, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); - int result = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL)); - if (result == -1) { - if (errno == ESRCH) { - // If the process wasn't found, it must be dead. - return true; - } - - DPLOG(ERROR) << "kevent (setup " << handle << ")"; - return false; - } - - // Keep track of the elapsed time to be able to restart kevent if it's - // interrupted. - bool wait_forever = wait.InMilliseconds() == base::kNoTimeout; - base::TimeDelta remaining_delta; - base::TimeTicks deadline; - if (!wait_forever) { - remaining_delta = wait; - deadline = base::TimeTicks::Now() + remaining_delta; - } - - result = -1; - struct kevent event = {0}; - - while (wait_forever || remaining_delta > base::TimeDelta()) { - struct timespec remaining_timespec; - struct timespec* remaining_timespec_ptr; - if (wait_forever) { - remaining_timespec_ptr = NULL; - } else { - remaining_timespec = remaining_delta.ToTimeSpec(); - remaining_timespec_ptr = &remaining_timespec; - } - - result = kevent(kq, NULL, 0, &event, 1, remaining_timespec_ptr); - - if (result == -1 && errno == EINTR) { - if (!wait_forever) { - remaining_delta = deadline - base::TimeTicks::Now(); - } - result = 0; - } else { - break; - } - } - - if (result < 0) { - DPLOG(ERROR) << "kevent (wait " << handle << ")"; - return false; - } else if (result > 1) { - DLOG(ERROR) << "kevent (wait " << handle << "): unexpected result " - << result; - return false; - } else if (result == 0) { - // Timed out. - return false; - } - - DCHECK_EQ(result, 1); - - if (event.filter != EVFILT_PROC || - (event.fflags & NOTE_EXIT) == 0 || - event.ident != static_cast<uintptr_t>(handle)) { - DLOG(ERROR) << "kevent (wait " << handle - << "): unexpected event: filter=" << event.filter - << ", fflags=" << event.fflags - << ", ident=" << event.ident; - return false; - } - - return true; -} -#endif // OS_MACOSX - -bool WaitForSingleProcess(ProcessHandle handle, base::TimeDelta wait) { - ProcessHandle parent_pid = GetParentProcessId(handle); - ProcessHandle our_pid = Process::Current().handle(); - if (parent_pid != our_pid) { -#if defined(OS_MACOSX) - // On Mac we can wait on non child processes. - return WaitForSingleNonChildProcess(handle, wait); -#else - // Currently on Linux we can't handle non child processes. - NOTIMPLEMENTED(); -#endif // OS_MACOSX - } - - bool waitpid_success; - int status = -1; - if (wait.InMilliseconds() == base::kNoTimeout) { - waitpid_success = (HANDLE_EINTR(waitpid(handle, &status, 0)) != -1); - } else { - status = WaitpidWithTimeout( - handle, wait.InMilliseconds(), &waitpid_success); - } - - if (status != -1) { - DCHECK(waitpid_success); - return WIFEXITED(status); - } else { - return false; - } -} - // Return value used by GetAppOutputInternal to encapsulate the various exit // scenarios from the function. enum GetAppOutputInternalResult { @@ -1119,133 +784,4 @@ bool GetAppOutputWithExitCode(const CommandLine& cl, return result == EXECUTE_SUCCESS; } -bool WaitForProcessesToExit(const FilePath::StringType& executable_name, - base::TimeDelta wait, - const ProcessFilter* filter) { - bool result = false; - - // TODO(port): This is inefficient, but works if there are multiple procs. - // TODO(port): use waitpid to avoid leaving zombies around - - base::TimeTicks end_time = base::TimeTicks::Now() + wait; - do { - NamedProcessIterator iter(executable_name, filter); - if (!iter.NextProcessEntry()) { - result = true; - break; - } - base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); - } while ((end_time - base::TimeTicks::Now()) > base::TimeDelta()); - - return result; -} - -bool CleanupProcesses(const FilePath::StringType& executable_name, - base::TimeDelta wait, - int exit_code, - const ProcessFilter* filter) { - bool exited_cleanly = WaitForProcessesToExit(executable_name, wait, filter); - if (!exited_cleanly) - KillProcesses(executable_name, exit_code, filter); - return exited_cleanly; -} - -#if !defined(OS_MACOSX) - -namespace { - -// Return true if the given child is dead. This will also reap the process. -// Doesn't block. -static bool IsChildDead(pid_t child) { - const pid_t result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG)); - if (result == -1) { - DPLOG(ERROR) << "waitpid(" << child << ")"; - NOTREACHED(); - } else if (result > 0) { - // The child has died. - return true; - } - - return false; -} - -// A thread class which waits for the given child to exit and reaps it. -// If the child doesn't exit within a couple of seconds, kill it. -class BackgroundReaper : public PlatformThread::Delegate { - public: - BackgroundReaper(pid_t child, unsigned timeout) - : child_(child), - timeout_(timeout) { - } - - // Overridden from PlatformThread::Delegate: - virtual void ThreadMain() OVERRIDE { - WaitForChildToDie(); - delete this; - } - - void WaitForChildToDie() { - // Wait forever case. - if (timeout_ == 0) { - pid_t r = HANDLE_EINTR(waitpid(child_, NULL, 0)); - if (r != child_) { - DPLOG(ERROR) << "While waiting for " << child_ - << " to terminate, we got the following result: " << r; - } - return; - } - - // There's no good way to wait for a specific child to exit in a timed - // fashion. (No kqueue on Linux), so we just loop and sleep. - - // Wait for 2 * timeout_ 500 milliseconds intervals. - for (unsigned i = 0; i < 2 * timeout_; ++i) { - PlatformThread::Sleep(TimeDelta::FromMilliseconds(500)); - if (IsChildDead(child_)) - return; - } - - 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. - if (HANDLE_EINTR(waitpid(child_, NULL, 0)) < 0) - DPLOG(WARNING) << "waitpid"; - } else { - DLOG(ERROR) << "While waiting for " << child_ << " to terminate we" - << " failed to deliver a SIGKILL signal (" << errno << ")."; - } - } - - private: - const pid_t child_; - // Number of seconds to wait, if 0 then wait forever and do not attempt to - // kill |child_|. - const unsigned timeout_; - - DISALLOW_COPY_AND_ASSIGN(BackgroundReaper); -}; - -} // namespace - -void EnsureProcessTerminated(ProcessHandle process) { - // If the child is already dead, then there's nothing to do. - if (IsChildDead(process)) - return; - - const unsigned timeout = 2; // seconds - BackgroundReaper* reaper = new BackgroundReaper(process, timeout); - PlatformThread::CreateNonJoinable(0, reaper); -} - -void EnsureProcessGetsReaped(ProcessHandle process) { - // If the child is already dead, then there's nothing to do. - if (IsChildDead(process)) - return; - - BackgroundReaper* reaper = new BackgroundReaper(process, 0); - PlatformThread::CreateNonJoinable(0, reaper); -} - -#endif // !defined(OS_MACOSX) - } // namespace base diff --git a/base/process_util_win.cc b/base/process_util_win.cc index 2230105..691e1ae 100644 --- a/base/process_util_win.cc +++ b/base/process_util_win.cc @@ -33,15 +33,6 @@ namespace base { namespace { -// Exit codes with special meanings on Windows. -const DWORD kNormalTerminationExitCode = 0; -const DWORD kDebuggerInactiveExitCode = 0xC0000354; -const DWORD kKeyboardInterruptExitCode = 0xC000013A; -const DWORD kDebuggerTerminatedExitCode = 0x40010004; - -// Maximum amount of time (in milliseconds) to wait for the process to exit. -static const int kWaitInterval = 2000; - // This exit code is used by the Windows task manager when it kills a // process. It's value is obviously not that unique, and it's // surprising to me that the task manager uses this value, but it @@ -50,60 +41,6 @@ static const int kWaitInterval = 2000; // process goes away. const DWORD kProcessKilledExitCode = 1; -class TimerExpiredTask : public win::ObjectWatcher::Delegate { - public: - explicit TimerExpiredTask(ProcessHandle process); - ~TimerExpiredTask(); - - void TimedOut(); - - // MessageLoop::Watcher ----------------------------------------------------- - virtual void OnObjectSignaled(HANDLE object); - - private: - void KillProcess(); - - // The process that we are watching. - ProcessHandle process_; - - win::ObjectWatcher watcher_; - - DISALLOW_COPY_AND_ASSIGN(TimerExpiredTask); -}; - -TimerExpiredTask::TimerExpiredTask(ProcessHandle process) : process_(process) { - watcher_.StartWatching(process_, this); -} - -TimerExpiredTask::~TimerExpiredTask() { - TimedOut(); - DCHECK(!process_) << "Make sure to close the handle."; -} - -void TimerExpiredTask::TimedOut() { - if (process_) - KillProcess(); -} - -void TimerExpiredTask::OnObjectSignaled(HANDLE object) { - CloseHandle(process_); - process_ = NULL; -} - -void TimerExpiredTask::KillProcess() { - // Stop watching the process handle since we're killing it. - watcher_.StopWatching(); - - // OK, time to get frisky. We don't actually care when the process - // terminates. We just care that it eventually terminates, and that's what - // TerminateProcess should do for us. Don't check for the result code since - // it fails quite often. This should be investigated eventually. - base::KillProcess(process_, kProcessKilledExitCode, false); - - // Now, just cleanup as if the process exited normally. - OnObjectSignaled(process_); -} - } // namespace void RouteStdioToConsole() { @@ -379,22 +316,6 @@ bool SetJobObjectAsKillOnJobClose(HANDLE job_object) { sizeof(limit_info)); } -// Attempts to kill the process identified by the given process -// entry structure, giving it the specified exit code. -// Returns true if this is successful, false otherwise. -bool KillProcessById(ProcessId process_id, int exit_code, bool wait) { - HANDLE process = OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, - FALSE, // Don't inherit handle - process_id); - if (!process) { - DLOG_GETLASTERROR(ERROR) << "Unable to open process " << process_id; - return false; - } - bool ret = KillProcess(process, exit_code, wait); - CloseHandle(process); - return ret; -} - bool GetAppOutput(const CommandLine& cl, std::string* output) { HANDLE out_read = NULL; HANDLE out_write = NULL; @@ -465,150 +386,6 @@ bool GetAppOutput(const CommandLine& cl, std::string* output) { return true; } -bool KillProcess(ProcessHandle process, int exit_code, bool wait) { - bool result = (TerminateProcess(process, exit_code) != FALSE); - if (result && wait) { - // The process may not end immediately due to pending I/O - if (WAIT_OBJECT_0 != WaitForSingleObject(process, 60 * 1000)) - DLOG_GETLASTERROR(ERROR) << "Error waiting for process exit"; - } else if (!result) { - DLOG_GETLASTERROR(ERROR) << "Unable to terminate process"; - } - return result; -} - -TerminationStatus GetTerminationStatus(ProcessHandle handle, int* exit_code) { - DWORD tmp_exit_code = 0; - - if (!::GetExitCodeProcess(handle, &tmp_exit_code)) { - DLOG_GETLASTERROR(FATAL) << "GetExitCodeProcess() failed"; - if (exit_code) { - // This really is a random number. We haven't received any - // information about the exit code, presumably because this - // process doesn't have permission to get the exit code, or - // because of some other cause for GetExitCodeProcess to fail - // (MSDN docs don't give the possible failure error codes for - // this function, so it could be anything). But we don't want - // to leave exit_code uninitialized, since that could cause - // random interpretations of the exit code. So we assume it - // terminated "normally" in this case. - *exit_code = kNormalTerminationExitCode; - } - // Assume the child has exited normally if we can't get the exit - // code. - return TERMINATION_STATUS_NORMAL_TERMINATION; - } - if (tmp_exit_code == STILL_ACTIVE) { - DWORD wait_result = WaitForSingleObject(handle, 0); - if (wait_result == WAIT_TIMEOUT) { - if (exit_code) - *exit_code = wait_result; - return TERMINATION_STATUS_STILL_RUNNING; - } - - if (wait_result == WAIT_FAILED) { - DLOG_GETLASTERROR(ERROR) << "WaitForSingleObject() failed"; - } else { - DCHECK_EQ(WAIT_OBJECT_0, wait_result); - - // Strange, the process used 0x103 (STILL_ACTIVE) as exit code. - NOTREACHED(); - } - - return TERMINATION_STATUS_ABNORMAL_TERMINATION; - } - - if (exit_code) - *exit_code = tmp_exit_code; - - switch (tmp_exit_code) { - case kNormalTerminationExitCode: - return TERMINATION_STATUS_NORMAL_TERMINATION; - case kDebuggerInactiveExitCode: // STATUS_DEBUGGER_INACTIVE. - case kKeyboardInterruptExitCode: // Control-C/end session. - case kDebuggerTerminatedExitCode: // Debugger terminated process. - case kProcessKilledExitCode: // Task manager kill. - return TERMINATION_STATUS_PROCESS_WAS_KILLED; - default: - // All other exit codes indicate crashes. - return TERMINATION_STATUS_PROCESS_CRASHED; - } -} - -bool WaitForExitCode(ProcessHandle handle, int* exit_code) { - bool success = WaitForExitCodeWithTimeout( - handle, exit_code, base::TimeDelta::FromMilliseconds(INFINITE)); - CloseProcessHandle(handle); - return success; -} - -bool WaitForExitCodeWithTimeout(ProcessHandle handle, int* exit_code, - base::TimeDelta timeout) { - if (::WaitForSingleObject(handle, timeout.InMilliseconds()) != WAIT_OBJECT_0) - return false; - DWORD temp_code; // Don't clobber out-parameters in case of failure. - if (!::GetExitCodeProcess(handle, &temp_code)) - return false; - - *exit_code = temp_code; - return true; -} - -bool WaitForProcessesToExit(const FilePath::StringType& executable_name, - base::TimeDelta wait, - const ProcessFilter* filter) { - const ProcessEntry* entry; - bool result = true; - DWORD start_time = GetTickCount(); - - NamedProcessIterator iter(executable_name, filter); - while ((entry = iter.NextProcessEntry())) { - DWORD remaining_wait = std::max<int64>( - 0, wait.InMilliseconds() - (GetTickCount() - start_time)); - HANDLE process = OpenProcess(SYNCHRONIZE, - FALSE, - entry->th32ProcessID); - DWORD wait_result = WaitForSingleObject(process, remaining_wait); - CloseHandle(process); - result = result && (wait_result == WAIT_OBJECT_0); - } - - return result; -} - -bool WaitForSingleProcess(ProcessHandle handle, base::TimeDelta wait) { - int exit_code; - if (!WaitForExitCodeWithTimeout(handle, &exit_code, wait)) - return false; - return exit_code == 0; -} - -bool CleanupProcesses(const FilePath::StringType& executable_name, - base::TimeDelta wait, - int exit_code, - const ProcessFilter* filter) { - bool exited_cleanly = WaitForProcessesToExit(executable_name, wait, filter); - if (!exited_cleanly) - KillProcesses(executable_name, exit_code, filter); - return exited_cleanly; -} - -void EnsureProcessTerminated(ProcessHandle process) { - DCHECK(process != GetCurrentProcess()); - - // If already signaled, then we are done! - if (WaitForSingleObject(process, 0) == WAIT_OBJECT_0) { - CloseHandle(process); - return; - } - - MessageLoop::current()->PostDelayedTask( - FROM_HERE, - base::Bind(&TimerExpiredTask::TimedOut, - base::Owned(new TimerExpiredTask(process))), - base::TimeDelta::FromMilliseconds(kWaitInterval)); -} - void RaiseProcessToHighPriority() { SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); } |