| // Copyright 2012 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 "content/browser/child_process_launcher.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/files/file_util.h" |
| #include "base/i18n/icu_util.h" |
| #include "base/logging.h" |
| #include "base/process/launch.h" |
| #include "build/build_config.h" |
| #include "content/public/browser/child_process_launcher_utils.h" |
| #include "content/public/common/sandboxed_process_launcher_delegate.h" |
| #include "services/service_manager/embedder/result_codes.h" |
| |
| namespace content { |
| |
| using internal::ChildProcessLauncherHelper; |
| |
| ChildProcessLauncher::ChildProcessLauncher( |
| std::unique_ptr<SandboxedProcessLauncherDelegate> delegate, |
| std::unique_ptr<base::CommandLine> command_line, |
| int child_process_id, |
| Client* client, |
| mojo::OutgoingInvitation mojo_invitation, |
| const mojo::ProcessErrorCallback& process_error_callback, |
| bool terminate_on_shutdown) |
| : client_(client), |
| starting_(true), |
| start_time_(base::TimeTicks::Now()), |
| #if defined(ADDRESS_SANITIZER) || defined(LEAK_SANITIZER) || \ |
| defined(MEMORY_SANITIZER) || defined(THREAD_SANITIZER) || \ |
| defined(UNDEFINED_SANITIZER) || defined(CLANG_COVERAGE) |
| terminate_child_on_shutdown_(false), |
| #else |
| terminate_child_on_shutdown_(terminate_on_shutdown), |
| #endif |
| weak_factory_(this) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(BrowserThread::GetCurrentThreadIdentifier(&client_thread_id_)); |
| |
| helper_ = new ChildProcessLauncherHelper( |
| child_process_id, client_thread_id_, std::move(command_line), |
| std::move(delegate), weak_factory_.GetWeakPtr(), terminate_on_shutdown, |
| std::move(mojo_invitation), process_error_callback); |
| helper_->StartLaunchOnClientThread(); |
| } |
| |
| ChildProcessLauncher::~ChildProcessLauncher() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (process_.process.IsValid() && terminate_child_on_shutdown_) { |
| // Client has gone away, so just kill the process. |
| ChildProcessLauncherHelper::ForceNormalProcessTerminationAsync( |
| std::move(process_)); |
| } |
| } |
| |
| void ChildProcessLauncher::SetProcessPriority( |
| const ChildProcessLauncherPriority& priority) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| base::Process to_pass = process_.process.Duplicate(); |
| GetProcessLauncherTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &ChildProcessLauncherHelper::SetProcessPriorityOnLauncherThread, |
| helper_, std::move(to_pass), priority)); |
| } |
| |
| void ChildProcessLauncher::Notify( |
| ChildProcessLauncherHelper::Process process, |
| int error_code) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| starting_ = false; |
| process_ = std::move(process); |
| |
| if (process_.process.IsValid()) { |
| client_->OnProcessLaunched(); |
| } else { |
| termination_info_.status = base::TERMINATION_STATUS_LAUNCH_FAILED; |
| |
| // NOTE: May delete |this|. |
| client_->OnProcessLaunchFailed(error_code); |
| } |
| } |
| |
| bool ChildProcessLauncher::IsStarting() { |
| // TODO(crbug.com/469248): This fails in some tests. |
| // DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return starting_; |
| } |
| |
| const base::Process& ChildProcessLauncher::GetProcess() const { |
| // TODO(crbug.com/469248): This fails in some tests. |
| // DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return process_.process; |
| } |
| |
| ChildProcessTerminationInfo ChildProcessLauncher::GetChildTerminationInfo( |
| bool known_dead) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!process_.process.IsValid()) { |
| // Make sure to avoid using the default termination status if the process |
| // hasn't even started yet. |
| if (IsStarting()) { |
| termination_info_.status = base::TERMINATION_STATUS_STILL_RUNNING; |
| termination_info_.uptime = base::TimeTicks::Now() - start_time_; |
| DCHECK_LE(base::TimeDelta::FromSeconds(0), termination_info_.uptime); |
| } |
| |
| // Process doesn't exist, so return the cached termination info. |
| return termination_info_; |
| } |
| |
| termination_info_ = helper_->GetTerminationInfo(process_, known_dead); |
| termination_info_.uptime = base::TimeTicks::Now() - start_time_; |
| DCHECK_LE(base::TimeDelta::FromSeconds(0), termination_info_.uptime); |
| |
| // 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 |
| // GetTerminationInfo called waitpid with WNOHANG, it'll reap the process. |
| // However, if GetTerminationInfo didn't reap the child (because it was |
| // still running), we'll need to Terminate via ProcessWatcher. So we can't |
| // close the handle here. |
| if (termination_info_.status != base::TERMINATION_STATUS_STILL_RUNNING) { |
| process_.process.Exited(termination_info_.exit_code); |
| process_.process.Close(); |
| } |
| |
| return termination_info_; |
| } |
| |
| bool ChildProcessLauncher::Terminate(int exit_code) { |
| return IsStarting() ? false |
| : ChildProcessLauncherHelper::TerminateProcess( |
| GetProcess(), exit_code); |
| } |
| |
| // static |
| bool ChildProcessLauncher::TerminateProcess(const base::Process& process, |
| int exit_code) { |
| return ChildProcessLauncherHelper::TerminateProcess(process, exit_code); |
| } |
| |
| // static |
| void ChildProcessLauncher::SetRegisteredFilesForService( |
| const std::string& service_name, |
| catalog::RequiredFileMap required_files) { |
| ChildProcessLauncherHelper::SetRegisteredFilesForService( |
| service_name, std::move(required_files)); |
| } |
| |
| // static |
| void ChildProcessLauncher::ResetRegisteredFilesForTesting() { |
| ChildProcessLauncherHelper::ResetRegisteredFilesForTesting(); |
| } |
| |
| ChildProcessLauncher::Client* ChildProcessLauncher::ReplaceClientForTest( |
| Client* client) { |
| Client* ret = client_; |
| client_ = client; |
| return ret; |
| } |
| |
| bool ChildProcessLauncherPriority::operator==( |
| const ChildProcessLauncherPriority& other) const { |
| // |should_boost_for_pending_views| is temporary and constant for all |
| // ChildProcessLauncherPriority throughout a session (experiment driven). |
| DCHECK_EQ(should_boost_for_pending_views, |
| other.should_boost_for_pending_views); |
| return visible == other.visible && |
| has_media_stream == other.has_media_stream && |
| frame_depth == other.frame_depth && |
| intersects_viewport == other.intersects_viewport && |
| boost_for_pending_views == other.boost_for_pending_views |
| #if defined(OS_ANDROID) |
| && importance == other.importance |
| #endif |
| ; |
| } |
| |
| } // namespace content |