Mojo; pub/sub pattern; dup'ing InterfacePtr's; N:M not N:1

57 views
Skip to first unread message

Nigel Tao

unread,
Oct 11, 2018, 12:01:49 AM10/11/18
to chromi...@chromium.org
I'm experimenting with having a number of publishers and subscribers
(Ps and Ss) of some events, in Mojo. I also have an event broker
(another Mojo interface), but it's pretty dumb. It's a glorified
meeting place with a well known address, where Ps and Ss find out
about each other.

Once they find each other, I'd like to have the broker get out of the
way, and the Ps to talk directly to the Ss. (This is possibly
premature optimization??)

My .mojom file has:

```
interface Broker {
RegisterPublisher(Publisher p);
RegisterSubscriber(Subscriber s);
}

interface Publisher {
Subscribe(Subscriber s);
}

interface Subscriber {
OnEvent(Event e);
}

struct Event {
// etc
}
```

Whenever the Broker sees a new P, I'd like it to connect it to all of
the previously registered Ss. Vice versa, when it sees a new S, I'd
like to connect it to all of the previously registered Ps.

It's not obvious how to do this, in Mojo. In my Broker implementation,
RegisterFoo adds the FooPtr argument (a PPtr or a SPtr) to an
mojo::InterfacePtrSet - the set of previously registered Foos. That's
straightforward. But I also want to do the "connect to previously
registered things", something like:

```
void Broker::RegisterPublisher(blah::mojom::PPtr p) {
subscribers_.ForAllPtrs([&p](auto* s) {
p->Subscribe(s);
});
publishers_.AddPtr(std::move(p));
}
```

This doesn't work. It doesn't even compile (because s has type
blah::mojom::S*, but p->Subscribe wants blah::mojom::SPtr). But I
think that not working makes sense. If an SPtr wraps a message pipe
endpoint (and closes that endpoint upon destruction), then I can't
magically create new message pipes out of nowhere. I think I
originally wanted something that'd be to endpoints what POSIX's dup is
for file descriptors, but perhaps a Mojo message pipe is conceptually
single-writer, not multi-writer. Is that right?

What should I do instead? Is there prior art in the Chromium code
base? I've seen the Observer pattern in Chromium's .mojom files
(whether the verb is called Observe, Listen or Subscribe), but AFAICT
they're N:1 not N:M - N observers and only 1 and not M observables.

Should I add explicit Dup methods to the Subscriber interface, so it's
something like:

```
interface Subscriber {
Dup() => (Subscriber duplicate_of_this);
OnEvent(Event e);
}
```

and when a RegisterSubscriber(s) call comes into the Broker, it calls
Dup multiple times, one for each previously registered P, and then
connects each dupe with a P? Similarly, a RegisterPublisher(p) call
will again call Dup multiple times.

Or should the Broker permanently be a person-in-the-middle, instead of
having Ps and Ss connected directly, with the Broker talking to each P
on behalf of each S and explicitly forwarding messages on? I'd like to
avoid that because Events are really delta's that reflect changes in
state, and when a P sees a new S, the initial batch of events brings S
up to date with the current state. If the Broker tries to manage fan
out from each P to multiple Ss, then I think it also needs to track
per-S state (whether it's seen the initial batch, and if new Events
concurrently arrive, and conflict, determining which Event wins),
which could get tricky if new Ps and Ss and Events are concurrently
appearing, and if the various Mojo calls between independent (and
possibly out-of-process) actors have weak ordering guarantees. Also,
not shown yet, but Subscribe will also probably take a
SubscribeOptions arg, so that a Broker might not be able to Subscribe
only once on behalf of N Ss.

That's not to say that proxying everything through the Broker is
unfeasible. Maybe I'm overestimating its drawbacks. But having a
Broker manually wire up Ps and Ss and forwarding Mojo calls feels like
I'm re-implementing message pipes.

John Abd-El-Malek

unread,
Oct 11, 2018, 12:18:22 AM10/11/18
to nige...@chromium.org, chromium-mojo
What is the rough frequency of these messages, and the order of magnitude of  N & M?

