0
Files
src/content/utility/utility_thread_impl.cc
Ken Rockot c18bc4632f More consistent order for service process shutdown
Today, service processes self-terminate as soon as they detect peer
closure on their main service pipe. This is detected on the IO thread
and results in the immediate scheduling of a main-thread task to
terminate the process.

Meanwhile, service instances themselves also watch for peer closure on
the same pipe and are notified on whichever thread runs the service (IO
or main thread). This triggers immediate destruction of the service
instance.

Because there is no ordering guarantee between the two independent
signal handlers, the net result is that during clean shutdown of a
service process, the service instance's destructor may not run, or
may run after shutdown has already started. This can be problematic
if the service continues to operate and perform tasks that depend
on a now-partially-shut-down process environment like the task
scheduler.

As a separate but related issue, the pipe-watching logic has been
watching for peer closure when it should really be watching for an
unreadable state (i.e., peer closure AND empty inbound queue). This
means that service termination could race with messages still on the
pipe unless developers are careful to synchronize their browser-side
Remote's teardown against some kind of ack message from the service.

This change does a bunch of stuff all at once to solve these problems:

- Modifies ContentClient ServiceFactory APIs so that they populate a
  shared instance (two shared instances really, one for IO, one for
  main thread) rather than having embedders provide their own instance.
- Gives UtilityThreadImpl and its internal (IO-thread-bound)
  ServiceBinderImpl their own ServiceFactory instances with
  clearly-defined ownership and lifetime.
- Removes independent pipe watching logic from ServiceBinderImpl,
  instead having it track service instance lifetimes from both
  ServiceFactory instances.
- Modifies ServiceFactory's pipe watching logic to watch for an
  unreadable pipe rather than for peer closure.

The net result is that service processes which are cleanly shut down,
meaning they neither crashed nor were reaped during full browser
process shutdown, will always run their service's destructor before
initiating shutdown.

Bug: 1135957
Change-Id: I16adbd7c98b4eb4333a92cd338643d4d5a9f2d6f
Tbr: caseq@chromium.org
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2503346
Commit-Queue: Ken Rockot <rockot@google.com>
Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#821847}
2020-10-28 19:35:45 +00:00

257 lines
9.3 KiB
C++

