0

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}
This commit is contained in:
Ken Rockot
2020-10-28 19:35:45 +00:00
committed by Commit Bot
parent 8173822970
commit c18bc4632f
23 changed files with 357 additions and 266 deletions

@ -95,11 +95,11 @@ void ChromeContentUtilityClient::UtilityThreadStarted() {
}
}
mojo::ServiceFactory*
ChromeContentUtilityClient::GetMainThreadServiceFactory() {
void ChromeContentUtilityClient::RegisterMainThreadServices(
mojo::ServiceFactory& services) {
if (utility_process_running_elevated_)
return ::GetElevatedMainThreadServiceFactory();
return ::GetMainThreadServiceFactory();
return ::RegisterElevatedMainThreadServices(services);
return ::RegisterMainThreadServices(services);
}
void ChromeContentUtilityClient::PostIOThreadCreated(
@ -109,8 +109,9 @@ void ChromeContentUtilityClient::PostIOThreadCreated(
metrics::CallStackProfileParams::IO_THREAD));
}
mojo::ServiceFactory* ChromeContentUtilityClient::GetIOThreadServiceFactory() {
return ::GetIOThreadServiceFactory();
void ChromeContentUtilityClient::RegisterIOThreadServices(
mojo::ServiceFactory& services) {
return ::RegisterIOThreadServices(services);
}
// static

@ -33,8 +33,8 @@ class ChromeContentUtilityClient : public content::ContentUtilityClient {
void RegisterNetworkBinders(
service_manager::BinderRegistry* registry) override;
void UtilityThreadStarted() override;
mojo::ServiceFactory* GetMainThreadServiceFactory() override;
mojo::ServiceFactory* GetIOThreadServiceFactory() override;
void RegisterMainThreadServices(mojo::ServiceFactory& services) override;
void RegisterIOThreadServices(mojo::ServiceFactory& services) override;
// See NetworkBinderProvider above.
static void SetNetworkBinderCreationCallback(

@ -252,91 +252,76 @@ auto RunAssistantAudioDecoder(
} // namespace
mojo::ServiceFactory* GetElevatedMainThreadServiceFactory() {
void RegisterElevatedMainThreadServices(mojo::ServiceFactory& services) {
// NOTE: This ServiceFactory is only used in utility processes which are run
// with elevated system privileges.
// clang-format off
static base::NoDestructor<mojo::ServiceFactory> factory {
#if BUILDFLAG(ENABLE_EXTENSIONS) && defined(OS_WIN)
// On non-Windows, this service runs in a regular utility process.
RunRemovableStorageWriter,
// On non-Windows, this service runs in a regular utility process.
services.Add(RunRemovableStorageWriter);
#endif
};
// clang-format on
return factory.get();
}
mojo::ServiceFactory* GetMainThreadServiceFactory() {
// clang-format off
static base::NoDestructor<mojo::ServiceFactory> factory {
RunFilePatcher,
RunUnzipper,
RunLanguageDetectionService,
RunQRCodeGeneratorService,
RunMachineLearningService,
void RegisterMainThreadServices(mojo::ServiceFactory& services) {
services.Add(RunFilePatcher);
services.Add(RunUnzipper);
services.Add(RunLanguageDetectionService);
services.Add(RunQRCodeGeneratorService);
services.Add(RunMachineLearningService);
#if !defined(OS_ANDROID)
RunProfileImporter,
RunMirroringService,
RunSpeechRecognitionService,
services.Add(RunProfileImporter);
services.Add(RunMirroringService);
services.Add(RunSpeechRecognitionService);
#endif
#if defined(OS_WIN)
RunQuarantineService,
RunWindowsUtility,
RunWindowsIconReader,
services.Add(RunQuarantineService);
services.Add(RunWindowsUtility);
services.Add(RunWindowsIconReader);
#endif // defined(OS_WIN)
#if BUILDFLAG(ENABLE_PRINTING) && defined(OS_CHROMEOS)
RunCupsIppParser,
services.Add(RunCupsIppParser);
#endif
#if BUILDFLAG(FULL_SAFE_BROWSING) || defined(OS_CHROMEOS)
RunFileUtil,
services.Add(RunFileUtil);
#endif
#if BUILDFLAG(ENABLE_EXTENSIONS) && !defined(OS_WIN)
// On Windows, this service runs in an elevated utility process.
RunRemovableStorageWriter,
// On Windows, this service runs in an elevated utility process.
services.Add(RunRemovableStorageWriter);
#endif
#if BUILDFLAG(ENABLE_EXTENSIONS) || defined(OS_ANDROID)
RunMediaParserFactory,
services.Add(RunMediaParserFactory);
#endif
#if BUILDFLAG(ENABLE_PRINT_PREVIEW) || \
(BUILDFLAG(ENABLE_PRINTING) && defined(OS_WIN))
RunPrintingService,
services.Add(RunPrintingService);
#endif
#if BUILDFLAG(ENABLE_PRINTING)
RunPrintCompositor,
services.Add(RunPrintCompositor);
#endif
#if BUILDFLAG(ENABLE_PAINT_PREVIEW)
RunPaintPreviewCompositor,
services.Add(RunPaintPreviewCompositor);
#endif
#if defined(OS_CHROMEOS)
RunImeService,
RunSharing,
RunTtsService,
services.Add(RunImeService);
services.Add(RunSharing);
services.Add(RunTtsService);
#if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
RunAssistantAudioDecoder,
services.Add(RunAssistantAudioDecoder);
#endif
#endif
};
// clang-format on
return factory.get();
}
mojo::ServiceFactory* GetIOThreadServiceFactory() {
// clang-format off
static base::NoDestructor<mojo::ServiceFactory> factory {
void RegisterIOThreadServices(mojo::ServiceFactory& services) {
#if !defined(OS_ANDROID)
RunProxyResolver,
#endif // !defined(OS_ANDROID)
};
// clang-format on
return factory.get();
services.Add(RunProxyResolver);
#endif
}

@ -12,8 +12,8 @@ class ServiceFactory;
// Helpers to run out-of-process services in a dedicated utility process. All
// out-of-process services will need to have their implementation hooked up in
// one of these helpers.
mojo::ServiceFactory* GetElevatedMainThreadServiceFactory();
mojo::ServiceFactory* GetMainThreadServiceFactory();
mojo::ServiceFactory* GetIOThreadServiceFactory();
void RegisterElevatedMainThreadServices(mojo::ServiceFactory& services);
void RegisterMainThreadServices(mojo::ServiceFactory& services);
void RegisterIOThreadServices(mojo::ServiceFactory& services);
#endif // CHROME_UTILITY_SERVICES_H_

@ -2,8 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string.h>
#include "base/memory/shared_memory_mapping.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/optional.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/test/bind_test_util.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
@ -88,6 +93,36 @@ IN_PROC_BROWSER_TEST_F(ServiceProcessHostBrowserTest, RemoteDisconnectQuits) {
observer.WaitForDeath();
}
IN_PROC_BROWSER_TEST_F(ServiceProcessHostBrowserTest, AllMessagesReceived) {
// Verifies that messages sent right before disconnection are always received
// and dispatched by the service before it self-terminates.
EchoServiceProcessObserver observer;
auto echo_service = ServiceProcessHost::Launch<echo::mojom::EchoService>();
const size_t kBufferSize = 256;
const std::string kMessages[] = {
"I thought we were having steamed clams.",
"D'oh, no! I said steamed hams. That's what I call hamburgers.",
"You call hamburgers, \"steamed hams?\"",
"Yes. It's a regional dialect."};
auto region = base::UnsafeSharedMemoryRegion::Create(kBufferSize);
base::WritableSharedMemoryMapping mapping = region.Map();
memset(mapping.memory(), 0, kBufferSize);
// Send several messages, since it helps to verify a lack of raciness between
// service-side message dispatch and service termination.
for (const auto& message : kMessages) {
ASSERT_LE(message.size(), kBufferSize);
echo_service->EchoStringToSharedMemory(message, region.Duplicate());
}
echo_service.reset();
observer.WaitForDeath();
const std::string& kLastMessage = kMessages[base::size(kMessages) - 1];
EXPECT_EQ(0,
memcmp(mapping.memory(), kLastMessage.data(), kLastMessage.size()));
}
IN_PROC_BROWSER_TEST_F(ServiceProcessHostBrowserTest, ObserveCrash) {
EchoServiceProcessObserver observer;
auto echo_service = ServiceProcessHost::Launch<echo::mojom::EchoService>();

@ -16,12 +16,4 @@ bool ContentUtilityClient::HandleServiceRequest(
return false;
}
mojo::ServiceFactory* ContentUtilityClient::GetIOThreadServiceFactory() {
return nullptr;
}
mojo::ServiceFactory* ContentUtilityClient::GetMainThreadServiceFactory() {
return nullptr;
}
} // namespace content

@ -63,18 +63,16 @@ class CONTENT_EXPORT ContentUtilityClient {
mojo::PendingReceiver<service_manager::mojom::Service> receiver);
// Allows the embedder to handle an incoming service interface request to run
// a service on the IO thread. Should return a ServiceFactory instance which
// lives at least as long as the IO thread, or nullptr.
// a service on the IO thread.
//
// Only called from the IO thread.
virtual mojo::ServiceFactory* GetIOThreadServiceFactory();
virtual void RegisterIOThreadServices(mojo::ServiceFactory& services) {}
// Allows the embedder to handle an incoming service interface request to run
// a service on the main thread. Should return a ServiceFactory instance which
// which effectively lives forever, or nullptr.
// a service on the main thread.
//
// Only called from the main thread.
virtual mojo::ServiceFactory* GetMainThreadServiceFactory();
virtual void RegisterMainThreadServices(mojo::ServiceFactory& services) {}
virtual void RegisterNetworkBinders(
service_manager::BinderRegistry* registry) {}

@ -172,11 +172,9 @@ bool ShellContentUtilityClient::HandleServiceRequest(
return false;
}
mojo::ServiceFactory* ShellContentUtilityClient::GetIOThreadServiceFactory() {
static base::NoDestructor<mojo::ServiceFactory> factory{
RunEchoService,
};
return factory.get();
void ShellContentUtilityClient::RegisterIOThreadServices(
mojo::ServiceFactory& services) {
services.Add(RunEchoService);
}
void ShellContentUtilityClient::RegisterNetworkBinders(

@ -23,7 +23,7 @@ class ShellContentUtilityClient : public ContentUtilityClient {
bool HandleServiceRequest(
const std::string& service_name,
mojo::PendingReceiver<service_manager::mojom::Service> receiver) override;
mojo::ServiceFactory* GetIOThreadServiceFactory() override;
void RegisterIOThreadServices(mojo::ServiceFactory& services) override;
void RegisterNetworkBinders(
service_manager::BinderRegistry* registry) override;

@ -206,69 +206,35 @@ auto RunXrDeviceService(
}
#endif
mojo::ServiceFactory& GetIOThreadServiceFactory() {
static base::NoDestructor<mojo::ServiceFactory> factory{
// The network service runs on the IO thread because it needs a message
// loop of type IO that can get notified when pipes have data.
RunNetworkService,
};
return *factory;
}
mojo::ServiceFactory& GetMainThreadServiceFactory() {
// clang-format off
static base::NoDestructor<mojo::ServiceFactory> factory{
RunAudio,
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
RunCdmService,
#endif
RunDataDecoder,
RunStorageService,
RunTracing,
RunVideoCapture,
#if BUILDFLAG(ENABLE_VR) && !defined(OS_ANDROID)
RunXrDeviceService,
#endif
};
// clang-format on
return *factory;
}
} // namespace
void HandleServiceRequestOnIOThread(
mojo::GenericPendingReceiver receiver,
base::SequencedTaskRunner* main_thread_task_runner) {
if (GetIOThreadServiceFactory().MaybeRunService(&receiver))
return;
void RegisterIOThreadServices(mojo::ServiceFactory& services) {
// The network service runs on the IO thread because it needs a message
// loop of type IO that can get notified when pipes have data.
services.Add(RunNetworkService);
// If the request was handled already, we should not reach this point.
DCHECK(receiver.is_valid());
auto* embedder_factory =
GetContentClient()->utility()->GetIOThreadServiceFactory();
if (embedder_factory && embedder_factory->MaybeRunService(&receiver))
return;
DCHECK(receiver.is_valid());
main_thread_task_runner->PostTask(
FROM_HERE,
base::BindOnce(&HandleServiceRequestOnMainThread, std::move(receiver)));
// Add new IO-thread services above this line.
GetContentClient()->utility()->RegisterIOThreadServices(services);
}
void HandleServiceRequestOnMainThread(mojo::GenericPendingReceiver receiver) {
if (GetMainThreadServiceFactory().MaybeRunService(&receiver))
return;
void RegisterMainThreadServices(mojo::ServiceFactory& services) {
services.Add(RunAudio);
// If the request was handled already, we should not reach this point.
DCHECK(receiver.is_valid());
auto* embedder_factory =
GetContentClient()->utility()->GetMainThreadServiceFactory();
if (embedder_factory && embedder_factory->MaybeRunService(&receiver))
return;
services.Add(RunDataDecoder);
services.Add(RunStorageService);
services.Add(RunTracing);
services.Add(RunVideoCapture);
DCHECK(receiver.is_valid());
DLOG(ERROR) << "Unhandled out-of-process service request for "
<< receiver.interface_name().value();
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
services.Add(RunCdmService);
#endif
#if BUILDFLAG(ENABLE_VR) && !defined(OS_ANDROID)
services.Add(RunXrDeviceService);
#endif
// Add new main-thread services above this line.
GetContentClient()->utility()->RegisterMainThreadServices(services);
}
} // namespace content

@ -5,17 +5,14 @@
#ifndef CONTENT_UTILITY_SERVICES_H_
#define CONTENT_UTILITY_SERVICES_H_
#include "base/memory/scoped_refptr.h"
#include "base/sequenced_task_runner.h"
#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
namespace mojo {
class ServiceFactory;
}
namespace content {
void HandleServiceRequestOnIOThread(
mojo::GenericPendingReceiver receiver,
base::SequencedTaskRunner* main_thread_task_runner);
void HandleServiceRequestOnMainThread(mojo::GenericPendingReceiver receiver);
void RegisterIOThreadServices(mojo::ServiceFactory& services);
void RegisterMainThreadServices(mojo::ServiceFactory& services);
} // namespace content

@ -13,6 +13,7 @@
#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"
@ -27,7 +28,7 @@
#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/system/simple_watcher.h"
#include "mojo/public/cpp/bindings/service_factory.h"
namespace content {
@ -53,18 +54,34 @@ class ServiceBinderImpl {
if (trace_log->IsProcessNameEmpty())
trace_log->set_process_name("Service: " + service_name);
// We watch for and terminate on PEER_CLOSED, but we also terminate if the
// watcher is cancelled (meaning the local endpoint was closed rather than
// the peer). Hence any breakage of the service pipe leads to termination.
auto watcher = std::make_unique<mojo::SimpleWatcher>(
FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC);
watcher->Watch(receiver->pipe(), MOJO_HANDLE_SIGNAL_PEER_CLOSED,
MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
base::BindRepeating(&ServiceBinderImpl::OnServicePipeClosed,
base::Unretained(this), watcher.get()));
service_pipe_watchers_.insert(std::move(watcher));
HandleServiceRequestOnIOThread(std::move(*receiver),
main_thread_task_runner_.get());
// 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() {
@ -73,21 +90,22 @@ class ServiceBinderImpl {
}
private:
void OnServicePipeClosed(mojo::SimpleWatcher* which,
MojoResult result,
const mojo::HandleSignalsState& state) {
// NOTE: It doesn't matter whether this was peer closure or local closure,
// and those are the only two ways this method can be invoked.
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));
}
auto it = service_pipe_watchers_.find(which);
DCHECK(it != service_pipe_watchers_.end());
service_pipe_watchers_.erase(it);
void OnServiceTerminated() {
if (--num_service_instances_ > 0)
return;
// No more services running in this process.
if (service_pipe_watchers_.empty()) {
main_thread_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&ServiceBinderImpl::ShutDownProcess));
}
// 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() {
@ -100,12 +118,15 @@ class ServiceBinderImpl {
const scoped_refptr<base::SequencedTaskRunner> main_thread_task_runner_;
// These trap signals on any (unowned) primordial service pipes. We don't
// actually care about the signals so these never get armed. We only watch for
// cancellation, because that means the service's primordial pipe handle was
// closed locally and we treat that as the service calling it quits.
std::set<std::unique_ptr<mojo::SimpleWatcher>, base::UniquePtrComparator>
service_pipe_watchers_;
// 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);
};
@ -170,6 +191,24 @@ void UtilityThreadImpl::EnsureBlinkInitializedWithSandboxSupport() {
}
#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;

@ -7,13 +7,19 @@
#include <memory>
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "build/build_config.h"
#include "content/child/child_thread_impl.h"
#include "content/public/utility/utility_thread.h"
#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
#include "third_party/blink/public/platform/platform.h"
namespace mojo {
class ServiceFactory;
}
namespace content {
class UtilityServiceFactory;
@ -34,6 +40,17 @@ class UtilityThreadImpl : public UtilityThread,
void EnsureBlinkInitializedWithSandboxSupport() override;
#endif
// Handles an incoming service interface receiver from a browser-side
// ServiceProcessHost. This is called only if `receiver` didn't first match
// any registered IO-thread service handlers in this process. If successful,
// `termination_callback` will eventually be invoked when the new service
// instance terminates.
//
// If there is no matching service, `receiver` is discarded and
// `termination_callback` is invoked immediately.
void HandleServiceRequest(mojo::GenericPendingReceiver receiver,
base::OnceClosure termination_callback);
private:
void EnsureBlinkInitializedInternal(bool sandbox_support);
void Init();
@ -47,9 +64,16 @@ class UtilityThreadImpl : public UtilityThread,
// blink::Platform implementation if needed.
std::unique_ptr<blink::Platform> blink_platform_impl_;
// Helper to handle incoming RunService calls.
// Helper to handle incoming RunService calls. Note that this is deprecated
// and only remains in support of some embedders which haven't migrated away
// from Service Manager-based services yet.
std::unique_ptr<UtilityServiceFactory> service_factory_;
// The ServiceFactory used to handle incoming service requests from a
// browser-side ServiceProcessHost. Any service registered here will run on
// the main thread of its service process.
std::unique_ptr<mojo::ServiceFactory> main_thread_services_;
DISALLOW_COPY_AND_ASSIGN(UtilityThreadImpl);
};

@ -48,14 +48,11 @@ HeadlessContentUtilityClient::HeadlessContentUtilityClient(
HeadlessContentUtilityClient::~HeadlessContentUtilityClient() = default;
mojo::ServiceFactory*
HeadlessContentUtilityClient::GetMainThreadServiceFactory() {
static base::NoDestructor<mojo::ServiceFactory> factory {
void HeadlessContentUtilityClient::RegisterMainThreadServices(
mojo::ServiceFactory& services) {
#if BUILDFLAG(ENABLE_PRINTING)
RunPrintCompositor,
services.Add(RunPrintCompositor);
#endif
};
return factory.get();
}
void HeadlessContentUtilityClient::RegisterNetworkBinders(

@ -26,7 +26,7 @@ class HEADLESS_EXPORT HeadlessContentUtilityClient
~HeadlessContentUtilityClient() override;
// content::ContentUtilityClient:
mojo::ServiceFactory* GetMainThreadServiceFactory() override;
void RegisterMainThreadServices(mojo::ServiceFactory& services) override;
void RegisterNetworkBinders(
service_manager::BinderRegistry* registry) override;

@ -5,33 +5,44 @@
#include "mojo/public/cpp/bindings/service_factory.h"
#include "base/bind.h"
#include "base/stl_util.h"
namespace mojo {
ServiceFactory::ServiceFactory() = default;
ServiceFactory::~ServiceFactory() = default;
bool ServiceFactory::MaybeRunService(mojo::GenericPendingReceiver* receiver) {
DCHECK(receiver->is_valid());
bool ServiceFactory::CanRunService(
const GenericPendingReceiver& receiver) const {
DCHECK(receiver.is_valid());
return base::Contains(constructors_, *receiver.interface_name());
}
bool ServiceFactory::RunService(GenericPendingReceiver receiver,
base::OnceClosure termination_callback) {
DCHECK(receiver.is_valid());
// We grab a weak handle to the receiver's message pipe first. If any function
// accepts the receiver, we will tie its returned object's lifetime to the
// connection state of that pipe.
MessagePipeHandle pipe = receiver->pipe();
MessagePipeHandle pipe = receiver.pipe();
for (const auto& callback : callbacks_) {
if (auto instance = callback.Run(receiver)) {
DCHECK(!receiver->is_valid());
instance->WatchPipe(
pipe, base::BindOnce(&ServiceFactory::OnInstanceDisconnected,
weak_ptr_factory_.GetWeakPtr(), instance.get()));
instances_.insert(std::move(instance));
return true;
}
auto it = constructors_.find(*receiver.interface_name());
if (it == constructors_.end())
return false;
DCHECK(receiver->is_valid());
auto instance = it->second.Run(std::move(receiver));
auto disconnect_callback =
base::BindOnce(&ServiceFactory::OnInstanceDisconnected,
weak_ptr_factory_.GetWeakPtr(), instance.get());
if (termination_callback) {
disconnect_callback =
std::move(disconnect_callback).Then(std::move(termination_callback));
}
return false;
instance->WatchPipe(pipe, std::move(disconnect_callback));
instances_.insert(std::move(instance));
return true;
}
void ServiceFactory::OnInstanceDisconnected(InstanceHolderBase* instance) {
@ -48,21 +59,26 @@ void ServiceFactory::InstanceHolderBase::WatchPipe(
base::OnceClosure disconnect_callback) {
DCHECK(!disconnect_callback_);
disconnect_callback_ = std::move(disconnect_callback);
watcher_.Watch(pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
watcher_.Watch(pipe, MOJO_HANDLE_SIGNAL_READABLE,
MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
base::BindRepeating(&InstanceHolderBase::OnDisconnect,
base::BindRepeating(&InstanceHolderBase::OnPipeSignaled,
base::Unretained(this)));
}
void ServiceFactory::InstanceHolderBase::OnDisconnect(
void ServiceFactory::InstanceHolderBase::OnPipeSignaled(
MojoResult result,
const HandleSignalsState& state) {
// It doesn't matter what the parameters are, since the only way the watcher
// can signal is if the peer was closed or the local pipe handle was closed.
// The callback always destroys |this| when run, so there's also no chance of
// this method running more than once.
DCHECK(disconnect_callback_);
std::move(disconnect_callback_).Run();
// We only care about the two conditions below. FAILED_PRECONDITION implies
// that the peer was closed and all its sent messages have been read (and
// dispatched) locally, while CANCELLED implies that the service pipe was
// closed locally. In both cases, we run the callback which will delete
// `this` and, ultimately, the service instance itself.
if (result == MOJO_RESULT_FAILED_PRECONDITION ||
result == MOJO_RESULT_CANCELLED) {
watcher_.Cancel();
DCHECK(disconnect_callback_);
std::move(disconnect_callback_).Run();
}
}
} // namespace mojo

@ -5,6 +5,7 @@
#ifndef MOJO_PUBLIC_CPP_BINDINGS_SERVICE_FACTORY_H_
#define MOJO_PUBLIC_CPP_BINDINGS_SERVICE_FACTORY_H_
#include <map>
#include <memory>
#include "base/bind.h"
@ -35,11 +36,11 @@ struct ServiceFactoryTraits;
// where |T| is any type (generally an implementation of |Interface|), and
// |Interface| is a mojom interface.
//
// Any time |MaybeRunService()| is called on the ServiceFactory, it will match
// the GenericPendingReceiver argument's interface type against the list of
// factories it has available, and if it finds a match it will run that function
// and retain ownership of the returned object until the corresponding receiver
// is disconnected.
// Any time |RunService()| is called on the ServiceFactory, it will match the
// GenericPendingReceiver argument's interface type against the list of
// factories it has available and run the corresponding function, retaining
// ownership of the returned object until the corresponding receiver is
// disconnected.
//
// Typical usage might look something like:
//
@ -51,31 +52,54 @@ struct ServiceFactoryTraits;
// return std::make_unique<bar::BarImpl>(std::move(receiver));
// }
//
// void HandleServiceRequest(mojo::GenericPendingReceiver receiver) {
// static base::NoDestructor<mojo::ServiceFactory> factory{
// RunFooService,
// RunBarService,
// };
// void RegisterServices(mojo::ServiceFactory& services) {
// services.Add(RunFooService);
// services.Add(RunBarService);
// }
//
// if (!factory->MaybeRunService(&receiver)) {
// // The receiver was for neither the Foo nor Bar service. Sad!
// LOG(ERROR) << "Unknown service: " << *receiver.interface_name();
// void HandleServiceRequest(const mojo::ServiceFactory& factory,
// mojo::GenericPendingReceiver receiver) {
// if (factory.CanRunService(receiver)) {
// factory.RunService(std::move(receiver), base::NullCallback());
// return;
// }
//
// // The receiver was for neither the Foo nor Bar service. Sad!
// LOG(ERROR) << "Unknown service: " << *receiver.interface_name();
// }
//
class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) ServiceFactory {
public:
template <typename... Funcs>
explicit ServiceFactory(Funcs... fns)
: callbacks_({base::BindRepeating(&RunFunction<Funcs>, fns)...}) {}
ServiceFactory();
~ServiceFactory();
// Attempts to run a service supported by this factory.
// Adds a new service to the factory. The argument may be any function that
// accepts a single PendingReceiver<T> and returns a unique_ptr<T>, where T is
// a service interface (that is, a generated mojom interface class
// corresponding to some service's main interface.)
template <typename Func>
void Add(Func func) {
using Interface = typename internal::ServiceFactoryTraits<Func>::Interface;
constructors_[Interface::Name_] =
base::BindRepeating(&RunConstructor<Func>, func);
}
// If `receiver` is references an interface matching a service known to this
// factory, this returns true. Otherwise it returns false. `receiver` MUST be
// valid.
bool CanRunService(const GenericPendingReceiver& receiver) const;
// Consumes `receiver` and binds it to a new instance of the corresponding
// service, constructed using the service's registered function within this
// factory.
//
// Returns |true| and consumes |*receiver| if it is a suitable match for some
// function known by the factory; otherwise returns |false| and leaves
// |*receiver| intact.
bool MaybeRunService(GenericPendingReceiver* receiver);
// `termination_callback`, if not null, will be invoked on the calling
// TaskRunner whenever the new service instance is eventually destroyed.
//
// If the service represented by `receiver` is not known to this factory, it
// is discarded and `termination_callback` is never run.
bool RunService(GenericPendingReceiver receiver,
base::OnceClosure termination_callback);
private:
class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) InstanceHolderBase {
@ -87,7 +111,7 @@ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) ServiceFactory {
base::OnceClosure disconnect_callback);
private:
void OnDisconnect(MojoResult result, const HandleSignalsState& state);
void OnPipeSignaled(MojoResult result, const HandleSignalsState& state);
SimpleWatcher watcher_;
base::OnceClosure disconnect_callback_;
@ -109,23 +133,20 @@ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) ServiceFactory {
};
template <typename Func>
static std::unique_ptr<InstanceHolderBase> RunFunction(
static std::unique_ptr<InstanceHolderBase> RunConstructor(
Func fn,
GenericPendingReceiver* receiver) {
GenericPendingReceiver receiver) {
using Interface = typename internal::ServiceFactoryTraits<Func>::Interface;
if (auto typed_receiver = receiver->As<Interface>()) {
return std::make_unique<InstanceHolder<Interface>>(
fn(std::move(typed_receiver)));
}
return nullptr;
return std::make_unique<InstanceHolder<Interface>>(
fn(receiver.As<Interface>()));
}
void OnInstanceDisconnected(InstanceHolderBase* instance);
using GenericCallback =
using Constructor =
base::RepeatingCallback<std::unique_ptr<InstanceHolderBase>(
GenericPendingReceiver*)>;
const std::vector<GenericCallback> callbacks_;
GenericPendingReceiver)>;
std::map<std::string, Constructor> constructors_;
base::flat_set<std::unique_ptr<InstanceHolderBase>, base::UniquePtrComparator>
instances_;

@ -40,20 +40,10 @@ class TestService1Impl : public mojom::TestService1 {
~TestService1Impl() override {
--num_instances_;
if (destruction_wait_loop_)
destruction_wait_loop_->Quit();
}
static int num_instances() { return num_instances_; }
static void WaitForInstanceDestruction() {
static base::NoDestructor<base::Optional<base::RunLoop>> loop;
loop->emplace();
destruction_wait_loop_ = &loop->value();
(*loop)->Run();
destruction_wait_loop_ = nullptr;
}
private:
// mojom::TestService1:
void GetIdentity(GetIdentityCallback callback) override {
@ -64,7 +54,6 @@ class TestService1Impl : public mojom::TestService1 {
Receiver<mojom::TestService1> receiver_;
static int num_instances_;
static base::RunLoop* destruction_wait_loop_;
DISALLOW_COPY_AND_ASSIGN(TestService1Impl);
};
@ -88,7 +77,6 @@ class TestService2Impl : public mojom::TestService2 {
};
int TestService1Impl::num_instances_ = 0;
base::RunLoop* TestService1Impl::destruction_wait_loop_ = nullptr;
auto RunTestService1(PendingReceiver<mojom::TestService1> receiver) {
return std::make_unique<TestService1Impl>(std::move(receiver));
@ -99,11 +87,13 @@ auto RunTestService2(PendingReceiver<mojom::TestService2> receiver) {
}
TEST_F(ServiceFactoryTest, BasicMatching) {
ServiceFactory factory{RunTestService1, RunTestService2};
ServiceFactory factory;
factory.Add(RunTestService1);
factory.Add(RunTestService2);
Remote<mojom::TestService1> remote1;
GenericPendingReceiver receiver = remote1.BindNewPipeAndPassReceiver();
EXPECT_TRUE(factory.MaybeRunService(&receiver));
EXPECT_TRUE(factory.RunService(std::move(receiver), base::NullCallback()));
EXPECT_FALSE(receiver.is_valid());
// Verify that we connected to an instance of TestService1.
@ -113,7 +103,7 @@ TEST_F(ServiceFactoryTest, BasicMatching) {
Remote<mojom::TestService2> remote2;
receiver = remote2.BindNewPipeAndPassReceiver();
EXPECT_TRUE(factory.MaybeRunService(&receiver));
EXPECT_TRUE(factory.RunService(std::move(receiver), base::NullCallback()));
EXPECT_FALSE(receiver.is_valid());
// Verify that we connected to an instance of TestService2.
@ -122,64 +112,73 @@ TEST_F(ServiceFactoryTest, BasicMatching) {
Remote<mojom::TestService3> remote3;
receiver = remote3.BindNewPipeAndPassReceiver();
EXPECT_FALSE(factory.MaybeRunService(&receiver));
EXPECT_TRUE(receiver.is_valid());
EXPECT_TRUE(receiver.As<mojom::TestService3>());
EXPECT_FALSE(factory.CanRunService(receiver));
EXPECT_FALSE(factory.RunService(std::move(receiver), base::NullCallback()));
EXPECT_FALSE(receiver.is_valid());
}
TEST_F(ServiceFactoryTest, DestroyInstanceOnClientDisconnect) {
ServiceFactory factory{RunTestService1};
ServiceFactory factory;
factory.Add(RunTestService1);
base::RunLoop loop1;
base::OnceClosure quit1 = loop1.QuitClosure();
Remote<mojom::TestService1> remote1;
GenericPendingReceiver receiver = remote1.BindNewPipeAndPassReceiver();
EXPECT_TRUE(factory.MaybeRunService(&receiver));
EXPECT_TRUE(factory.RunService(std::move(receiver), std::move(quit1)));
base::RunLoop loop2;
base::OnceClosure quit2 = loop2.QuitClosure();
Remote<mojom::TestService1> remote2;
receiver = remote2.BindNewPipeAndPassReceiver();
EXPECT_TRUE(factory.MaybeRunService(&receiver));
EXPECT_TRUE(factory.RunService(std::move(receiver), std::move(quit2)));
remote1.FlushForTesting();
remote2.FlushForTesting();
EXPECT_EQ(2, TestService1Impl::num_instances());
remote1.reset();
TestService1Impl::WaitForInstanceDestruction();
loop1.Run();
EXPECT_EQ(1, TestService1Impl::num_instances());
remote2.FlushForTesting();
EXPECT_EQ(1, TestService1Impl::num_instances());
remote2.reset();
TestService1Impl::WaitForInstanceDestruction();
loop2.Run();
EXPECT_EQ(0, TestService1Impl::num_instances());
}
TEST_F(ServiceFactoryTest, DestroyInstanceOnServiceDisconnect) {
ServiceFactory factory{RunTestService1};
ServiceFactory factory;
factory.Add(RunTestService1);
base::RunLoop loop;
base::OnceClosure quit = loop.QuitClosure();
Remote<mojom::TestService1> remote;
GenericPendingReceiver receiver = remote.BindNewPipeAndPassReceiver();
EXPECT_TRUE(factory.MaybeRunService(&receiver));
EXPECT_TRUE(factory.RunService(std::move(receiver), std::move(quit)));
remote.FlushForTesting();
EXPECT_EQ(1, TestService1Impl::num_instances());
remote->Quit();
remote.FlushForTesting();
loop.Run();
EXPECT_EQ(0, TestService1Impl::num_instances());
}
TEST_F(ServiceFactoryTest, DestroyInstancesOnFactoryDestruction) {
base::Optional<ServiceFactory> factory{base::in_place, RunTestService1};
base::Optional<ServiceFactory> factory{base::in_place};
factory->Add(RunTestService1);
Remote<mojom::TestService1> remote1;
GenericPendingReceiver receiver = remote1.BindNewPipeAndPassReceiver();
EXPECT_TRUE(factory->MaybeRunService(&receiver));
EXPECT_TRUE(factory->RunService(std::move(receiver), base::NullCallback()));
remote1.FlushForTesting();
EXPECT_EQ(1, TestService1Impl::num_instances());
Remote<mojom::TestService1> remote2;
receiver = remote2.BindNewPipeAndPassReceiver();
EXPECT_TRUE(factory->MaybeRunService(&receiver));
EXPECT_TRUE(factory->RunService(std::move(receiver), base::NullCallback()));
remote2.FlushForTesting();
EXPECT_EQ(2, TestService1Impl::num_instances());

@ -224,7 +224,11 @@ void SimpleWatcher::ArmOrNotify() {
MojoResult ready_result;
HandleSignalsState ready_state;
MojoResult rv = Arm(&ready_result, &ready_state);
if (rv == MOJO_RESULT_OK)
// NOTE: If the watched handle has been closed, the above call will result in
// MOJO_RESULT_NOT_FOUND. A MOJO_RESULT_CANCELLED notification will already
// have been posted to this object as a result, so there's nothing else to do.
if (rv == MOJO_RESULT_OK || rv == MOJO_RESULT_NOT_FOUND)
return;
DCHECK_EQ(MOJO_RESULT_FAILED_PRECONDITION, rv);

@ -4,7 +4,10 @@
#include "services/test/echo/echo_service.h"
#include <string.h>
#include "base/immediate_crash.h"
#include "base/memory/shared_memory_mapping.h"
namespace echo {
@ -18,6 +21,13 @@ void EchoService::EchoString(const std::string& input,
std::move(callback).Run(input);
}
void EchoService::EchoStringToSharedMemory(
const std::string& input,
base::UnsafeSharedMemoryRegion region) {
base::WritableSharedMemoryMapping mapping = region.Map();
memcpy(mapping.memory(), input.data(), input.size());
}
void EchoService::Quit() {
receiver_.reset();
}

@ -21,6 +21,8 @@ class EchoService : public mojom::EchoService {
// mojom::EchoService:
void EchoString(const std::string& input,
EchoStringCallback callback) override;
void EchoStringToSharedMemory(const std::string& input,
base::UnsafeSharedMemoryRegion region) override;
void Quit() override;
void Crash() override;

@ -7,4 +7,5 @@ import("//mojo/public/tools/bindings/mojom.gni")
mojom("mojom") {
generate_java = true
sources = [ "echo.mojom" ]
public_deps = [ "//mojo/public/mojom/base" ]
}

@ -4,11 +4,17 @@
module echo.mojom;
import "mojo/public/mojom/base/shared_memory.mojom";
// Echos its input.
interface EchoService {
// Echos the passed-in string.
EchoString(string input) => (string echoed_input);
// Echos the passed-in string into the provided shared memory buffer.
EchoStringToSharedMemory(string input,
mojo_base.mojom.UnsafeSharedMemoryRegion region);
// Causes the service to disconnect itself.
Quit();