If the above are low, then a central broker is simplest and anything extra might be premature optimization?

--
You received this message because you are subscribed to the Google Groups "chromium-mojo" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-moj...@chromium.org.
To post to this group, send email to chromi...@chromium.org.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/chromium-mojo/CAEdON6bPF9rdD9Hcvkzm%2BP3ZDrvC_%2BN-zYLdH%3DsrS-yij_PBew%40mail.gmail.com.

Nigel Tao

unread,
Oct 11, 2018, 1:08:30 AM10/11/18
to John Abd-El-Malek, chromi...@chromium.org
On Thu, Oct 11, 2018 at 3:18 PM John Abd-El-Malek <j...@chromium.org> wrote:
> What is the rough frequency of these messages, and the order of magnitude of N & M?
>
> If the above are low, then a central broker is simplest and anything extra might be premature optimization?

Yeah, they're all pretty low.

Optimization isn't really my primary concern. I don't think I
explained it very well, but some state tracking (above the Mojo layer)
is possibly simpler if the central broker is out of the way. I was
mostly wondering if N:M was even feasible, or if i was missing
something.

But keeping the broker as a central proxy should be straightforward. Thanks.

Ken Rockot

unread,
Oct 11, 2018, 11:55:54 AM10/11/18
to Nigel Tao, John Abd-El-Malek, chromium-mojo
The prior art you're looking for is a pattern of introducing Clone methods on interfaces that should be dup'able. Similar to your Dup proposal, but slightly nicer: you don't need to reply with a Ptr and involve async ugliness. You can instead have the method take an interface request as an argument:

// mojom
interface Publisher {
  Subscribe(Subscriber s);
  Clone(Publisher& request);
};

interface Subscriber {
  OnEvent(Event e);
  Clone(Subscriber& request);
};

