The Service Manager is a tool that brokers connections and capabilities between -- and manages instances of -- system components referred to henceforth as services.
The Service Manager performs the following functions:
The Service Manager presents a series of Mojo interfaces to services, though in practice most interaction with the Service Manager is made simpler by using its corresponding C++ client library.
The Mojo system provides two key components of interest here - a lightweight message pipe concept allowing two endpoints to communicate, and a bindings layer that allows interfaces to be described to bind to those endpoints, with ergonomic bindings for languages used in Chrome.
Mojo message pipes are designed to be lightweight and may be read from/written to and passed around from one process to another. In most situations a developer won't interact with the pipes directly, but rather with bindings types generated to encapsulate a bound interface. To use the bindings, a developer defines their interface in the Mojom IDL format. With some build magic, the generated definitions can then be referenced from C++, JavaScript and Java code.
See the Mojo documentation for a complete overview, detailed explanations, and API references.
A service is a collection of one or more private implementations of public Mojo interfaces which are reachable via the Service Manager. Every service is comprised of the following pieces:
The Service Manager is responsible for starting new service instances on-demand, and a given service many have any number of concurrently running instances. The Service Manager disambiguates service instances by their unique identity. A service's identity is represented by the 3-tuple of its service name, user ID, and instance qualifier:
As long as a service instance is running it must maintain an implementation of the service_manager.mojom.Service
interface. Typically this is done in C++ code by implementing the C++ client library's service_manager::Service
interface. This interface is driven by messages from the Service Manager and is used to receive incoming interface requests the Service Manager brokers from other services.
Every service instance also has an outgoing link back to the Service Manager which it can use to make interface requests to other services in the system. This is the service_manager.mojom.Connector
interface, and it‘s commonly used via the C++ client library’s service_manager::Connector
class.
This section walks through the creation of a simple skeleton service.
Consider this implementation of the service_manager::Service
interface:
//services/my_service/my_service.h
#include "base/macros.h" #include "services/service_manager/public/cpp/service.h" namespace my_service { class MyService : public service_manager::Service { public: MyService(); ~MyService() override; // service_manager::Service: void OnStart() override; void OnBindInterface(const service_manager::ServiceInfo& remote_info, const std::string& interface_name, mojo::ScopedMessagePipeHandle handle) override; private: DISALLOW_COPY_AND_ASSIGN(MyService); }; } // namespace my_service
//services/my_service/my_service.cc
#include "services/my_service/my_service.h" namespace my_service { MyService::MyService() = default; MyService::~MyService() = default; void MyService::OnStart() { } void MyService::OnBindInterface(const service_manager::ServiceInfo& remote_info, const std::string& interface_name, mojo::ScopedMessagePipeHandle handle) { } } // namespace my_service
While services do not need to define a main entry point -- e.g. they may only intend to be embedded in other running processes -- for the sake of completeness we also define a ServiceMain
definition so that the service can be run in its own process:
//services/my_service/my_service_main.cc
#include "services/my_service/my_service.h" #include "services/service_manager/public/c/main.h" #include "services/service_manager/public/cpp/service_runner.h" MojoResult ServiceMain(MojoHandle service_request_handle) { return service_manager::ServiceRunner(new MyService).Run( service_request_handle); }
A static manifest is provided to the Service Manager by each service to declare the capabilities exposed and required by the service:
//services/my_service/manifest.json
{ "name": "my_service", "display_name": "My Service", "interface_provider_specs": { "service_manager:connector": {} } }
See Service Manifests for more information.
Finally some build targets corresponding to the above things:
//services/my_service/BUILD.gn
import("//services/service_manager/public/cpp/service.gni") import("//services/service_manager/public/service_manifest.gni") source_set("lib") { public = [ "my_service.h" ] sources = [ "my_service.cc" ] public_deps = [ "//base", "//services/service_manager/public/cpp", ] } service("my_service") { sources = [ "my_service_main.cc", ] deps = [ ":lib", "//services/service_manager/public/c", ] } service_manifest("manifest") { name = "my_service" source = "manifest.json" }
Building the my_service
target produces a my_service.service
(or on Windows, my_service.service.exe
) binary in the output directory. This can be run as a standalone executable, but it will exit immediately without doing anything interesting, because it won't have a Service
pipe to drive it. The Service Manager knows how to provide such a pipe when launching a service executable.
This service doesn‘t do much of anything. It will simply run forever (or at least until the Service Manager itself shuts down), ignoring all incoming messages. Before we expand on the definition of this service, let’s look at some of the details of the service_manager::Service
interface.
The Service
implementation is guaranteed to receive a single OnStart()
invocation from the Service Manager before anything else hapens. Once this method is called, the implementation can access its service_manager::ServiceContext
via context()
. This object itself exposes a few values:
service_info()
is a service_manager::ServiceInfo
structure describing the running service from the Service Manager‘s perspective. This includes the service_manager::Identity
which uniquely identifies the running instance, as well as the service_manager::InterfaceProviderSpec
describing the capability specifications outlined in the service’s manifest.identity()
is a shortcut to the Identity
stored in the ServiceInfo
.connector()
is a service_manager::Connector
which can be used to make outgoing interface requests to other services.For example, we could modify MyService
to connect out to logger service on startup:
void MyService::OnStart() { logger::mojom::LoggerPtr logger; context()->connector()->BindInterface("logger", &logger); logger->Log("Started MyService!"); }
The OnBindInterface
method on service_manager::Service
is invoked by the Service Manager any time another service instance uses its own Connector
to request an interface from this my_service
instance. The Service Manager only invokes this method once it has already validated that the request meets the mutual constraints specified in each involved service's manifest.
The arguments to OnBindInterface
are as follows:
remote_info
is the service_manager::ServiceInfo
corresponding to the remote service which is requesting this interface. The information in this structure is provided authoritatively by the Service Manager and can be trusted in any context.interface_name
is the (std::string
) name of the interface being requested by the remote service. The Service Manager has already validated that the remote service requires at least one capability which exposes this interface from the local service.handle
is the mojo::ScopedMessagePipeHandle
of an interface pipe which the remote service expects us to bind to a concrete implementation of the requested interface.The Service Manager client library provides a service_manager::BinderRegistry
class definition which can make it easier for services to bind incoming interface requests. Typesafe binding callbacks are added to an BinderRegistry
ahead of time, and the incoming arguments to OnBindInterface
can be forwarded to the registry, which will bind the message pipe if it knows how. For example, we could modify our MyService
implementation as follows:
namespace { void BindDatabase(my_service::mojom::DatabaseRequest request) { mojo::MakeStrongBinding(std::make_unique<my_service::DatabaseImpl>(), std::move(request)); } } // namespace MyService::MyService() { // Imagine |registry_| is added as a member of MyService, with type // service_manager::BinderRegistry. // The |my_service::mojom::Database| interface type is inferred by the // compiler in the AddInterface call, and this effectively adds the bound // function to an internal map keyed on the interface name, i.e. // "my_service::mojom::Database" in this case. registry_.AddInterface(base::Bind(&BindDatabase)); } void MyService::OnBindInterface(const service_manager::ServiceInfo& remote_info, const std::string& interface_name, mojo::ScopedMessagePipeHandle handle) { registry_.BindInterface(interface_name, std::move(handle)); }
For more details regarding the definition of Mojom interfaces, implementing them in C++, and working with C++ types like InterfaceRequest
, see the Mojom IDL and Bindings Generator and Mojo C++ Bindings API documentation.
If some service were to come along and attempt to connect to my_service
and bind the my_service::mojom::Database
interface, we might see the Service Manager spit out an error log complaining that InterfaceProviderSpec
prevented a connection to my_service
.
In order for the interface to be reachable by other services, we must first fix its manifest's interface provider spec. The interface provider spec is a dictionary keyed by interface provider name, with each value representing the capability spec for that provider.
Each capability spec defines an optional "provides"
key and an optional "requires"
key.
The provides
key value is a dictionary which is itself keyed by arbitrary free-form strings (capability names, implicitly scoped to the manifest's own service) whose values are lists of Mojom interface names exposed as part of that capability.
The requires
key value is also a dictionary, but it's one which is keyed by remote service name. Each value is a list of capabilities required from the corresponding remote service.
Finally, every interface provider spec (often exclusively) contains one standard capability spec named “service_manager:connector”. This is the capability spec enforced when inter-service connections are made from a service's Connector
interface.
Let's update the my_service
manifest as follows:
//services/my_service/manifest.json
{ "name": "my_service", "display_name": "My Service", "interface_provider_specs": { "service_manager:connector": { "provides": { "database": [ "my_service::mojom::Database" ] } } } }
This means that my_service
has defined a database
capability comprised solely of the my_service::mojom::Database
interface. Any service which requires this capability can bind that interface from my_service
.
For the sake of this example, let's define another service manifest:
//services/other_service/manifest.json
{ "name": "other_service", "display_name": "Other Service", "interface_provider_specs": { "service_manager:connector": { "requires": { "my_service": [ "database" ] } } } }
Now if other_service
attempts to bind the database interface:
void OtherService::OnStart() { my_service::mojom::DatabasePtr database; context()->connector()->BindInterface("my_service", &database); database->AddTable(...); }
The Service Manager will approve of the request and forward it on to the my_service
instance's OnBindInterface
method.
Now that we‘ve built a simple service it’s time to write a test for it. The Service Manager client library provides a test fixture base class in service_manager::test::ServiceTest
that makes writing service integration tests straightforward. This test fixture runs an in-process Service Manager on a background thread which allows test service instances to be injected at runtime.
Let's look at a simple test of our service:
//services/my_service/my_service_unittest.cc
#include "base/bind.h" #include "base/run_loop.h" #include "services/service_manager/public/cpp/service_test.h" #include "path/to/some_interface.mojom.h" class MyServiceTest : public service_manager::test::ServiceTest { public: // Our tests run as service instances themselves. In this case each instance // identifies as the service named "my_service_unittests". MyServiceTest() : service_manager::test::ServiceTest("my_service_unittests") { } ~MyServiceTest() override {} } TEST_F(MyServiceTest, Basic) { my_service::mojom::DatabasePtr database; connector()->BindInterface("my_service", &database); base::RunLoop loop; // This assumes DropTable expects a response with no arguments. When the // response is received, the RunLoop is quit. database->DropTable("foo", loop.QuitClosure()); loop.Run(); }
If adding a new test binary for these tests, we can augment our BUILD.gn
to use the service_test
GN template like so:
//services/my_service/BUILD.gn
import("//services/catalog/public/tools/catalog.gni") import("//services/service_manager/public/tools/test/service_test.gni") service_test("my_service_unittests") { sources = [ "my_service_unittest.cc", ] deps = [ "//services/my_service/public/interfaces", ] catalog = ":my_service_unittests_catalog" } service_manifest("my_service_unittests_manifest") { name = "my_service_unittests" manifest = "my_service_unittests_manifest.json" } catalog("my_service_unittests_catalog") { testonly = true embedded_services = [ ":my_service_unittests_manifest" ] standalone_services = [ ":manifest" ] }
Alright, there‘s a lot going on here. First we also have to create a service manifest for the test service itself, as the Service Manager needs to be able to reason about the test’s own required capabilities with respect to the service-under-test.
We can do something like:
//services/my_service/my_service_unittests_manifest.json
{ "name": "my_service_unittests", "display_name": "my_service tests", "interface_provider_specs": { "service_manager:connector": { "requires": { "my_service": [ "database" ] } } } }
You may also notice that we have suddenly introduced a catalog in the service_test
target incantation. Any runtime environment which hosts a Service Manager must provide the Service Manager implementation with a catalog of service manifests. This catalog defines the complete set of services recognized by the Service Manager instance and can be used in all kinds of interesting ways to control how various services are started in the system. See Service Manager Catalogs for more information.
For now let's just accept that we have to create a catalog
rule for our test suite and plug it into the service_test
target.
In practice, we typically try to avoid introducing new unittest binaries for individual services. Instead we have an aggregate service_unittests
target defined in //services/BUILD.gn
. There are several examples of other services adding their service tests to this suite.
A catalog is an aggregation of service manifests which comprises a complete runtime configuration of the Service Manager.
The GN catalog
target template defined in //services/catalog/public/tools/catalog.gni
. provides a simple means of aggregating service manifests into a single build artifact. See the comments on the template for detailed documentation.
This GNI also defines a catalog_cpp_source
target which can generate a static C++ representation of an aggregated catalog manifest so that it can be passed the Service Manager at runtime.
In general, service developers should never be concerned with creating new catalogs or instantiating the Service Manager, but it's important to be aware of these concepts. When introducing a new service into any runtime environment -- including Chrome, Content, or various unit test suites such as service_unittests
discussed in the previous section -- your service manifest must be added to the catalog used in that environment.
TODO - expand on this
TODO
TODO