POSIX: don't spawn zombies.

http://codereview.chromium.org/93147

BUG=9401


git-svn-id: svn://svn.chromium.org/chrome/trunk/src@14705 0039d316-1c4b-4281-b951-d872f2087c98
Esse commit está contido em:
agl@chromium.org
2009-04-28 01:37:23 +00:00
commit dbaaf83317
13 arquivos alterados com 161 adições e 28 exclusões
+3 -2
Ver Arquivo
@@ -18,8 +18,9 @@ void Process::Terminate(int result_code) {
// result_code isn't supportable.
if (!process_)
return;
// Wait so we clean up the zombie
KillProcess(process_, result_code, true);
// We don't wait here. It's the responsibility of other code to reap the
// child.
KillProcess(process_, result_code, false);
}
bool Process::IsProcessBackgrounded() const {
+7 -3
Ver Arquivo
@@ -172,9 +172,13 @@ bool KillProcessById(ProcessId process_id, int exit_code, bool wait);
#endif
// Get the termination status (exit code) of the process and return true if the
// status indicates the process crashed. It is an error to call this if the
// process hasn't terminated yet.
bool DidProcessCrash(ProcessHandle handle);
// status indicates the process crashed. |child_exited| is set to true iff the
// child process has terminated. (|child_exited| may be NULL.)
//
// On Windows, it is an error to call this if the process hasn't terminated
// yet. On POSIX, |child_exited| is set correctly since we detect terminate in
// a different manner on POSIX.
bool DidProcessCrash(bool* child_exited, ProcessHandle handle);
// Waits for process to exit. In POSIX systems, if the process hasn't been
// signaled then puts the exit code in |exit_code|; otherwise it's considered
+23 -10
Ver Arquivo
@@ -56,23 +56,25 @@ ProcessId GetProcId(ProcessHandle 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) {
bool result = false;
bool result = kill(process_id, SIGTERM) == 0;
int status = kill(process_id, SIGTERM);
if (!status && wait) {
if (result && wait) {
int tries = 60;
// The process may not end immediately due to pending I/O
while (tries-- > 0) {
int pid = waitpid(process_id, &status, WNOHANG);
if (pid == process_id) {
result = true;
int pid = waitpid(process_id, NULL, WNOHANG);
if (pid == process_id)
break;
}
sleep(1);
}
result = kill(process_id, SIGKILL) == 0;
}
if (!result)
DLOG(ERROR) << "Unable to terminate process.";
return result;
}
@@ -141,13 +143,24 @@ void RaiseProcessToHighPriority() {
// setpriority() or sched_getscheduler, but these all require extra rights.
}
bool DidProcessCrash(ProcessHandle handle) {
bool DidProcessCrash(bool* child_exited, ProcessHandle handle) {
int status;
if (waitpid(handle, &status, WNOHANG)) {
// I feel like dancing!
const int result = waitpid(handle, &status, WNOHANG);
if (result == -1) {
LOG(ERROR) << "waitpid failed pid:" << handle << " errno:" << errno;
if (child_exited)
*child_exited = false;
return false;
} else if (result == 0) {
// the child hasn't exited yet.
if (child_exited)
*child_exited = false;
return false;
}
if (child_exited)
*child_exited = true;
if (WIFSIGNALED(status)) {
switch(WTERMSIG(status)) {
case SIGSEGV:
+5 -1
Ver Arquivo
@@ -270,8 +270,12 @@ bool KillProcess(ProcessHandle process, int exit_code, bool wait) {
return result;
}
bool DidProcessCrash(ProcessHandle handle) {
bool DidProcessCrash(bool* child_exited, ProcessHandle handle) {
DWORD exitcode = 0;
if (child_exited)
*child_exited = true; // On Windows it an error to call this function if
// the child hasn't already exited.
if (!::GetExitCodeProcess(handle, &exitcode)) {
NOTREACHED();
return false;
+15
Ver Arquivo
@@ -56,6 +56,7 @@
// TODO(port): get rid of this include. It's used just to provide declarations
// and stub definitions for classes we encouter during the porting effort.
#include "chrome/common/temp_scaffolding_stubs.h"
#include <signal.h>
#endif
// TODO(port): several win-only methods have been pulled out of this, but
@@ -180,6 +181,12 @@ void RunUIMessageLoop(BrowserProcess* browser_process) {
#endif
}
#if defined(OS_POSIX)
// See comment below, where sigaction is called.
void SIGCHLDHandler(int signal) {
}
#endif
} // namespace
// Main routine for running as the Browser process.
@@ -201,6 +208,14 @@ int BrowserMain(const MainFunctionParams& parameters) {
tracked_objects::AutoTracking tracking_objects;
#endif
#if defined(OS_POSIX)
// We need to accept SIGCHLD, even though our handler is a no-op because
// otherwise we cannot wait on children. (According to POSIX 2001.)
struct sigaction action = {0};
action.sa_handler = SIGCHLDHandler;
CHECK(sigaction(SIGCHLD, &action, NULL) == 0);
#endif
// Do platform-specific things (such as finishing initializing Cocoa)
// prior to instantiating the message loop. This could be turned into a
// broadcast notification.
@@ -668,13 +668,24 @@ void BrowserRenderProcessHost::OnChannelError() {
DCHECK(process_.handle());
DCHECK(channel_.get());
if (base::DidProcessCrash(process_.handle())) {
bool child_exited;
if (base::DidProcessCrash(&child_exited, process_.handle())) {
NotificationService::current()->Notify(
NotificationType::RENDERER_PROCESS_CRASHED,
Source<RenderProcessHost>(this), NotificationService::NoDetails());
}
process_.Close();
// POSIX: If the process crashed, then the kernel closed the socket for it
// and so the child has already died by the time we get here. Since
// DidProcessCrash called waitpid with WNOHANG, it'll reap the process.
// However, if DidProcessCrash didn't reap the child, we'll need to in
// ~BrowserRenderProcessHost via ProcessWatcher. So we can't close the handle
// here.
//
// This is moot on Windows where |child_exited| will always be true.
if (child_exited)
process_.Close();
channel_.reset();
// This process should detach all the listeners, causing the object to be
+2 -2
Ver Arquivo
@@ -250,7 +250,8 @@
'common/pref_names.h',
'common/pref_service.cc',
'common/pref_service.h',
'common/process_watcher.cc',
'common/process_watcher_posix.cc',
'common/process_watcher_win.cc',
'common/process_watcher.h',
'common/property_bag.cc',
'common/property_bag.h',
@@ -348,7 +349,6 @@
'common/classfactory.cc',
'common/drag_drop_types.cc',
'common/os_exchange_data.cc',
'common/process_watcher.cc',
],
}],
],
+1 -1
Ver Arquivo
@@ -117,7 +117,7 @@ void ChildProcessHost::OnWaitableEventSignaled(base::WaitableEvent *event) {
DCHECK(handle());
DCHECK_EQ(object, handle());
bool did_crash = base::DidProcessCrash(object);
bool did_crash = base::DidProcessCrash(NULL, object);
if (did_crash) {
// Report that this child process crashed.
Notify(NotificationType::CHILD_PROCESS_CRASHED);
+1 -1
Ver Arquivo
@@ -698,7 +698,7 @@
>
</File>
<File
RelativePath=".\process_watcher.cc"
RelativePath=".\process_watcher_win.cc"
>
</File>
<File
+79
Ver Arquivo
@@ -0,0 +1,79 @@
// 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/types.h>
#include <sys/wait.h>
#include "base/platform_thread.h"
// Return true if the given child is dead. This will also reap the process.
// Doesn't block.
static bool IsChildDead(pid_t child) {
const int result = waitpid(child, NULL, WNOHANG);
if (result == -1) {
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:
explicit BackgroundReaper(pid_t child)
: child_(child) {
}
void ThreadMain() {
WaitForChildToDie();
delete this;
}
void WaitForChildToDie() {
// 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.
// Waits 0.5 * 4 = 2 seconds.
for (unsigned i = 0; i < 4; ++i) {
PlatformThread::Sleep(500); // 0.5 seconds
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.
int result;
do {
result = waitpid(child_, NULL, 0);
} while (result == -1 && errno == EINTR);
} else {
LOG(ERROR) << "While waiting for " << child_ << " to terminate we"
<< " failed to deliver a SIGKILL signal (" << errno << ").";
}
}
private:
const pid_t child_;
DISALLOW_COPY_AND_ASSIGN(BackgroundReaper);
};
// static
void ProcessWatcher::EnsureProcessTerminated(base::ProcessHandle process) {
// If the child is already dead, then there's nothing to do
if (IsChildDead(process))
return;
BackgroundReaper* reaper = new BackgroundReaper(process);
PlatformThread::CreateNonJoinable(0, reaper);
}
-5
Ver Arquivo
@@ -225,11 +225,6 @@ LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info,
return NULL;
}
void ProcessWatcher::EnsureProcessTerminated(int) {
NOTIMPLEMENTED();
}
//--------------------------------------------------------------------------
MemoryDetails::MemoryDetails() {
+12 -1
Ver Arquivo
@@ -292,7 +292,18 @@ void UITest::LaunchBrowser(const CommandLine& arguments, bool clear_profile) {
if (!homepage_.empty())
command_line.AppendSwitchWithValue(switches::kHomePage,
homepage_);
PathService::Get(chrome::DIR_USER_DATA, &user_data_dir_);
#if defined(OS_POSIX)
const char* alternative_userdir = getenv("CHROME_UI_TESTS_USER_DATA_DIR");
#else
const FilePath::StringType::value_type* const alternative_userdir = NULL;
#endif
if (alternative_userdir) {
user_data_dir_ = FilePath(alternative_userdir);
} else {
PathService::Get(chrome::DIR_USER_DATA, &user_data_dir_);
}
if (!user_data_dir_.empty())
command_line.AppendSwitchWithValue(switches::kUserDataDir,
user_data_dir_.ToWStringHack());