It might be nice to put this in a document linked to from the mojo docs site (go/mojo-docs). That way we can comment on it directly.
As you note, we talked a lot about making this change back when we introduced replies, and I even mostly hacked up a CL to implement it. It started to feel heavy weight, and yeah, the FIFO issue was an impediment. Maybe my approach felt heavyweight because I forced people to add their own setClient methods to their mojom interfaces. Maybe if we automated that part of it it wouldn't feel so bad.I think the ordering problem you describe also exists to an extent in a world with only replies. Imagine if you call two methods sequentially and each generates a reply. If you want to wait synchronously just for the second reply, you cannot do so without first handling the reply to the first method. This means WaitForIncomingMethodCall is still potentially confusing in what side effect it produces.
Another thing: The model of Foo and FooClient is really meant to mimic what people do in idiomatic C++. They often have a class Foo with a client interface that they set on Foo to receive asynchronous notifications. Foo methods can also take base::Callback<>, etc. When you just think about straight C++ usage like this in Chromium, you'll see that we don't struggle with the ordering problems that much, and so maybe the struggles we experience with Mojom are not as bad as they may sometimes seem?
On Tue, Dec 16, 2014 at 2:16 PM, Darin Fisher <da...@chromium.org> wrote:It might be nice to put this in a document linked to from the mojo docs site (go/mojo-docs). That way we can comment on it directly.That's a really good idea! https://groups.google.com/a/chromium.org/d/msg/mojo-dev/eqsG0aeALLo/Klovi1HJAtsJ is world-readable and mojo...@chromium.org has comment access.
[...]Proposal: Given all this, I propose that we abolish the interface Client notion altogether and say that a single mojom interface declaration defines the full set of messages that can go over a single message pipe. [...]
To unsubscribe from this group and stop receiving emails from it, send an email to mojo-dev+u...@chromium.org.
Why not go the next step and make the calls synchronous? If asynchrony is needed, the client can spawn multiple threads and have each one open a new message pipe to the server. This dramatically simplifies the programming model for the common case, and is only inefficient if you imagine a world where hundreds of threads in the caller want to call the same service.
On Tue, Dec 16, 2014 at 2:16 PM, Darin Fisher <da...@chromium.org> wrote:It might be nice to put this in a document linked to from the mojo docs site (go/mojo-docs). That way we can comment on it directly.That's a really good idea! https://groups.google.com/a/chromium.org/d/msg/mojo-dev/eqsG0aeALLo/Klovi1HJAtsJ is world-readable and mojo...@chromium.org has comment access.As you note, we talked a lot about making this change back when we introduced replies, and I even mostly hacked up a CL to implement it. It started to feel heavy weight, and yeah, the FIFO issue was an impediment. Maybe my approach felt heavyweight because I forced people to add their own setClient methods to their mojom interfaces. Maybe if we automated that part of it it wouldn't feel so bad.I think the ordering problem you describe also exists to an extent in a world with only replies. Imagine if you call two methods sequentially and each generates a reply. If you want to wait synchronously just for the second reply, you cannot do so without first handling the reply to the first method. This means WaitForIncomingMethodCall is still potentially confusing in what side effect it produces.Actually we don't define the order in which replies need to be invoked at the mojom level. I.e. if I haveinterface Foo {One() => (int reply);Two() => (int reply);};and I invoke One() then Two(), there's no requirement at the mojom level that the implementation of Foo responds in that order. It may hold on to the callback from method One() until it receives Two() or later.
However, if I make those calls then I do know that the incoming methods are the replies to those two methods. I can then either make the calls one at a time to enforce fully serialized order or rely on higher-level promises in the contract with Foo to know a method order. The thing that my proposal will ensure is that the replies will be the only thing that the class dealing with the endpoint has to deal with. That's a much simpler problem.
Another thing: The model of Foo and FooClient is really meant to mimic what people do in idiomatic C++. They often have a class Foo with a client interface that they set on Foo to receive asynchronous notifications. Foo methods can also take base::Callback<>, etc. When you just think about straight C++ usage like this in Chromium, you'll see that we don't struggle with the ordering problems that much, and so maybe the struggles we experience with Mojom are not as bad as they may sometimes seem?I don't agree that this pattern is really idiomatic C++, or that having explicit state machines in Chromium is easy to deal with. On the first point, in C++ there's a growing trend to use fibers / {cooperative,lightweight,green} threads to deal with concurrent programming since the model is much simpler than fully asynchronous state machine advancement. We write code in the complicated way in Chromium because the IPC system doesn't really give us much choice - we are going to get a large set of incoming messages and need to do something with them. I'd also argue that we have piles and piles of complexity and bugs related to message ordering especially whenever anything synchronous is added to the mix. See any of the NPAPI deadlocks or difficult pepper ordering issues we've had or the continuing struggles to rationally schedule different types of messages.
Mojom gives us an ability that chrome IPC does not have to separate concerns into separate interfaces, which sidesteps a lot of these problems. Thus idiomatic code in mojo will look different from idiomatic Chromium code as it is operating under a different set of constraints. Idiomatic C++ code using a coroutine library (or Go code or C# code with async+await or whatever) looks very different from Chromium IPC handling logic. I'd argue it's much simpler to write and reason about as well.- James
By the way, I'm not clear why the Client interface causes problems for synchronous programming.
Maybe we just need a variant of WaitForIncomingMethodCall that waits for the next reply instead?Imagine you have a C++ class like this:class Foo {public:Foo(FooClient* client);void DoStuff();};During the call to DoStuff(), it could be reasonable for a method on FooClient to be invoke before DoStuff() returns.
If you had a variant of WaitForIncomingMethodCall that was instead WaitForIncomingReply, then you could model the above just fine using Mojom.Maybe the above is the simpler primitive, and then the decision about when to use a client and when not to use a client is more of a question of style just as it is in normal code?
To unsubscribe from this group and stop receiving emails from it, send an email to mojo-dev+u...@chromium.org.
Not a nested loop exactly, but it would dispatch Client method calls.
Ok, I'm convinced. I agree that "wait for next reply" would result in seemingly random Client calls running while blocked, and that means undesirable complexity.
We don't actually have that many client interfaces. We will need to be careful about how we eliminate these. Blindly converting them to use a separate pipe would be dangerous. We need to make sure that doesn't create ordering problems.
-Darin
May be this has already been answered in the thread.How should a pure listener be implemented in the new scheme?In the current scheme the listener is generally implemented as a Client of the Service hosting the state. The listener connects to the service and registers itself as client.In the new scheme, since the service cannot call the client directly, we need to switch the role of Client and Service. Now the service hosting the state needs to find and connect to all interested listeners. Is this is correct? How can the service be expected to find all the listeners?
May be this has already been answered in the thread.How should a pure listener be implemented in the new scheme?In the current scheme the listener is generally implemented as a Client of the Service hosting the state. The listener connects to the service and registers itself as client.
In the new scheme, since the service cannot call the client directly, we need to switch the role of Client and Service. Now the service hosting the state needs to find and connect to all interested listeners. Is this is correct? How can the service be expected to find all the listeners?If the proposal is just about adding an extra message pipe for the client interface, then it should be OK. Although in pure listener cases where you do not call any function on the service, just listen, you would end up with two pipes where one pipe is unused.
To unsubscribe from this group and stop receiving emails from it, send an email to mojo-dev+u...@chromium.org.
To unsubscribe from this group and stop receiving emails from it, send an email to mojo-dev+u...@chromium.org.
There's an interesting problem here. In a world without clients, if the NativeViewport is destroyed before a listener is added, does it have to wait for a listener to be added? If the viewport had already been destroyed, then maybe the caller would see an error upon adding the listener because the viewport's message pipe had already been closed.
Note that even if the NV had already been destroyed, the caller may or may not see an error on trying to add a listener, unless adding a listener results in a response being sent. (Regardless, the caller would eventually notice the listener pipe being closed.)
There's an interesting problem here. In a world without clients, if the NativeViewport is destroyed before a listener is added, does it have to wait for a listener to be added? If the viewport had already been destroyed, then maybe the caller would see an error upon adding the listener because the viewport's message pipe had already been closed.Note quite as nice as being guaranteed that the NativeViewportClient's OnDestroyed() method runs before the viewport closes its message pipe down. Assuming that's true now.
On Tue, Dec 16, 2014 at 2:52 PM, James Robinson <jam...@chromium.org> wrote:On Tue, Dec 16, 2014 at 2:16 PM, Darin Fisher <da...@chromium.org> wrote:It might be nice to put this in a document linked to from the mojo docs site (go/mojo-docs). That way we can comment on it directly.That's a really good idea! https://groups.google.com/a/chromium.org/d/msg/mojo-dev/eqsG0aeALLo/Klovi1HJAtsJ is world-readable and mojo...@chromium.org has comment access.As you note, we talked a lot about making this change back when we introduced replies, and I even mostly hacked up a CL to implement it. It started to feel heavy weight, and yeah, the FIFO issue was an impediment. Maybe my approach felt heavyweight because I forced people to add their own setClient methods to their mojom interfaces. Maybe if we automated that part of it it wouldn't feel so bad.I think the ordering problem you describe also exists to an extent in a world with only replies. Imagine if you call two methods sequentially and each generates a reply. If you want to wait synchronously just for the second reply, you cannot do so without first handling the reply to the first method. This means WaitForIncomingMethodCall is still potentially confusing in what side effect it produces.Actually we don't define the order in which replies need to be invoked at the mojom level. I.e. if I haveinterface Foo {One() => (int reply);Two() => (int reply);};and I invoke One() then Two(), there's no requirement at the mojom level that the implementation of Foo responds in that order. It may hold on to the callback from method One() until it receives Two() or later.Right, in such cases it is just all the more likely that the client could be confused by what next incoming method call to expect.It seems like there is still a documentation requirement unless you truly invoke one method at a time, waiting for its callback to run before proceeding to the next method.However, if I make those calls then I do know that the incoming methods are the replies to those two methods. I can then either make the calls one at a time to enforce fully serialized order or rely on higher-level promises in the contract with Foo to know a method order. The thing that my proposal will ensure is that the replies will be the only thing that the class dealing with the endpoint has to deal with. That's a much simpler problem.Hmm...I agree it makes synchronous RPCs simpler since it better mimics function calls.
Yes, that's a valid issue. One option would be to put the async and sync methods on different interfaces (different pipes). This means changing the API, which may of course not be ideal. If you wanted to mimic Chrome IPC style sync calls, then you would need a way to defer other incoming messages while waiting for a particular incoming message. That is something we could invent if desired.
-Darin
To unsubscribe from this group and stop receiving emails from it, send an email to mojo-dev+u...@chromium.org.
From a given thread, mixing sync and async calls is dicey.
In particular, making a sync call when an async call is pending (with the callback bound to the same thread) should probably be prohibited.
But in the new universe, we'll probably have to track the number of outstanding calls (i.e., responses still pending) anyway, so this bad condition should be easy to detect.In a thread-safe version of the new universe, this should be done on a per-thread basis; it should be fine for one thread to make async calls on a given message pipe while another thread makes sync calls on the same message pipe.WaitForIncomingMethodCall() should at least be renamed, and made to die if there's more than one response pending (for the calling thread). Better yet would be to generate synchronous wrappers, so that you could just do something like:foo_proxy->SyncTwo(&result);(probably "connection" errors should be reported synchronously also). "SyncTwo" would die if there's already a response pending (for the calling thread).("SyncTwo" is probably a poor way to do it, but hopefully you get the gist. Maybe you'd wrap the proxy instead, and do something like "Sync(foo_proxy)->Two(&result);".)