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:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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',
|
||||
],
|
||||
}],
|
||||
],
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -698,7 +698,7 @@
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\process_watcher.cc"
|
||||
RelativePath=".\process_watcher_win.cc"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -225,11 +225,6 @@ LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ProcessWatcher::EnsureProcessTerminated(int) {
|
||||
NOTIMPLEMENTED();
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
MemoryDetails::MemoryDetails() {
|
||||
|
||||
@@ -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());
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário