summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoragl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-04-28 01:37:23 +0000
committeragl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-04-28 01:37:23 +0000
commit140a7cd1d47292be7c5872c6019c1ab09af774e3 (patch)
tree5210806d7de816fa0025952e20bf6396b1c0d9fb
parenta7c2ff748541a96df53995216dc2a7c1209dca48 (diff)
downloadchromium_src-140a7cd1d47292be7c5872c6019c1ab09af774e3.zip
chromium_src-140a7cd1d47292be7c5872c6019c1ab09af774e3.tar.gz
chromium_src-140a7cd1d47292be7c5872c6019c1ab09af774e3.tar.bz2
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
-rw-r--r--base/process_posix.cc5
-rw-r--r--base/process_util.h10
-rw-r--r--base/process_util_posix.cc33
-rw-r--r--base/process_util_win.cc6
-rw-r--r--chrome/browser/browser_main.cc15
-rw-r--r--chrome/browser/renderer_host/browser_render_process_host.cc15
-rw-r--r--chrome/chrome.gyp4
-rw-r--r--chrome/common/child_process_host.cc2
-rw-r--r--chrome/common/common.vcproj2
-rw-r--r--chrome/common/process_watcher_posix.cc79
-rw-r--r--chrome/common/process_watcher_win.cc (renamed from chrome/common/process_watcher.cc)0
-rw-r--r--chrome/common/temp_scaffolding_stubs.cc5
-rw-r--r--chrome/test/ui/ui_test.cc13
13 files changed, 161 insertions, 28 deletions
diff --git a/base/process_posix.cc b/base/process_posix.cc
index ff8cf92..c9a7dfc 100644
--- a/base/process_posix.cc
+++ b/base/process_posix.cc
@@ -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 {
diff --git a/base/process_util.h b/base/process_util.h
index 9eb8690..a692870 100644
--- a/base/process_util.h
+++ b/base/process_util.h
@@ -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
diff --git a/base/process_util_posix.cc b/base/process_util_posix.cc
index 2e3fd55..efe7442 100644
--- a/base/process_util_posix.cc
+++ b/base/process_util_posix.cc
@@ -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:
diff --git a/base/process_util_win.cc b/base/process_util_win.cc
index 13fec9a..fc05664 100644
--- a/base/process_util_win.cc
+++ b/base/process_util_win.cc
@@ -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;
diff --git a/chrome/browser/browser_main.cc b/chrome/browser/browser_main.cc
index d687acb..a2a5530 100644
--- a/chrome/browser/browser_main.cc
+++ b/chrome/browser/browser_main.cc
@@ -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.
diff --git a/chrome/browser/renderer_host/browser_render_process_host.cc b/chrome/browser/renderer_host/browser_render_process_host.cc
index 0e25051..c7c01f8 100644
--- a/chrome/browser/renderer_host/browser_render_process_host.cc
+++ b/chrome/browser/renderer_host/browser_render_process_host.cc
@@ -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
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index 4566a65..d85abc6b 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -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',
],
}],
],
diff --git a/chrome/common/child_process_host.cc b/chrome/common/child_process_host.cc
index 7f934b0..e7a987f 100644
--- a/chrome/common/child_process_host.cc
+++ b/chrome/common/child_process_host.cc
@@ -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);
diff --git a/chrome/common/common.vcproj b/chrome/common/common.vcproj
index 7bf3fed..b09e53d 100644
--- a/chrome/common/common.vcproj
+++ b/chrome/common/common.vcproj
@@ -698,7 +698,7 @@
>
</File>
<File
- RelativePath=".\process_watcher.cc"
+ RelativePath=".\process_watcher_win.cc"
>
</File>
<File
diff --git a/chrome/common/process_watcher_posix.cc b/chrome/common/process_watcher_posix.cc
new file mode 100644
index 0000000..ef73521
--- /dev/null
+++ b/chrome/common/process_watcher_posix.cc
@@ -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);
+}
diff --git a/chrome/common/process_watcher.cc b/chrome/common/process_watcher_win.cc
index 1ee7edf..1ee7edf 100644
--- a/chrome/common/process_watcher.cc
+++ b/chrome/common/process_watcher_win.cc
diff --git a/chrome/common/temp_scaffolding_stubs.cc b/chrome/common/temp_scaffolding_stubs.cc
index f4adce1..742d693 100644
--- a/chrome/common/temp_scaffolding_stubs.cc
+++ b/chrome/common/temp_scaffolding_stubs.cc
@@ -225,11 +225,6 @@ LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info,
return NULL;
}
-void ProcessWatcher::EnsureProcessTerminated(int) {
- NOTIMPLEMENTED();
-}
-
-
//--------------------------------------------------------------------------
MemoryDetails::MemoryDetails() {
diff --git a/chrome/test/ui/ui_test.cc b/chrome/test/ui/ui_test.cc
index 0ee888b..687e102 100644
--- a/chrome/test/ui/ui_test.cc
+++ b/chrome/test/ui/ui_test.cc
@@ -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());