// C++
BrokerImpl::RegisterPublisher(mojom::Publisher p) {
  subscribers_.ForAllPtrs([&p](mojom::Subscriber* s) {
    mojom::SubscriberPtr dup;
    s->Clone(mojo::MakeRequest(&dup));
    p->Subscribe(std::move(dup));
}

SubscriberImpl::Clone(mojom::SubscriberRequest request) {
  // Assuming bindings_ is a BindingSet here.
  bindings_.AddBinding(this, std::move(request));
}

For whatever it's worth, there have been some interesting cases in the past (and one very recently) where we've wanted a pub/sub system that isn't bound to any single publishing or subscribing service instance's lifetime. i.e. we want service A to remain subscribed to events published by service B, without having to keep service B alive in the interim. The way I was going to propose we model this is as a dedicated, persistent pubsub service with generic (e.g. essentially base::Value) events. I don't suppose you happen to be solving that exact problem? :)

--
You received this message because you are subscribed to the Google Groups "chromium-mojo" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-moj...@chromium.org.
To post to this group, send email to chromi...@chromium.org.

Nigel Tao

unread,
Oct 11, 2018, 8:03:10 PM10/11/18
to Ken Rockot, John Abd-El-Malek, chromi...@chromium.org
On Fri, Oct 12, 2018 at 2:55 AM Ken Rockot <roc...@google.com> wrote:
> have the method take an interface request as an argument

Ah, I see. Nice.


> For whatever it's worth, there have been some interesting cases in the past (and one very recently) where we've wanted a pub/sub system that isn't bound to any single publishing or subscribing service instance's lifetime. i.e. we want service A to remain subscribed to events published by service B, without having to keep service B alive in the interim. The way I was going to propose we model this is as a dedicated, persistent pubsub service with generic (e.g. essentially base::Value) events. I don't suppose you happen to be solving that exact problem? :)

That's not *exactly* the problem I'm solving, as I expect my B's to
live forever (or for as long as a Chrome OS session). More
importantly, when a new A arrives, I want it to catch up with a
summary of all of the B's previous events: each event is a state
delta. I haven't decided yet whether the broker or the B's should be
primarily responsible for sending the initial state of the world.

But I do want a dedicated, persistent (meaning long-lived, not
saved-to-disk) pubsub service. If your dedicated, persistent pubsub
service existed, I think I'd use it. :-)

My design had a custom Mojo struct type for the events, but a
base::Value would also work for me. It might even be a better fit,
with optional fields, as I was otherwise going to define my own
"Ternary" Mojo enum to represent "true / false / unknown" in a few
places. Generally, when my Mojo struct had integer-typed or enum-typed
fields, I'd reserved 0 to mean "unknown", as a semantic convention
above the Mojo layer.

If you're interested, I'm working on the App Service, a uniform way to
programatically work with apps, regardless of their implementation. On
Chrome OS, we have Extension apps, Web apps, Android apps and Linux
apps. Those are our B's, which announce "here are my apps", which can
change as apps are installed, uninstalled, disabled, updated, etc. Our
A's are e.g. UI surfaces where we show app icons and names, and let
you launch them, choose between them (intent pickers), switch between
them (window management), configure them, etc. Currently, that's all
wired up (using N*M wires, with N A's and M B's) inside the monolithic
browser process. It'd be nice to change N*M to N+M, it'd be nice to
allow Chrome OS apps that aren't tied to the browser, and it'd be nice
for the usual Servicification reasons.

In my (work in progress) design, events are things like "app Foo was
installed", "app Bar's icon changed", and the initial state of the
world is "here's all the currently installed apps".

App Service Design Doc:
https://docs.google.com/document/d/1oc2BPABvnkctf0EP_9kyuH4h3eZTfDXkpGD8jczH6sE/edit#heading=h.7v094uo4zv3u

lo...@chromium.org

unread,
Oct 11, 2018, 11:28:54 PM10/11/18
to chromium-mojo, nige...@chromium.org, j...@chromium.org
Hi, Ken!


For whatever it's worth, there have been some interesting cases in the past (and one very recently) where we've wanted a pub/sub system that isn't bound to any single publishing or subscribing service instance's lifetime. i.e. we want service A to remain subscribed to events published by service B, without having to keep service B alive in the interim. The way I was going to propose we model this is as a dedicated, persistent pubsub service with generic (e.g. essentially base::Value) events. I don't suppose you happen to be solving that exact problem? :)
In general... Can we have "auto connection" ("auto discovery") for services as an intrinsic feature for chromium-mojo?
(With handy helpers for 1:1, 1:many and many:many relationships between services)

Alexey.

 
On Wed, Oct 10, 2018 at 10:08 PM Nigel Tao wrote:
On Thu, Oct 11, 2018 at 3:18 PM John Abd-El-Malekwrote:

> What is the rough frequency of these messages, and the order of magnitude of  N & M?
>
> If the above are low, then a central broker is simplest and anything extra might be premature optimization?

Yeah, they're all pretty low.

Optimization isn't really my primary concern. I don't think I
explained it very well, but some state tracking (above the Mojo layer)
is possibly simpler if the central broker is out of the way. I was
mostly wondering if N:M was even feasible, or if i was missing
something.

But keeping the broker as a central proxy should be straightforward. Thanks.

--
You received this message because you are subscribed to the Google Groups "chromium-mojo" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-mojo+unsubscribe@chromium.org.

John Abd-El-Malek

unread,
Oct 12, 2018, 12:53:05 AM10/12/18
to Nigel Tao, Ken Rockot, chromium-mojo


On Thu, Oct 11, 2018, 5:03 PM Nigel Tao <nige...@chromium.org wrote:
On Fri, Oct 12, 2018 at 2:55 AM Ken Rockot <roc...@google.com> wrote:
> have the method take an interface request as an argument

Ah, I see. Nice.


> For whatever it's worth, there have been some interesting cases in the past (and one very recently) where we've wanted a pub/sub system that isn't bound to any single publishing or subscribing service instance's lifetime. i.e. we want service A to remain subscribed to events published by service B, without having to keep service B alive in the interim. The way I was going to propose we model this is as a dedicated, persistent pubsub service with generic (e.g. essentially base::Value) events. I don't suppose you happen to be solving that exact problem? :)

That's not *exactly* the problem I'm solving, as I expect my B's to
live forever (or for as long as a Chrome OS session). More
importantly, when a new A arrives, I want it to catch up with a
summary of all of the B's previous events: each event is a state
delta. I haven't decided yet whether the broker or the B's should be
primarily responsible for sending the initial state of the world.

But I do want a dedicated, persistent (meaning long-lived, not
saved-to-disk) pubsub service. If your dedicated, persistent pubsub
service existed, I think I'd use it. :-)

FWIW a common pub/sub service was one of the foundational services we envisioned, but it wasn't worked on because we had no need so far. If this work drives the creation of a reusuable service, that'd be great :)

Daniel Cheng

unread,
Oct 12, 2018, 12:56:47 AM10/12/18
to lo...@chromium.org, chromi...@chromium.org, nige...@chromium.org, j...@chromium.org
On Thu, Oct 11, 2018 at 8:28 PM <lo...@chromium.org> wrote:
Hi, Ken!

For whatever it's worth, there have been some interesting cases in the past (and one very recently) where we've wanted a pub/sub system that isn't bound to any single publishing or subscribing service instance's lifetime. i.e. we want service A to remain subscribed to events published by service B, without having to keep service B alive in the interim. The way I was going to propose we model this is as a dedicated, persistent pubsub service with generic (e.g. essentially base::Value) events. I don't suppose you happen to be solving that exact problem? :)
In general... Can we have "auto connection" ("auto discovery") for services as an intrinsic feature for chromium-mojo?
(With handy helpers for 1:1, 1:many and many:many relationships between services)

Alexey.

What does "auto discovery" mean in this context?

Daniel

 

 
On Wed, Oct 10, 2018 at 10:08 PM Nigel Tao wrote:
On Thu, Oct 11, 2018 at 3:18 PM John Abd-El-Malekwrote:
> What is the rough frequency of these messages, and the order of magnitude of  N & M?
>
> If the above are low, then a central broker is simplest and anything extra might be premature optimization?

Yeah, they're all pretty low.

Optimization isn't really my primary concern. I don't think I
explained it very well, but some state tracking (above the Mojo layer)
is possibly simpler if the central broker is out of the way. I was
mostly wondering if N:M was even feasible, or if i was missing
something.

But keeping the broker as a central proxy should be straightforward. Thanks.

--
You received this message because you are subscribed to the Google Groups "chromium-mojo" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-moj...@chromium.org.

--
You received this message because you are subscribed to the Google Groups "chromium-mojo" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-moj...@chromium.org.

To post to this group, send email to chromi...@chromium.org.

lo...@chromium.org

unread,
Oct 12, 2018, 1:05:41 AM10/12/18
to chromium-mojo, lo...@chromium.org, nige...@chromium.org, j...@chromium.org
Hi, Deniel!

On Friday, October 12, 2018 at 3:56:47 PM UTC+11, Daniel Cheng wrote:
On Thu, Oct 11, 2018 at 8:28 PM loyso wrote:
Hi, Ken!

For whatever it's worth, there have been some interesting cases in the past (and one very recently) where we've wanted a pub/sub system that isn't bound to any single publishing or subscribing service instance's lifetime. i.e. we want service A to remain subscribed to events published by service B, without having to keep service B alive in the interim. The way I was going to propose we model this is as a dedicated, persistent pubsub service with generic (e.g. essentially base::Value) events. I don't suppose you happen to be solving that exact problem? :)
In general... Can we have "auto connection" ("auto discovery") for services as an intrinsic feature for chromium-mojo?
(With handy helpers for 1:1, 1:many and many:many relationships between services)

Alexey.

What does "auto discovery" mean in this context?

By "auto discovery" I meant "a pub/sub system that isn't bound to any single publishing or subscribing service instance's lifetime. i.e. we want service A to remain subscribed to events published by service B, without having to keep service B alive in the interim. ".

 
Daniel

 

 
On Wed, Oct 10, 2018 at 10:08 PM Nigel Tao wrote:
On Thu, Oct 11, 2018 at 3:18 PM John Abd-El-Malekwrote:
> What is the rough frequency of these messages, and the order of magnitude of  N & M?
>
> If the above are low, then a central broker is simplest and anything extra might be premature optimization?

Yeah, they're all pretty low.

Optimization isn't really my primary concern. I don't think I
explained it very well, but some state tracking (above the Mojo layer)
is possibly simpler if the central broker is out of the way. I was
mostly wondering if N:M was even feasible, or if i was missing
something.

But keeping the broker as a central proxy should be straightforward. Thanks.

--
You received this message because you are subscribed to the Google Groups "chromium-mojo" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-mojo+unsubscribe@chromium.org.

--
You received this message because you are subscribed to the Google Groups "chromium-mojo" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-mojo+unsubscribe@chromium.org.

Nigel Tao

unread,
Oct 25, 2018, 2:41:04 AM10/25/18
to lo...@chromium.org, Ken Rockot, chromi...@chromium.org
For the curious, I have a working version of a Mojo pub/sub system in
https://chromium-review.googlesource.com/c/chromium/src/+/1298819,
using rockot@'s Clone methods. In particular,
https://chromium-review.googlesource.com/c/chromium/src/+/1298819/1/chrome/browser/apps/foundation/app_service/public/mojom/app_registry.mojom
defines Publisher and Subscriber Mojo interfaces. Let's call them P and S.

It also re-defines the Registry interface to primarily be just a place
where Ps and Ss discover each other. Every time a P registers with the
Registry, it's connected with (a Clone of) each prior S. Vice versa
every time an S registers itself with the Registry: that new S is
Clone'd multiple times and connected to each prior P. See the
::Connect function in
https://chromium-review.googlesource.com/c/chromium/src/+/1298819/1/chrome/browser/apps/foundation/app_service/app_registry/app_registry.cc#28
and the two places it's called, further below in the same file.

Eventually, we will want some sort of un-registration mechanism. But
the CL is very much a work-in-progress, proof-of-concept prototype.

Once the Ps and Ss are connected (connection is the
Publisher.Subscribe Mojo method), the Registry pretty much gets out of
the way. The AppRegistry C++ implementation is actually pretty small.

Another thing the Registry still does (which isn't necessarily related
to a general pub/sub system) is provide methods like Launch (as in
"launch an app"), which is forwarded on to a single P (keyed by the
PublisherType enum; every P has a unique PublisherType). The actual
C++ implementation in
https://chromium-review.googlesource.com/c/chromium/src/+/1298819/1/chrome/browser/apps/foundation/app_service/app_registry/app_registry.cc#130
is a bit of a hack (scroll to AppRegistry::Launch at the bottom). If
rockot@ or anyone else has a better suggestion on how to implement it,
I'm listening.

Example apps::mojom::Publisher C++ implementation:
https://chromium-review.googlesource.com/c/chromium/src/+/1298819/1/chrome/browser/web_applications/extensions/web_app_publisher.cc

Example apps::mojom::Subscriber C++ implementation:
https://chromium-review.googlesource.com/c/chromium/src/+/1298819/1/chrome/browser/apps/foundation/app_service/app_registry/app_registry_keyed_service.cc


On Fri, Oct 12, 2018 at 2:28 PM <lo...@chromium.org> wrote:
> In general... Can we have "auto connection" ("auto discovery") for services as an intrinsic feature for chromium-mojo?
> (With handy helpers for 1:1, 1:many and many:many relationships between services)

My AppRegistry C++ implementation turned out much simpler than I first
feared. It handles M:N, and I'd expect it to handle lifetimes longer
than any one particular P or S implementation. (I haven't proven this
yet, as I haven't prototyped un-registration, but I don't see why it
wouldn't work). It's "auto discovery" in a sense, because all a P or
an S need to discover each other is a service_manager::Connector*
(e.g. from a Profile*) and the well-known name for the App Registry
service, apps::mojom::kServiceName constant defined in
https://cs.chromium.org/chromium/src/chrome/browser/apps/foundation/app_service/public/mojom/constants.mojom?sq=package:chromium&dr&g=0&l=7

I'm not sure if any additional "handy helpers" are necessary.
Reply all
Reply to author
Forward
0 new messages