Use Mojo pipes to signal sync IPC events
This transitions legacy sync IPC to use Mojo message pipe handles
for all of its sync event waiting. This is a necessary precursor to
mixing sync legacy IPC with sync Mojo IPC, and is also required to
support correct FIFO between ChannelProxy and Mojo Channel
associated interfaces.
Specifically:
- Introduces a new IPC::MojoEvent type which is a WaitableEvent-like
interface around a local message pipe.
- Moves mojo::SyncHandleRegistry out of internal bindings API and
exposes it publicly.
- Replaces most uses of WaitableEvent with MojoEvent for sync IPC
- Replaces all use of WaitableEvent::WaitMany for sync IPC
with mojo::SyncHandleRegistry::WatchAllHandles.
- Cleans up some unnecessary complexity in SyncMessage since
pump_messages_event() was only being used with a single
global event that's always signaled.
The system's behavior should be effectively unchanged by this CL,
but legacy sync IPC and mojo sync IPC can now be mixed freely.
BUG=612500
CQ_INCLUDE_TRYBOTS=tryserver.chromium.linux:linux_optional_gpu_tests_rel;tryserver.chromium.mac:mac_optional_gpu_tests_rel;tryserver.chromium.win:win_optional_gpu_tests_rel
Review-Url: https://codereview.chromium.org/2033243003
Cr-Commit-Position: refs/heads/master@{#399848}
diff --git a/ipc/ipc_sync_channel.cc b/ipc/ipc_sync_channel.cc
index 6485139..570647b 100644
--- a/ipc/ipc_sync_channel.cc
+++ b/ipc/ipc_sync_channel.cc
@@ -13,9 +13,10 @@
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/logging.h"
+#include "base/macros.h"
#include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
-#include "base/synchronization/waitable_event_watcher.h"
#include "base/threading/thread_local.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
@@ -23,12 +24,84 @@
#include "ipc/ipc_logging.h"
#include "ipc/ipc_message_macros.h"
#include "ipc/ipc_sync_message.h"
+#include "ipc/mojo_event.h"
+#include "mojo/public/cpp/bindings/sync_handle_registry.h"
using base::TimeDelta;
using base::TimeTicks;
using base::WaitableEvent;
namespace IPC {
+
+namespace {
+
+// A lazy thread-local Mojo Event which is always signaled. Used to wake up the
+// sync waiter when a SyncMessage requires the MessageLoop to be pumped while
+// waiting for a reply. This object is created lazily and ref-counted so it can
+// be cleaned up when no longer in use.
+class PumpMessagesEvent {
+ public:
+ // Acquires the event for this thread. Creates a new instance if necessary.
+ static PumpMessagesEvent* Acquire() {
+ PumpMessagesEvent* pump_messages_event = g_event_.Pointer()->Get();
+ if (!pump_messages_event) {
+ pump_messages_event = new PumpMessagesEvent;
+ pump_messages_event->event_.Signal();
+ g_event_.Pointer()->Set(pump_messages_event);
+ }
+ pump_messages_event->ref_count_++;
+ return pump_messages_event;
+ }
+
+ // Releases a handle to this event. There must be a 1:1 correspondence between
+ // calls to Acquire() and calls to Release().
+ static void Release() {
+ PumpMessagesEvent* pump_messages_event = g_event_.Pointer()->Get();
+ DCHECK(pump_messages_event);
+ DCHECK_GT(pump_messages_event->ref_count_, 0);
+ pump_messages_event->ref_count_--;
+ if (!pump_messages_event->ref_count_) {
+ g_event_.Pointer()->Set(nullptr);
+ delete pump_messages_event;
+ }
+ }
+
+ const mojo::Handle& GetHandle() const { return event_.GetHandle(); }
+
+ private:
+ PumpMessagesEvent() {}
+ ~PumpMessagesEvent() {}
+
+ int ref_count_ = 0;
+ MojoEvent event_;
+
+ static base::LazyInstance<base::ThreadLocalPointer<PumpMessagesEvent>>
+ g_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(PumpMessagesEvent);
+};
+
+// A generic callback used when watching handles synchronously. Sets |*signal|
+// to true. Also sets |*error| to true in case of an error.
+void OnSyncHandleReady(bool* signal, bool* error, MojoResult result) {
+ *signal = true;
+ *error = result != MOJO_RESULT_OK;
+}
+
+// A ReadyCallback for use with mojo::Watcher. Ignores the result (DCHECKs, but
+// is only used in cases where failure should be impossible) and runs
+// |callback|.
+void RunOnHandleReady(const base::Closure& callback, MojoResult result) {
+ DCHECK(result == MOJO_RESULT_OK || result == MOJO_RESULT_ABORTED);
+ if (result == MOJO_RESULT_OK)
+ callback.Run();
+}
+
+} // namespace
+
+base::LazyInstance<base::ThreadLocalPointer<PumpMessagesEvent>>
+PumpMessagesEvent::g_event_ = LAZY_INSTANCE_INITIALIZER;
+
// When we're blocked in a Send(), we need to process incoming synchronous
// messages right away because it could be blocking our reply (either
// directly from the same object we're calling, or indirectly through one or
@@ -155,7 +228,7 @@
}
}
- WaitableEvent* dispatch_event() { return &dispatch_event_; }
+ MojoEvent* dispatch_event() { return &dispatch_event_; }
base::SingleThreadTaskRunner* listener_task_runner() {
return listener_task_runner_.get();
}
@@ -177,11 +250,11 @@
}
}
- base::WaitableEventWatcher* top_send_done_watcher() {
+ mojo::Watcher* top_send_done_watcher() {
return top_send_done_watcher_;
}
- void set_top_send_done_watcher(base::WaitableEventWatcher* watcher) {
+ void set_top_send_done_watcher(mojo::Watcher* watcher) {
top_send_done_watcher_ = watcher;
}
@@ -192,8 +265,6 @@
// as manual reset.
ReceivedSyncMsgQueue()
: message_queue_version_(0),
- dispatch_event_(base::WaitableEvent::ResetPolicy::MANUAL,
- base::WaitableEvent::InitialState::NOT_SIGNALED),
listener_task_runner_(base::ThreadTaskRunnerHandle::Get()),
task_pending_(false),
listener_count_(0),
@@ -214,19 +285,19 @@
std::vector<QueuedMessage> received_replies_;
- // Set when we got a synchronous message that we must respond to as the
+ // Signaled when we get a synchronous message that we must respond to, as the
// sender needs its reply before it can reply to our original synchronous
// message.
- WaitableEvent dispatch_event_;
+ MojoEvent dispatch_event_;
scoped_refptr<base::SingleThreadTaskRunner> listener_task_runner_;
base::Lock message_lock_;
bool task_pending_;
int listener_count_;
- // The current send done event watcher for this thread. Used to maintain
- // a local global stack of send done watchers to ensure that nested sync
+ // The current send done handle watcher for this thread. Used to maintain
+ // a thread-local stack of send done watchers to ensure that nested sync
// message loops complete correctly.
- base::WaitableEventWatcher* top_send_done_watcher_;
+ mojo::Watcher* top_send_done_watcher_;
};
base::LazyInstance<base::ThreadLocalPointer<SyncChannel::ReceivedSyncMsgQueue> >
@@ -262,8 +333,7 @@
// Send completes, so the event will need to remain set.
PendingSyncMsg pending(
SyncMessage::GetMessageId(*sync_msg), sync_msg->GetReplyDeserializer(),
- new WaitableEvent(base::WaitableEvent::ResetPolicy::MANUAL,
- base::WaitableEvent::InitialState::NOT_SIGNALED));
+ new MojoEvent);
base::AutoLock auto_lock(deserializers_lock_);
deserializers_.push_back(pending);
}
@@ -275,7 +345,7 @@
PendingSyncMsg msg = deserializers_.back();
delete msg.deserializer;
delete msg.done_event;
- msg.done_event = NULL;
+ msg.done_event = nullptr;
deserializers_.pop_back();
result = msg.send_result;
}
@@ -292,12 +362,12 @@
return result;
}
-WaitableEvent* SyncChannel::SyncContext::GetSendDoneEvent() {
+MojoEvent* SyncChannel::SyncContext::GetSendDoneEvent() {
base::AutoLock auto_lock(deserializers_lock_);
return deserializers_.back().done_event;
}
-WaitableEvent* SyncChannel::SyncContext::GetDispatchEvent() {
+MojoEvent* SyncChannel::SyncContext::GetDispatchEvent() {
return received_sync_msgs_->dispatch_event();
}
@@ -321,7 +391,7 @@
DVLOG(1) << "Received error reply";
}
- base::WaitableEvent* done_event = deserializers_.back().done_event;
+ MojoEvent* done_event = deserializers_.back().done_event;
TRACE_EVENT_FLOW_BEGIN0(
TRACE_DISABLED_BY_DEFAULT("ipc.flow"),
"SyncChannel::SyncContext::TryToUnblockListener", done_event);
@@ -367,7 +437,7 @@
void SyncChannel::SyncContext::OnChannelOpened() {
shutdown_watcher_.StartWatching(
shutdown_event_,
- base::Bind(&SyncChannel::SyncContext::OnWaitableEventSignaled,
+ base::Bind(&SyncChannel::SyncContext::OnShutdownEventSignaled,
base::Unretained(this)));
Context::OnChannelOpened();
}
@@ -390,21 +460,12 @@
}
}
-void SyncChannel::SyncContext::OnWaitableEventSignaled(WaitableEvent* event) {
- if (event == shutdown_event_) {
- // Process shut down before we can get a reply to a synchronous message.
- // Cancel pending Send calls, which will end up setting the send done event.
- CancelPendingSends();
- } else {
- // We got the reply, timed out or the process shutdown.
- DCHECK_EQ(GetSendDoneEvent(), event);
- base::MessageLoop::current()->QuitNow();
- }
-}
+void SyncChannel::SyncContext::OnShutdownEventSignaled(WaitableEvent* event) {
+ DCHECK_EQ(event, shutdown_event_);
-base::WaitableEventWatcher::EventCallback
- SyncChannel::SyncContext::MakeWaitableEventCallback() {
- return base::Bind(&SyncChannel::SyncContext::OnWaitableEventSignaled, this);
+ // Process shut down before we can get a reply to a synchronous message.
+ // Cancel pending Send calls, which will end up setting the send done event.
+ CancelPendingSends();
}
// static
@@ -448,6 +509,10 @@
const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
WaitableEvent* shutdown_event)
: ChannelProxy(new SyncContext(listener, ipc_task_runner, shutdown_event)) {
+ // Keep a thread-local PumpMessagesEvent alive at least as long as any
+ // SyncChannel exists. This is balanced in the SyncChannel destructor below.
+ PumpMessagesEvent::Acquire();
+
// The current (listener) thread must be distinct from the IPC thread, or else
// sending synchronous messages will deadlock.
DCHECK_NE(ipc_task_runner.get(), base::ThreadTaskRunnerHandle::Get().get());
@@ -455,6 +520,7 @@
}
SyncChannel::~SyncChannel() {
+ PumpMessagesEvent::Release();
}
void SyncChannel::SetRestrictDispatchChannelGroup(int group) {
@@ -495,14 +561,14 @@
}
SyncMessage* sync_msg = static_cast<SyncMessage*>(message);
+ bool pump_messages = sync_msg->ShouldPumpMessages();
context->Push(sync_msg);
- WaitableEvent* pump_messages_event = sync_msg->pump_messages_event();
ChannelProxy::Send(message);
// Wait for reply, or for any other incoming synchronous messages.
// *this* might get deleted, so only call static functions at this point.
- WaitForReply(context.get(), pump_messages_event);
+ WaitForReply(context.get(), pump_messages);
TRACE_EVENT_FLOW_END0(TRACE_DISABLED_BY_DEFAULT("ipc.flow"),
"SyncChannel::Send", context->GetSendDoneEvent());
@@ -510,19 +576,45 @@
return context->Pop();
}
-void SyncChannel::WaitForReply(
- SyncContext* context, WaitableEvent* pump_messages_event) {
+void SyncChannel::WaitForReply(SyncContext* context, bool pump_messages) {
context->DispatchMessages();
- while (true) {
- WaitableEvent* objects[] = {
- context->GetDispatchEvent(),
- context->GetSendDoneEvent(),
- pump_messages_event
- };
- unsigned count = pump_messages_event ? 3: 2;
- size_t result = WaitableEvent::WaitMany(objects, count);
- if (result == 0 /* dispatch event */) {
+ PumpMessagesEvent* pump_messages_event = nullptr;
+ if (pump_messages)
+ pump_messages_event = PumpMessagesEvent::Acquire();
+
+ scoped_refptr<mojo::SyncHandleRegistry> registry =
+ mojo::SyncHandleRegistry::current();
+
+ while (true) {
+ bool dispatch = false;
+ bool send_done = false;
+ bool should_pump_messages = false;
+ bool error = false;
+ registry->RegisterHandle(context->GetDispatchEvent()->GetHandle(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ base::Bind(&OnSyncHandleReady, &dispatch, &error));
+ registry->RegisterHandle(
+ context->GetSendDoneEvent()->GetHandle(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ base::Bind(&OnSyncHandleReady, &send_done, &error));
+ if (pump_messages_event) {
+ registry->RegisterHandle(
+ pump_messages_event->GetHandle(), MOJO_HANDLE_SIGNAL_READABLE,
+ base::Bind(&OnSyncHandleReady, &should_pump_messages, &error));
+ }
+
+ const bool* stop_flags[] = { &dispatch, &send_done, &should_pump_messages };
+ bool result = registry->WatchAllHandles(stop_flags, 3);
+ DCHECK(result);
+ DCHECK(!error);
+
+ registry->UnregisterHandle(context->GetDispatchEvent()->GetHandle());
+ registry->UnregisterHandle(context->GetSendDoneEvent()->GetHandle());
+ if (pump_messages_event)
+ registry->UnregisterHandle(pump_messages_event->GetHandle());
+
+ if (dispatch) {
// We're waiting for a reply, but we received a blocking synchronous
// call. We must process it or otherwise a deadlock might occur.
context->GetDispatchEvent()->Reset();
@@ -530,72 +622,72 @@
continue;
}
- if (result == 2 /* pump_messages_event */)
+ DCHECK(send_done || should_pump_messages);
+
+ if (should_pump_messages)
WaitForReplyWithNestedMessageLoop(context); // Run a nested message loop.
break;
}
+
+ if (pump_messages_event)
+ PumpMessagesEvent::Release();
}
void SyncChannel::WaitForReplyWithNestedMessageLoop(SyncContext* context) {
- base::WaitableEventWatcher send_done_watcher;
+ mojo::Watcher send_done_watcher;
ReceivedSyncMsgQueue* sync_msg_queue = context->received_sync_msgs();
- DCHECK(sync_msg_queue != NULL);
+ DCHECK_NE(sync_msg_queue, nullptr);
- base::WaitableEventWatcher* old_send_done_event_watcher =
- sync_msg_queue->top_send_done_watcher();
+ mojo::Watcher* old_watcher = sync_msg_queue->top_send_done_watcher();
+ mojo::Handle old_handle(mojo::kInvalidHandleValue);
+ mojo::Watcher::ReadyCallback old_callback;
- base::WaitableEventWatcher::EventCallback old_callback;
- base::WaitableEvent* old_event = NULL;
-
- // Maintain a local global stack of send done delegates to ensure that
- // nested sync calls complete in the correct sequence, i.e. the
- // outermost call completes first, etc.
- if (old_send_done_event_watcher) {
- old_callback = old_send_done_event_watcher->callback();
- old_event = old_send_done_event_watcher->GetWatchedEvent();
- old_send_done_event_watcher->StopWatching();
+ // Maintain a thread-local stack of watchers to ensure nested calls complete
+ // in the correct sequence, i.e. the outermost call completes first, etc.
+ if (old_watcher) {
+ old_callback = old_watcher->ready_callback();
+ old_handle = old_watcher->handle();
+ old_watcher->Cancel();
}
sync_msg_queue->set_top_send_done_watcher(&send_done_watcher);
- send_done_watcher.StartWatching(context->GetSendDoneEvent(),
- context->MakeWaitableEventCallback());
-
{
+ base::RunLoop nested_loop;
+ send_done_watcher.Start(
+ context->GetSendDoneEvent()->GetHandle(), MOJO_HANDLE_SIGNAL_READABLE,
+ base::Bind(&RunOnHandleReady, nested_loop.QuitClosure()));
+
base::MessageLoop::ScopedNestableTaskAllower allow(
base::MessageLoop::current());
- base::MessageLoop::current()->Run();
+ nested_loop.Run();
+ send_done_watcher.Cancel();
}
- sync_msg_queue->set_top_send_done_watcher(old_send_done_event_watcher);
- if (old_send_done_event_watcher && old_event) {
- old_send_done_event_watcher->StartWatching(old_event, old_callback);
- }
+ sync_msg_queue->set_top_send_done_watcher(old_watcher);
+ if (old_watcher)
+ old_watcher->Start(old_handle, MOJO_HANDLE_SIGNAL_READABLE, old_callback);
}
-void SyncChannel::OnWaitableEventSignaled(WaitableEvent* event) {
- DCHECK(event == sync_context()->GetDispatchEvent());
- // The call to DispatchMessages might delete this object, so reregister
- // the object watcher first.
- event->Reset();
- dispatch_watcher_.StartWatching(event, dispatch_watcher_callback_);
- sync_context()->DispatchMessages();
+void SyncChannel::OnDispatchHandleReady(MojoResult result) {
+ DCHECK(result == MOJO_RESULT_OK || result == MOJO_RESULT_ABORTED);
+ if (result == MOJO_RESULT_OK) {
+ sync_context()->GetDispatchEvent()->Reset();
+ sync_context()->DispatchMessages();
+ }
}
void SyncChannel::StartWatching() {
// Ideally we only want to watch this object when running a nested message
// loop. However, we don't know when it exits if there's another nested
// message loop running under it or not, so we wouldn't know whether to
- // stop or keep watching. So we always watch it, and create the event as
- // manual reset since the object watcher might otherwise reset the event
- // when we're doing a WaitMany.
- dispatch_watcher_callback_ =
- base::Bind(&SyncChannel::OnWaitableEventSignaled,
- base::Unretained(this));
- dispatch_watcher_.StartWatching(sync_context()->GetDispatchEvent(),
- dispatch_watcher_callback_);
+ // stop or keep watching. So we always watch it.
+ dispatch_watcher_.Start(sync_context()->GetDispatchEvent()->GetHandle(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ base::Bind(&SyncChannel::OnDispatchHandleReady,
+ base::Unretained(this)));
}
void SyncChannel::OnChannelInit() {