|  | // Copyright (c) 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 "chrome/browser/service_process/service_process_control.h" | 
|  |  | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/callback_helpers.h" | 
|  | #include "base/command_line.h" | 
|  | #include "base/files/file_path.h" | 
|  | #include "base/location.h" | 
|  | #include "base/memory/ref_counted.h" | 
|  | #include "base/metrics/histogram_base.h" | 
|  | #include "base/metrics/histogram_delta_serialization.h" | 
|  | #include "base/metrics/histogram_macros.h" | 
|  | #include "base/process/kill.h" | 
|  | #include "base/process/launch.h" | 
|  | #include "base/single_thread_task_runner.h" | 
|  | #include "base/stl_util.h" | 
|  | #include "base/task/thread_pool.h" | 
|  | #include "base/threading/thread.h" | 
|  | #include "base/threading/thread_restrictions.h" | 
|  | #include "base/threading/thread_task_runner_handle.h" | 
|  | #include "build/build_config.h" | 
|  | #include "chrome/browser/browser_process.h" | 
|  | #include "chrome/browser/upgrade_detector/upgrade_detector.h" | 
|  | #include "chrome/common/service_process_util.h" | 
|  | #include "content/public/browser/browser_task_traits.h" | 
|  | #include "content/public/browser/browser_thread.h" | 
|  | #include "content/public/browser/child_process_launcher_utils.h" | 
|  | #include "mojo/public/cpp/bindings/pending_receiver.h" | 
|  | #include "mojo/public/cpp/bindings/pending_remote.h" | 
|  | #include "mojo/public/cpp/platform/named_platform_channel.h" | 
|  | #include "mojo/public/cpp/system/isolated_connection.h" | 
|  |  | 
|  | using content::BrowserThread; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // The number of and initial delay between retry attempts when connecting to the | 
|  | // service process. These are applied with exponential backoff and are necessary | 
|  | // to avoid inherent raciness in how the service process listens for incoming | 
|  | // connections, particularly on Windows. | 
|  | const size_t kMaxConnectionAttempts = 10; | 
|  | constexpr base::TimeDelta kInitialConnectionRetryDelay = | 
|  | base::TimeDelta::FromMilliseconds(20); | 
|  |  | 
|  | void ConnectAsyncWithBackoff( | 
|  | mojo::PendingReceiver<service_manager::mojom::InterfaceProvider> | 
|  | interface_provider_receiver, | 
|  | mojo::NamedPlatformChannel::ServerName server_name, | 
|  | size_t num_retries_left, | 
|  | base::TimeDelta retry_delay, | 
|  | scoped_refptr<base::TaskRunner> response_task_runner, | 
|  | base::OnceCallback<void(std::unique_ptr<mojo::IsolatedConnection>)> | 
|  | response_callback) { | 
|  | mojo::PlatformChannelEndpoint endpoint = | 
|  | mojo::NamedPlatformChannel::ConnectToServer(server_name); | 
|  | if (!endpoint.is_valid()) { | 
|  | if (num_retries_left == 0) { | 
|  | response_task_runner->PostTask( | 
|  | FROM_HERE, base::BindOnce(std::move(response_callback), nullptr)); | 
|  | } else { | 
|  | base::ThreadPool::PostDelayedTask( | 
|  | FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, | 
|  | base::BindOnce( | 
|  | &ConnectAsyncWithBackoff, std::move(interface_provider_receiver), | 
|  | server_name, num_retries_left - 1, retry_delay * 2, | 
|  | std::move(response_task_runner), std::move(response_callback)), | 
|  | retry_delay); | 
|  | } | 
|  | } else { | 
|  | auto mojo_connection = std::make_unique<mojo::IsolatedConnection>(); | 
|  | mojo::FuseMessagePipes(mojo_connection->Connect(std::move(endpoint)), | 
|  | interface_provider_receiver.PassPipe()); | 
|  | response_task_runner->PostTask(FROM_HERE, | 
|  | base::BindOnce(std::move(response_callback), | 
|  | std::move(mojo_connection))); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // ServiceProcessControl implementation. | 
|  | ServiceProcessControl::ServiceProcessControl() | 
|  | : apply_changes_from_upgrade_observer_(false) {} | 
|  |  | 
|  | ServiceProcessControl::~ServiceProcessControl() = default; | 
|  |  | 
|  | void ServiceProcessControl::ConnectInternal() { | 
|  | // If the channel has already been established then we run the task | 
|  | // and return. | 
|  | if (service_process_) { | 
|  | RunConnectDoneTasks(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Actually going to connect. | 
|  | DVLOG(1) << "Connecting to Service Process IPC Server"; | 
|  |  | 
|  | mojo::PendingRemote<service_manager::mojom::InterfaceProvider> | 
|  | remote_interfaces; | 
|  | auto interface_provider_receiver = | 
|  | remote_interfaces.InitWithNewPipeAndPassReceiver(); | 
|  | SetMojoHandle(std::move(remote_interfaces)); | 
|  | base::ThreadPool::PostTask( | 
|  | FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, | 
|  | base::BindOnce( | 
|  | &ConnectAsyncWithBackoff, std::move(interface_provider_receiver), | 
|  | GetServiceProcessServerName(), kMaxConnectionAttempts, | 
|  | kInitialConnectionRetryDelay, base::ThreadTaskRunnerHandle::Get(), | 
|  | base::BindOnce(&ServiceProcessControl::OnPeerConnectionComplete, | 
|  | weak_factory_.GetWeakPtr()))); | 
|  | } | 
|  |  | 
|  | void ServiceProcessControl::OnPeerConnectionComplete( | 
|  | std::unique_ptr<mojo::IsolatedConnection> connection) { | 
|  | // Hold onto the connection object so the connection is kept alive. | 
|  | mojo_connection_ = std::move(connection); | 
|  | } | 
|  |  | 
|  | void ServiceProcessControl::SetMojoHandle( | 
|  | mojo::PendingRemote<service_manager::mojom::InterfaceProvider> handle) { | 
|  | remote_interfaces_.Close(); | 
|  | remote_interfaces_.Bind(std::move(handle)); | 
|  | remote_interfaces_.SetConnectionLostClosure(base::BindOnce( | 
|  | &ServiceProcessControl::OnChannelError, base::Unretained(this))); | 
|  |  | 
|  | // TODO(hclam): Handle error connecting to channel. | 
|  | remote_interfaces_.GetInterface( | 
|  | service_process_.BindNewPipeAndPassReceiver()); | 
|  | service_process_->Hello(base::BindOnce( | 
|  | &ServiceProcessControl::OnChannelConnected, base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | void ServiceProcessControl::RunConnectDoneTasks() { | 
|  | // The tasks executed here may add more tasks to the vector. So copy | 
|  | // them to the stack before executing them. This way recursion is | 
|  | // avoided. | 
|  | TaskList tasks; | 
|  |  | 
|  | if (IsConnected()) { | 
|  | tasks.swap(connect_success_tasks_); | 
|  | RunAllTasksHelper(&tasks); | 
|  | DCHECK(tasks.empty()); | 
|  | connect_failure_tasks_.clear(); | 
|  | } else { | 
|  | tasks.swap(connect_failure_tasks_); | 
|  | RunAllTasksHelper(&tasks); | 
|  | DCHECK(tasks.empty()); | 
|  | connect_success_tasks_.clear(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // static | 
|  | void ServiceProcessControl::RunAllTasksHelper(TaskList* task_list) { | 
|  | auto index = task_list->begin(); | 
|  | while (index != task_list->end()) { | 
|  | std::move(*index).Run(); | 
|  | index = task_list->erase(index); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool ServiceProcessControl::IsConnected() const { | 
|  | return !!service_process_; | 
|  | } | 
|  |  | 
|  | void ServiceProcessControl::Launch(base::OnceClosure success_task, | 
|  | base::OnceClosure failure_task) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | if (success_task) | 
|  | connect_success_tasks_.emplace_back(std::move(success_task)); | 
|  |  | 
|  | if (failure_task) | 
|  | connect_failure_tasks_.emplace_back(std::move(failure_task)); | 
|  |  | 
|  | // If we already in the process of launching, then we are done. | 
|  | if (launcher_.get()) | 
|  | return; | 
|  |  | 
|  | // If the service process is already running then connects to it. | 
|  | if (CheckServiceProcessReady()) { | 
|  | ConnectInternal(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents", SERVICE_EVENT_LAUNCH, | 
|  | SERVICE_EVENT_MAX); | 
|  |  | 
|  | std::unique_ptr<base::CommandLine> cmd_line( | 
|  | CreateServiceProcessCommandLine()); | 
|  | // And then start the process asynchronously. | 
|  | launcher_ = new Launcher(std::move(cmd_line)); | 
|  | launcher_->Run(base::BindOnce(&ServiceProcessControl::OnProcessLaunched, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | void ServiceProcessControl::Disconnect() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | mojo_connection_.reset(); | 
|  | remote_interfaces_.Close(); | 
|  | service_process_.reset(); | 
|  | UpgradeDetector::GetInstance()->RemoveObserver(this); | 
|  | } | 
|  |  | 
|  | void ServiceProcessControl::OnProcessLaunched() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | if (launcher_->launched()) { | 
|  | saved_pid_ = launcher_->saved_pid(); | 
|  | UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents", | 
|  | SERVICE_EVENT_LAUNCHED, SERVICE_EVENT_MAX); | 
|  | // After we have successfully created the service process we try to connect | 
|  | // to it. The launch task is transfered to a connect task. | 
|  | ConnectInternal(); | 
|  | } else { | 
|  | UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents", | 
|  | SERVICE_EVENT_LAUNCH_FAILED, SERVICE_EVENT_MAX); | 
|  | // If we don't have process handle that means launching the service process | 
|  | // has failed. | 
|  | RunConnectDoneTasks(); | 
|  | } | 
|  |  | 
|  | // We don't need the launcher anymore. | 
|  | launcher_.reset(); | 
|  | } | 
|  |  | 
|  | void ServiceProcessControl::OnUpgradeRecommended() { | 
|  | if (apply_changes_from_upgrade_observer_) | 
|  | service_process_->UpdateAvailable(); | 
|  | } | 
|  |  | 
|  | void ServiceProcessControl::OnChannelConnected() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents", | 
|  | SERVICE_EVENT_CHANNEL_CONNECTED, SERVICE_EVENT_MAX); | 
|  |  | 
|  | UpgradeDetector::GetInstance()->AddObserver(this); | 
|  |  | 
|  | // We just established a channel with the service process. Notify it if an | 
|  | // upgrade is available. | 
|  | if (UpgradeDetector::GetInstance()->notify_upgrade()) | 
|  | service_process_->UpdateAvailable(); | 
|  | else | 
|  | apply_changes_from_upgrade_observer_ = true; | 
|  |  | 
|  | RunConnectDoneTasks(); | 
|  | } | 
|  |  | 
|  | void ServiceProcessControl::OnChannelError() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents", | 
|  | SERVICE_EVENT_CHANNEL_ERROR, SERVICE_EVENT_MAX); | 
|  |  | 
|  | Disconnect(); | 
|  | RunConnectDoneTasks(); | 
|  | } | 
|  |  | 
|  | void ServiceProcessControl::OnHistograms( | 
|  | const std::vector<std::string>& pickled_histograms) { | 
|  | UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents", | 
|  | SERVICE_EVENT_HISTOGRAMS_REPLY, SERVICE_EVENT_MAX); | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | base::HistogramDeltaSerialization::DeserializeAndAddSamples( | 
|  | pickled_histograms); | 
|  | RunHistogramsCallback(); | 
|  | } | 
|  |  | 
|  | void ServiceProcessControl::RunHistogramsCallback() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | if (!histograms_callback_.is_null()) { | 
|  | std::move(histograms_callback_).Run(); | 
|  | } | 
|  | histograms_timeout_callback_.Cancel(); | 
|  | } | 
|  |  | 
|  | bool ServiceProcessControl::GetHistograms(base::OnceClosure histograms_callback, | 
|  | const base::TimeDelta& timeout) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | DCHECK(!histograms_callback.is_null()); | 
|  | histograms_callback_.Reset(); | 
|  |  | 
|  | // If the service process is already running then connect to it. | 
|  | if (!CheckServiceProcessReady()) | 
|  | return false; | 
|  | ConnectInternal(); | 
|  |  | 
|  | UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents", | 
|  | SERVICE_EVENT_HISTOGRAMS_REQUEST, | 
|  | SERVICE_EVENT_MAX); | 
|  |  | 
|  | if (!service_process_) | 
|  | return false; | 
|  |  | 
|  | service_process_->GetHistograms(base::BindOnce( | 
|  | &ServiceProcessControl::OnHistograms, base::Unretained(this))); | 
|  |  | 
|  | // Run timeout task to make sure |histograms_callback| is called. | 
|  | histograms_timeout_callback_.Reset(base::BindOnce( | 
|  | &ServiceProcessControl::RunHistogramsCallback, base::Unretained(this))); | 
|  | content::GetUIThreadTaskRunner({})->PostDelayedTask( | 
|  | FROM_HERE, histograms_timeout_callback_.callback(), timeout); | 
|  |  | 
|  | histograms_callback_ = std::move(histograms_callback); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ServiceProcessControl::Shutdown() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | if (!service_process_) | 
|  | return false; | 
|  | service_process_->ShutDown(); | 
|  | Disconnect(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // static | 
|  | ServiceProcessControl* ServiceProcessControl::GetInstance() { | 
|  | return base::Singleton<ServiceProcessControl>::get(); | 
|  | } | 
|  |  | 
|  | ServiceProcessControl::Launcher::Launcher( | 
|  | std::unique_ptr<base::CommandLine> cmd_line) | 
|  | : cmd_line_(std::move(cmd_line)), launched_(false), retry_count_(0) {} | 
|  |  | 
|  | // Execute the command line to start the process asynchronously. | 
|  | // After the command is executed, |task| is called with the process handle on | 
|  | // the UI thread. | 
|  | void ServiceProcessControl::Launcher::Run(base::OnceClosure task) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | notify_task_ = std::move(task); | 
|  | content::GetProcessLauncherTaskRunner()->PostTask( | 
|  | FROM_HERE, base::BindOnce(&Launcher::DoRun, this)); | 
|  | } | 
|  |  | 
|  | ServiceProcessControl::Launcher::~Launcher() { | 
|  | } | 
|  |  | 
|  |  | 
|  | void ServiceProcessControl::Launcher::Notify() { | 
|  | DCHECK(!notify_task_.is_null()); | 
|  | std::move(notify_task_).Run(); | 
|  | } | 
|  |  | 
|  | #if !defined(OS_MAC) | 
|  | void ServiceProcessControl::Launcher::DoDetectLaunched() { | 
|  | DCHECK(!notify_task_.is_null()); | 
|  |  | 
|  | const uint32_t kMaxLaunchDetectRetries = 10; | 
|  | launched_ = CheckServiceProcessReady(); | 
|  |  | 
|  | int exit_code = 0; | 
|  | if (launched_ || (retry_count_ >= kMaxLaunchDetectRetries) || | 
|  | process_.WaitForExitWithTimeout(base::TimeDelta(), &exit_code)) { | 
|  | process_.Close(); | 
|  | content::GetUIThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, base::BindOnce(&Launcher::Notify, this)); | 
|  | return; | 
|  | } | 
|  | retry_count_++; | 
|  |  | 
|  | // If the service process is not launched yet then check again in 2 seconds. | 
|  | const base::TimeDelta kDetectLaunchRetry = base::TimeDelta::FromSeconds(2); | 
|  | base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | 
|  | FROM_HERE, base::BindOnce(&Launcher::DoDetectLaunched, this), | 
|  | kDetectLaunchRetry); | 
|  | } | 
|  |  | 
|  | void ServiceProcessControl::Launcher::DoRun() { | 
|  | DCHECK(!notify_task_.is_null()); | 
|  |  | 
|  | base::LaunchOptions options; | 
|  | #if defined(OS_WIN) | 
|  | options.start_hidden = true; | 
|  | #endif | 
|  | process_ = base::LaunchProcess(*cmd_line_, options); | 
|  | if (process_.IsValid()) { | 
|  | saved_pid_ = process_.Pid(); | 
|  | content::GetIOThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, base::BindOnce(&Launcher::DoDetectLaunched, this)); | 
|  | } else { | 
|  | content::GetUIThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, base::BindOnce(&Launcher::Notify, this)); | 
|  | } | 
|  | } | 
|  | #endif  // !OS_MAC |