// 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 "content/utility/utility_thread_impl.h"
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/debug/crash_logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/no_destructor.h"
#include "base/optional.h"
#include "base/sequenced_task_runner.h"
#include "base/trace_event/trace_log.h"
#include "build/build_config.h"
#include "content/child/child_process.h"
#include "content/public/utility/content_utility_client.h"
#include "content/utility/browser_exposed_utility_interfaces.h"
#include "content/utility/services.h"
#include "content/utility/utility_blink_platform_with_sandbox_support_impl.h"
#include "content/utility/utility_service_factory.h"
#include "ipc/ipc_sync_channel.h"
#include "mojo/public/cpp/bindings/binder_map.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/service_factory.h"
namespace content {
namespace {
class ServiceBinderImpl {
public:
explicit ServiceBinderImpl(
scoped_refptr<base::SequencedTaskRunner> main_thread_task_runner)
: main_thread_task_runner_(std::move(main_thread_task_runner)) {}
~ServiceBinderImpl() = default;
void BindServiceInterface(mojo::GenericPendingReceiver* receiver) {
// Set a crash key so utility process crash reports indicate which service
// was running in the process.
static auto* service_name_crash_key = base::debug::AllocateCrashKeyString(
"service-name", base::debug::CrashKeySize::Size32);
const std::string& service_name = receiver->interface_name().value();
base::debug::SetCrashKeyString(service_name_crash_key, service_name);
// Traces should also indicate the service name.
auto* trace_log = base::trace_event::TraceLog::GetInstance();
if (trace_log->IsProcessNameEmpty())
trace_log->set_process_name("Service: " + service_name);
// Ensure the ServiceFactory is (lazily) initialized.
if (!io_thread_services_) {
io_thread_services_ = std::make_unique<mojo::ServiceFactory>();
RegisterIOThreadServices(*io_thread_services_);
}
// Note that this is balanced by `termination_callback` below, which is
// always eventually run as long as the process does not begin shutting
// down beforehand.
++num_service_instances_;
auto termination_callback =
base::BindOnce(&ServiceBinderImpl::OnServiceTerminated,
weak_ptr_factory_.GetWeakPtr());
if (io_thread_services_->CanRunService(*receiver)) {
io_thread_services_->RunService(std::move(*receiver),
std::move(termination_callback));
return;
}
termination_callback =
base::BindOnce(base::IgnoreResult(&base::SequencedTaskRunner::PostTask),
base::ThreadTaskRunnerHandle::Get(), FROM_HERE,
std::move(termination_callback));
main_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ServiceBinderImpl::TryRunMainThreadService,
std::move(*receiver), std::move(termination_callback)));
}
static base::Optional<ServiceBinderImpl>& GetInstanceStorage() {
static base::NoDestructor<base::Optional<ServiceBinderImpl>> storage;
return *storage;
}
private:
static void TryRunMainThreadService(mojo::GenericPendingReceiver receiver,
base::OnceClosure termination_callback) {
// NOTE: UtilityThreadImpl is the only defined subclass of UtilityThread, so
// this cast is safe.
auto* thread = static_cast<UtilityThreadImpl*>(UtilityThread::Get());
thread->HandleServiceRequest(std::move(receiver),
std::move(termination_callback));
}
void OnServiceTerminated() {
if (--num_service_instances_ > 0)
return;
// There are no more services running in this process. Time to terminate.
main_thread_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&ServiceBinderImpl::ShutDownProcess));
}
static void ShutDownProcess() {
// Ensure that shutdown also tears down |this|. This is necessary to support
// multiple tests in the same test suite using out-of-process services via
// the InProcessUtilityThreadHelper.
GetInstanceStorage().reset();
UtilityThread::Get()->ReleaseProcess();
}
const scoped_refptr<base::SequencedTaskRunner> main_thread_task_runner_;
// Tracks the number of service instances currently running (or pending
// creation) in this process. When the number transitions from non-zero to
// zero, the process will self-terminate.
int num_service_instances_ = 0;
// Handles service requests for services that must run on the IO thread.
std::unique_ptr<mojo::ServiceFactory> io_thread_services_;
base::WeakPtrFactory<ServiceBinderImpl> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(ServiceBinderImpl);
};
ChildThreadImpl::Options::ServiceBinder GetServiceBinder() {
auto& storage = ServiceBinderImpl::GetInstanceStorage();
// NOTE: This may already be initialized from a previous call if we're in
// single-process mode.
if (!storage)
storage.emplace(base::ThreadTaskRunnerHandle::Get());
return base::BindRepeating(&ServiceBinderImpl::BindServiceInterface,
base::Unretained(&storage.value()));
}
} // namespace
UtilityThreadImpl::UtilityThreadImpl(base::RepeatingClosure quit_closure)
: ChildThreadImpl(std::move(quit_closure),
ChildThreadImpl::Options::Builder()
.ServiceBinder(GetServiceBinder())
.ExposesInterfacesToBrowser()
.Build()) {
Init();
}
UtilityThreadImpl::UtilityThreadImpl(const InProcessChildThreadParams& params)
: ChildThreadImpl(base::DoNothing(),
ChildThreadImpl::Options::Builder()
.InBrowserProcess(params)
.ServiceBinder(GetServiceBinder())
.ExposesInterfacesToBrowser()
.Build()) {
Init();
}
UtilityThreadImpl::~UtilityThreadImpl() = default;
void UtilityThreadImpl::Shutdown() {
ChildThreadImpl::Shutdown();
}
void UtilityThreadImpl::ReleaseProcess() {
if (!IsInBrowserProcess()) {
ChildProcess::current()->ReleaseProcess();
return;
}
// Close the channel to cause the UtilityProcessHost to be deleted. We need to
// take a different code path than the multi-process case because that case
// depends on the child process going away to close the channel, but that
// can't happen when we're in single process mode.
channel()->Close();
}
void UtilityThreadImpl::EnsureBlinkInitialized() {
EnsureBlinkInitializedInternal(/*sandbox_support=*/false);
}
#if defined(OS_POSIX) && !defined(OS_ANDROID)
void UtilityThreadImpl::EnsureBlinkInitializedWithSandboxSupport() {
EnsureBlinkInitializedInternal(/*sandbox_support=*/true);
}
#endif
void UtilityThreadImpl::HandleServiceRequest(
mojo::GenericPendingReceiver receiver,
base::OnceClosure termination_callback) {
if (!main_thread_services_) {
main_thread_services_ = std::make_unique<mojo::ServiceFactory>();
RegisterMainThreadServices(*main_thread_services_);
}
if (main_thread_services_->CanRunService(receiver)) {
main_thread_services_->RunService(std::move(receiver),
std::move(termination_callback));
return;
}
DLOG(ERROR) << "Cannot run unknown service: " << *receiver.interface_name();
std::move(termination_callback).Run();
}
void UtilityThreadImpl::EnsureBlinkInitializedInternal(bool sandbox_support) {
if (blink_platform_impl_)
return;
// We can only initialize Blink on one thread, and in single process mode
// we run the utility thread on a separate thread. This means that if any
// code needs Blink initialized in the utility process, they need to have
// another path to support single process mode.
if (IsInBrowserProcess())
return;
blink_platform_impl_ =
sandbox_support
? std::make_unique<UtilityBlinkPlatformWithSandboxSupportImpl>()
: std::make_unique<blink::Platform>();
blink::Platform::CreateMainThreadAndInitialize(blink_platform_impl_.get());
}
void UtilityThreadImpl::Init() {
ChildProcess::current()->AddRefProcess();
GetContentClient()->utility()->UtilityThreadStarted();
// NOTE: Do not add new interfaces directly within this method. Instead,
// modify the definition of |ExposeUtilityInterfacesToBrowser()| to ensure
// security review coverage.
mojo::BinderMap binders;
content::ExposeUtilityInterfacesToBrowser(&binders);
ExposeInterfacesToBrowser(std::move(binders));
service_factory_.reset(new UtilityServiceFactory);
}
bool UtilityThreadImpl::OnControlMessageReceived(const IPC::Message& msg) {
return GetContentClient()->utility()->OnMessageReceived(msg);
}
void UtilityThreadImpl::RunService(
const std::string& service_name,
mojo::PendingReceiver<service_manager::mojom::Service> receiver) {
DCHECK(service_factory_);
service_factory_->RunService(service_name, std::move(receiver));
}
} // namespace content