diff options
author | Hidehiko Abe <hidehiko@google.com> | 2018-04-19 20:42:46 +0900 |
---|---|---|
committer | Hidehiko Abe <hidehiko@google.com> | 2018-04-20 11:11:59 +0900 |
commit | 991e618ea063cc28060d6baa0c0d6ffa3ad29452 (patch) | |
tree | d7c62aca68333ccc7dc58e356d515dd01c734a7f | |
parent | 1e4a1e50aed7593adac78af6f319adb67f03d7bb (diff) | |
download | libmojo-991e618ea063cc28060d6baa0c0d6ffa3ad29452.tar.gz |
Uprev libmojo to r462023.
To be aligned with libchrome.
Highlights of the update:
- r461016: Support sync calls through ThreadSafeInterfacePtr
- r458684: mojo: MessageReceiver*::AcceptWithResponder() now take a unique_ptr to the responder
- r458630: Mojo C++ Bindings: Support dispatch in nested message loops
- r457994: Mojo C++ bindings: rename GetIsolatedProxy to MakeIsolatedRequest to better match other functions.
- r457856: Mojo: Move waiting APIs to public library
- r457378: Introduce MojoQueryHandleSignalsState API
Bug: 73606903
Test: Built locally. Run on DUT.
Change-Id: Id3e2f5262eb97345ed2e6b597157d594cb8b4110
218 files changed, 11846 insertions, 8395 deletions
@@ -7,10 +7,12 @@ filegroup { srcs: [ "ipc/ipc.mojom", "mojo/common/file.mojom", + "mojo/common/file_path.mojom", "mojo/common/string16.mojom", "mojo/common/text_direction.mojom", "mojo/common/time.mojom", "mojo/common/unguessable_token.mojom", + "mojo/common/values.mojom", "mojo/common/version.mojom", "mojo/public/interfaces/bindings/interface_control_messages.mojom", "mojo/public/interfaces/bindings/pipe_control_messages.mojom", @@ -19,6 +21,46 @@ filegroup { ], } +filegroup { + name: "mojo_sources", + srcs: [ + "mojo/**/*.cc", + ], + exclude_srcs: [ + // Unused in Chrome. Looks like mistakenly checked in. + // TODO(hidehiko): Remove this after the file is removed in Chrome + // repository. http://crrev.com/c/644531 + "mojo/public/cpp/system/message.cc", + + // No WTF support. + "mojo/public/cpp/bindings/lib/string_traits_wtf.cc", + + // Exclude windows/mac/ios files. + "**/*_win.cc", + "mojo/edk/system/mach_port_relay.cc", + + // Exclude js binding related files. + "mojo/edk/js/**/*", + "mojo/public/js/**/*", + + // Exclude tests. + // Note that mojo/edk/embedder/test_embedder.cc needs to be included + // for Mojo support. cf) b/62071944. + "**/*_unittest.cc", + "**/*_unittests.cc", + "**/*_perftest.cc", + "mojo/android/javatests/**/*", + "mojo/edk/system/core_test_base.cc", + "mojo/edk/system/test_utils.cc", + "mojo/edk/test/**/*", + "mojo/public/c/system/tests/**/*", + "mojo/public/cpp/bindings/tests/**/*", + "mojo/public/cpp/system/tests/**/*", + "mojo/public/cpp/test_support/**/*", + "mojo/public/tests/**/*", + ], +} + // Python in Chrome repository requires still Python 2. python_defaults { name: "libmojo_scripts", @@ -100,6 +142,9 @@ genrule { "mojo/common/file.mojom.h", "mojo/common/file.mojom-shared.h", "mojo/common/file.mojom-shared-internal.h", + "mojo/common/file_path.mojom.h", + "mojo/common/file_path.mojom-shared.h", + "mojo/common/file_path.mojom-shared-internal.h", "mojo/common/string16.mojom.h", "mojo/common/string16.mojom-shared.h", "mojo/common/string16.mojom-shared-internal.h", @@ -112,6 +157,9 @@ genrule { "mojo/common/unguessable_token.mojom.h", "mojo/common/unguessable_token.mojom-shared.h", "mojo/common/unguessable_token.mojom-shared-internal.h", + "mojo/common/values.mojom.h", + "mojo/common/values.mojom-shared.h", + "mojo/common/values.mojom-shared-internal.h", "mojo/common/version.mojom.h", "mojo/common/version.mojom-shared.h", "mojo/common/version.mojom-shared-internal.h", @@ -248,84 +296,7 @@ cc_library_shared { "ipc/ipc_mojo_message_helper.cc", "ipc/ipc_mojo_param_traits.cc", "ipc/ipc_platform_file_attachment_posix.cc", - "mojo/android/system/base_run_loop.cc", - "mojo/android/system/core_impl.cc", - "mojo/android/system/watcher_impl.cc", - "mojo/common/common_custom_types_struct_traits.cc", - "mojo/edk/embedder/connection_params.cc", - "mojo/edk/embedder/embedder.cc", - "mojo/edk/embedder/entrypoints.cc", - "mojo/edk/embedder/platform_channel_pair.cc", - "mojo/edk/embedder/platform_channel_pair_posix.cc", - "mojo/edk/embedder/platform_channel_utils_posix.cc", - "mojo/edk/embedder/platform_handle.cc", - "mojo/edk/embedder/platform_handle_utils_posix.cc", - "mojo/edk/embedder/platform_shared_buffer.cc", - "mojo/edk/embedder/pending_process_connection.cc", - "mojo/edk/embedder/test_embedder.cc", - "mojo/edk/system/awakable_list.cc", - "mojo/edk/system/broker_host.cc", - "mojo/edk/system/broker_posix.cc", - "mojo/edk/system/channel.cc", - "mojo/edk/system/channel_posix.cc", - "mojo/edk/system/configuration.cc", - "mojo/edk/system/core.cc", - "mojo/edk/system/data_pipe_consumer_dispatcher.cc", - "mojo/edk/system/data_pipe_control_message.cc", - "mojo/edk/system/data_pipe_producer_dispatcher.cc", - "mojo/edk/system/dispatcher.cc", - "mojo/edk/system/handle_table.cc", - "mojo/edk/system/mapping_table.cc", - "mojo/edk/system/message_for_transit.cc", - "mojo/edk/system/message_pipe_dispatcher.cc", - "mojo/edk/system/node_channel.cc", - "mojo/edk/system/node_controller.cc", - "mojo/edk/system/platform_handle_dispatcher.cc", - "mojo/edk/system/ports/event.cc", - "mojo/edk/system/ports/message.cc", - "mojo/edk/system/ports/message_queue.cc", - "mojo/edk/system/ports/name.cc", - "mojo/edk/system/ports/node.cc", - "mojo/edk/system/ports/port.cc", - "mojo/edk/system/ports/port_ref.cc", - "mojo/edk/system/ports_message.cc", - "mojo/edk/system/request_context.cc", - "mojo/edk/system/shared_buffer_dispatcher.cc", - "mojo/edk/system/wait_set_dispatcher.cc", - "mojo/edk/system/waiter.cc", - "mojo/edk/system/watcher.cc", - "mojo/edk/system/watcher_set.cc", - "mojo/public/c/system/thunks.cc", - "mojo/public/cpp/bindings/lib/array_internal.cc", - "mojo/public/cpp/bindings/lib/associated_group.cc", - "mojo/public/cpp/bindings/lib/associated_group_controller.cc", - "mojo/public/cpp/bindings/lib/binding_state.cc", - "mojo/public/cpp/bindings/lib/connector.cc", - "mojo/public/cpp/bindings/lib/control_message_handler.cc", - "mojo/public/cpp/bindings/lib/control_message_proxy.cc", - "mojo/public/cpp/bindings/lib/filter_chain.cc", - "mojo/public/cpp/bindings/lib/fixed_buffer.cc", - "mojo/public/cpp/bindings/lib/interface_endpoint_client.cc", - "mojo/public/cpp/bindings/lib/message.cc", - "mojo/public/cpp/bindings/lib/message_buffer.cc", - "mojo/public/cpp/bindings/lib/message_builder.cc", - "mojo/public/cpp/bindings/lib/message_header_validator.cc", - "mojo/public/cpp/bindings/lib/multiplex_router.cc", - "mojo/public/cpp/bindings/lib/native_struct.cc", - "mojo/public/cpp/bindings/lib/native_struct_data.cc", - "mojo/public/cpp/bindings/lib/native_struct_serialization.cc", - "mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc", - "mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc", - "mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc", - "mojo/public/cpp/bindings/lib/serialization_context.cc", - "mojo/public/cpp/bindings/lib/sync_handle_registry.cc", - "mojo/public/cpp/bindings/lib/sync_handle_watcher.cc", - "mojo/public/cpp/bindings/lib/validation_context.cc", - "mojo/public/cpp/bindings/lib/validation_errors.cc", - "mojo/public/cpp/bindings/lib/validation_util.cc", - "mojo/public/cpp/system/buffer.cc", - "mojo/public/cpp/system/platform_handle.cc", - "mojo/public/cpp/system/watcher.cc", + ":mojo_sources", ], cflags: [ diff --git a/ipc/ipc_sync_message.h b/ipc/ipc_sync_message.h index ed5204f..7f05551 100644 --- a/ipc/ipc_sync_message.h +++ b/ipc/ipc_sync_message.h @@ -17,10 +17,13 @@ #include "build/build_config.h" #include "ipc/ipc_message.h" +namespace base { +class WaitableEvent; +} + namespace IPC { class MessageReplyDeserializer; -class MojoEvent; class IPC_EXPORT SyncMessage : public Message { public: @@ -90,12 +93,12 @@ class IPC_EXPORT MessageReplyDeserializer { // When sending a synchronous message, this structure contains an object // that knows how to deserialize the response. struct PendingSyncMsg { - PendingSyncMsg(int id, MessageReplyDeserializer* d, MojoEvent* e) - : id(id), deserializer(d), done_event(e), send_result(false) { } + PendingSyncMsg(int id, MessageReplyDeserializer* d, base::WaitableEvent* e) + : id(id), deserializer(d), done_event(e), send_result(false) {} int id; MessageReplyDeserializer* deserializer; - MojoEvent* done_event; + base::WaitableEvent* done_event; bool send_result; }; diff --git a/mojo/README.md b/mojo/README.md index 237d1d4..e1e7583 100644 --- a/mojo/README.md +++ b/mojo/README.md @@ -1,7 +1,142 @@ -Mojo -==== +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo +This document is a subset of the [Mojo documentation](/mojo). -[Mojo](https://www.chromium.org/developers/design-documents/mojo) is an IPC & -binding mechanism for Chromium. +[TOC] + +## Getting Started With Mojo + +To get started using Mojo in applications which already support it (such as +Chrome), the fastest path forward will be to look at the bindings documentation +for your language of choice ([**C++**](#C_Bindings), +[**JavaScript**](#JavaScript-Bindings), or [**Java**](#Java-Bindings)) as well +as the documentation for the +[**Mojom IDL and bindings generator**](/mojo/public/tools/bindings). + +If you're looking for information on creating and/or connecting to services, see +the top-level [Services documentation](/services). + +For specific details regarding the conversion of old things to new things, check +out [Converting Legacy Chrome IPC To Mojo](/ipc). + +## System Overview + +Mojo is a layered collection of runtime libraries providing a platform-agnostic +abstraction of common IPC primitives, a message IDL format, and a bindings +library with code generation for multiple target languages to facilitate +convenient message passing across arbitrary inter- and intra-process boundaries. + +The documentation here is segmented according to the different isolated layers +and libraries comprising the system. The basic hierarchy of features is as +follows: + +![Mojo Library Layering: EDK on bottom, different language bindings on top, public system support APIs in the middle](https://docs.google.com/drawings/d/1aNbLfF-fejgzxCxH_b8xAaCVvftW8BGTH_EHD7nvU1w/pub?w=570&h=327) + +## Embedder Development Kit (EDK) +Every process to be interconnected via Mojo IPC is called a **Mojo embedder** +and needs to embed the +[**Embedder Development Kit (EDK)**](/mojo/edk/embedder) library. The EDK +exposes the means for an embedder to physically connect one process to another +using any supported native IPC primitive (*e.g.,* a UNIX domain socket or +Windows named pipe) on the host platform. + +Details regarding where and how an application process actually embeds and +configures the EDK are generaly hidden from the rest of the application code, +and applications instead use the public System and Bindings APIs to get things +done within processes that embed Mojo. + +## C System API +Once the EDK is initialized within a process, the public +[**C System API**](/mojo/public/c/system) is usable on any thread for the +remainder of the process's lifetime. This is a lightweight API with a relatively +small (and eventually stable) ABI. Typically this API is not used directly, but +it is the foundation upon which all remaining upper layers are built. It exposes +the fundamental capabilities to create and interact with various types of Mojo +handles including **message pipes**, **data pipes**, and **shared buffers**. + +## High-Level System APIs + +There is a relatively small, higher-level system API for each supported +language, built upon the low-level C API. Like the C API, direct usage of these +system APIs is rare compared to the bindings APIs, but it is sometimes desirable +or necessary. + +### C++ +The [**C++ System API**](/mojo/public/cpp/system) provides a layer of +C++ helper classes and functions to make safe System API usage easier: +strongly-typed handle scopers, synchronous waiting operations, system handle +wrapping and unwrapping helpers, common handle operations, and utilities for +more easily watching handle state changes. + +### JavaScript +The [**JavaScript APIs**](/mojo/public/js) are WIP. :) + +### Java +The [**Java System API**](/mojo/public/java/system) provides helper classes for +working with Mojo primitives, covering all basic functionality of the low-level +C API. + +## High-Level Bindings APIs +Typically developers do not use raw message pipe I/O directly, but instead +define some set of interfaces which are used to generate code that message pipe +usage feel like a more idiomatic method-calling interface in the target +language of choice. This is the bindings layer. + +### Mojom IDL and Bindings Generator +Interfaces are defined using the [**Mojom IDL**](/mojo/public/tools/bindings), +which can be fed to the [**bindings generator**](/mojo/public/tools/bindings) to +generate code in various supported languages. Generated code manages +serialization and deserialization of messages between interface clients and +implementations, simplifying the code -- and ultimately hiding the message pipe +-- on either side of an interface connection. + +### C++ Bindings +By far the most commonly used API defined by Mojo, the +[**C++ Bindings API**](/mojo/public/cpp/bindings) exposes a robust set of +features for interacting with message pipes via generated C++ bindings code, +including support for sets of related bindings endpoints, associated interfaces, +nested sync IPC, versioning, bad-message reporting, arbitrary message filter +injection, and convenient test facilities. + +### JavaScript Bindings +The [**JavaScript APIs**](/mojo/public/js) are WIP. :) + +### Java Bindings +The [**Java Bindings API**](/mojo/public/java/bindings) provides helper classes +for working with Java code emitted by the bindings generator. + +## FAQ + +### Why not protobuf? Why a new thing? +There are number of potentially decent answers to this question, but the +deal-breaker is that a useful IPC mechanism must support transfer of native +object handles (*e.g.* file descriptors) across process boundaries. Other +non-new IPC things that do support this capability (*e.g.* D-Bus) have their own +substantial deficiencies. + +### Are message pipes expensive? +No. As an implementation detail, creating a message pipe is essentially +generating two random numbers and stuffing them into a hash table, along with a +few tiny heap allocations. + +### So really, can I create like, thousands of them? +Yes! Nobody will mind. Create millions if you like. (OK but maybe don't.) + +### Can I use in-process message pipes? +Yes, and message pipe usage is identical regardless of whether the pipe actually +crosses a process boundary -- in fact this detail is intentionally obscured. + +Message pipes which don't cross a process boundary are efficient: sent messages +are never copied, and a write on one end will synchronously modify the message +queue on the other end. When working with generated C++ bindings, for example, +the net result is that an `InterfacePtr` on one thread sending a message to a +`Binding` on another thread (or even the same thread) is effectively a +`PostTask` to the `Binding`'s `TaskRunner` with the added -- but often small -- +costs of serialization, deserialization, validation, and some internal routing +logic. + +### What about ____? + +Please post questions to +[`chromium-mojo@chromium.org`](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-mojo)! +The list is quite responsive. -TODO(rockot): Describe the important subdirectories.
\ No newline at end of file diff --git a/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java b/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java index 6783c09..1f8de94 100644 --- a/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java +++ b/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java @@ -5,7 +5,7 @@ package org.chromium.mojo; import org.chromium.mojo.system.Core; -import org.chromium.mojo.system.Core.WaitResult; +import org.chromium.mojo.system.Core.HandleSignalsState; import org.chromium.mojo.system.DataPipe; import org.chromium.mojo.system.DataPipe.ConsumerHandle; import org.chromium.mojo.system.DataPipe.ProducerHandle; @@ -35,14 +35,11 @@ public class HandleMock implements UntypedHandle, MessagePipeHandle, } /** - * @see Handle#wait(Core.HandleSignals, long) + * @see Handle#querySignalsState() */ @Override - public WaitResult wait(Core.HandleSignals signals, long deadline) { - // Do nothing. - WaitResult result = new WaitResult(); - result.setMojoResult(MojoResult.OK); - return result; + public HandleSignalsState querySignalsState() { + return null; } /** diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java index 5affb8f..6aa1726 100644 --- a/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java @@ -12,7 +12,6 @@ import org.chromium.mojo.bindings.BindingsTestUtils.CapturingErrorHandler; import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiverWithResponder; import org.chromium.mojo.system.Core; import org.chromium.mojo.system.Core.HandleSignals; -import org.chromium.mojo.system.Core.WaitResult; import org.chromium.mojo.system.Handle; import org.chromium.mojo.system.MessagePipeHandle; import org.chromium.mojo.system.MojoResult; @@ -227,8 +226,6 @@ public class RouterTest extends MojoTestCase { // Confirm that the pipe was closed on the Router side. HandleSignals closedFlag = HandleSignals.none().setPeerClosed(true); - WaitResult result = mHandle.wait(closedFlag, 0); - assertEquals(MojoResult.OK, result.getMojoResult()); - assertEquals(closedFlag, result.getHandleSignalsState().getSatisfiedSignals()); + assertEquals(closedFlag, mHandle.querySignalsState().getSatisfiedSignals()); } } diff --git a/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java b/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java index 77a9bda..5120198 100644 --- a/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java +++ b/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java @@ -9,9 +9,6 @@ import android.support.test.filters.SmallTest; import org.chromium.mojo.MojoTestCase; import org.chromium.mojo.system.Core; import org.chromium.mojo.system.Core.HandleSignals; -import org.chromium.mojo.system.Core.HandleSignalsState; -import org.chromium.mojo.system.Core.WaitManyResult; -import org.chromium.mojo.system.Core.WaitResult; import org.chromium.mojo.system.DataPipe; import org.chromium.mojo.system.Handle; import org.chromium.mojo.system.InvalidHandle; @@ -30,7 +27,6 @@ import java.util.List; import java.util.Random; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; /** * Testing the core API. @@ -76,22 +72,6 @@ public class CoreImplTest extends MojoTestCase { mHandlesToClose.add(handles.second); } - /** - * Runnable that will close the given handle. - */ - private static class CloseHandle implements Runnable { - private Handle mHandle; - - CloseHandle(Handle handle) { - mHandle = handle; - } - - @Override - public void run() { - mHandle.close(); - } - } - private static void checkSendingMessage(MessagePipeHandle in, MessagePipeHandle out) { Random random = new Random(); @@ -184,46 +164,6 @@ public class CoreImplTest extends MojoTestCase { } /** - * Testing {@link Core#waitMany(List, long)}. - */ - @SmallTest - public void testWaitMany() { - Core core = CoreImpl.getInstance(); - Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); - addHandlePairToClose(handles); - - // Test waiting on handles of a newly created message pipe - each should be writable, but - // not readable. - List<Pair<Handle, Core.HandleSignals>> handlesToWaitOn = - new ArrayList<Pair<Handle, Core.HandleSignals>>(); - handlesToWaitOn.add( - new Pair<Handle, Core.HandleSignals>(handles.second, Core.HandleSignals.READABLE)); - handlesToWaitOn.add( - new Pair<Handle, Core.HandleSignals>(handles.first, Core.HandleSignals.WRITABLE)); - WaitManyResult result = core.waitMany(handlesToWaitOn, 0); - assertEquals(MojoResult.OK, result.getMojoResult()); - assertEquals(1, result.getHandleIndex()); - for (HandleSignalsState state : result.getSignalStates()) { - assertEquals(HandleSignals.WRITABLE, state.getSatisfiedSignals()); - assertEquals(ALL_SIGNALS, state.getSatisfiableSignals()); - } - - // Same test, but swap the handles around. - handlesToWaitOn.clear(); - handlesToWaitOn.add( - new Pair<Handle, Core.HandleSignals>(handles.first, Core.HandleSignals.WRITABLE)); - handlesToWaitOn.add( - new Pair<Handle, Core.HandleSignals>(handles.second, Core.HandleSignals.READABLE)); - result = core.waitMany(handlesToWaitOn, 0); - assertEquals(MojoResult.OK, result.getMojoResult()); - assertEquals(0, result.getHandleIndex()); - for (HandleSignalsState state : result.getSignalStates()) { - assertEquals(HandleSignals.WRITABLE, state.getSatisfiedSignals()); - assertEquals(ALL_SIGNALS, state.getSatisfiableSignals()); - } - } - - /** * Testing that Core can be retrieved from a handle. */ @SmallTest @@ -274,53 +214,14 @@ public class CoreImplTest extends MojoTestCase { Core core = CoreImpl.getInstance(); Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); addHandlePairToClose(handles); - // Test waiting on handles of a newly created message pipe. - WaitResult waitResult = handles.first.wait( - Core.HandleSignals.none().setReadable(true).setWritable(true), 0); - assertEquals(MojoResult.OK, waitResult.getMojoResult()); - assertEquals( - HandleSignals.WRITABLE, waitResult.getHandleSignalsState().getSatisfiedSignals()); - assertEquals(ALL_SIGNALS, waitResult.getHandleSignalsState().getSatisfiableSignals()); - - waitResult = handles.first.wait(Core.HandleSignals.WRITABLE, 0); - assertEquals(MojoResult.OK, waitResult.getMojoResult()); - assertEquals( - HandleSignals.WRITABLE, waitResult.getHandleSignalsState().getSatisfiedSignals()); - assertEquals(ALL_SIGNALS, waitResult.getHandleSignalsState().getSatisfiableSignals()); - - waitResult = handles.first.wait(Core.HandleSignals.READABLE, 0); - assertEquals(MojoResult.DEADLINE_EXCEEDED, waitResult.getMojoResult()); - assertEquals( - HandleSignals.WRITABLE, waitResult.getHandleSignalsState().getSatisfiedSignals()); - assertEquals(ALL_SIGNALS, waitResult.getHandleSignalsState().getSatisfiableSignals()); // Testing read on an empty pipe. ResultAnd<MessagePipeHandle.ReadMessageResult> readResult = handles.first.readMessage(null, 0, MessagePipeHandle.ReadFlags.NONE); assertEquals(MojoResult.SHOULD_WAIT, readResult.getMojoResult()); - // Closing a pipe while waiting. - WORKER.schedule(new CloseHandle(handles.first), 10, TimeUnit.MILLISECONDS); - waitResult = handles.first.wait(Core.HandleSignals.READABLE, 1000000L); - assertEquals(MojoResult.CANCELLED, waitResult.getMojoResult()); - assertEquals( - HandleSignals.none(), waitResult.getHandleSignalsState().getSatisfiedSignals()); - assertEquals( - HandleSignals.none(), waitResult.getHandleSignalsState().getSatisfiableSignals()); - - handles = core.createMessagePipe(null); - addHandlePairToClose(handles); - - // Closing the other pipe while waiting. - WORKER.schedule(new CloseHandle(handles.first), 10, TimeUnit.MILLISECONDS); - waitResult = handles.second.wait(Core.HandleSignals.READABLE, 1000000L); - assertEquals(MojoResult.FAILED_PRECONDITION, waitResult.getMojoResult()); - - // Waiting on a closed pipe. - waitResult = handles.second.wait(Core.HandleSignals.READABLE, 0); - assertEquals(MojoResult.FAILED_PRECONDITION, waitResult.getMojoResult()); - waitResult = handles.second.wait(Core.HandleSignals.WRITABLE, 0); - assertEquals(MojoResult.FAILED_PRECONDITION, waitResult.getMojoResult()); + handles.first.close(); + handles.second.close(); } /** @@ -540,29 +441,6 @@ public class CoreImplTest extends MojoTestCase { Core core = CoreImpl.getInstance(); Handle handle = InvalidHandle.INSTANCE; - // Checking wait. - boolean exception = false; - try { - core.wait(handle, Core.HandleSignals.WRITABLE, 0); - } catch (MojoException e) { - assertEquals(MojoResult.INVALID_ARGUMENT, e.getMojoResult()); - exception = true; - } - assertTrue(exception); - - // Checking waitMany. - exception = false; - try { - List<Pair<Handle, Core.HandleSignals>> handles = - new ArrayList<Pair<Handle, Core.HandleSignals>>(); - handles.add(Pair.create(handle, Core.HandleSignals.WRITABLE)); - core.waitMany(handles, 0); - } catch (MojoException e) { - assertEquals(MojoResult.INVALID_ARGUMENT, e.getMojoResult()); - exception = true; - } - assertTrue(exception); - // Checking sending an invalid handle. // Until the behavior is changed on the C++ side, handle gracefully 2 different use case: // - Receive a INVALID_ARGUMENT exception diff --git a/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java b/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java index 6a99fe1..e14adb1 100644 --- a/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java +++ b/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java @@ -68,6 +68,17 @@ public class WatcherImplTest extends MojoTestCase { private static class WatcherResult implements Callback { private int mResult = Integer.MIN_VALUE; + private MessagePipeHandle mReadPipe; + + /** + * @param readPipe A MessagePipeHandle to read from when onResult triggers success. + */ + public WatcherResult(MessagePipeHandle readPipe) { + mReadPipe = readPipe; + } + public WatcherResult() { + this(null); + } /** * @see Callback#onResult(int) @@ -75,6 +86,11 @@ public class WatcherImplTest extends MojoTestCase { @Override public void onResult(int result) { this.mResult = result; + + if (result == MojoResult.OK && mReadPipe != null) { + mReadPipe.readMessage( + null, 0, MessagePipeHandle.ReadFlags.none().setMayDiscard(true)); + } } /** @@ -93,7 +109,7 @@ public class WatcherImplTest extends MojoTestCase { // Checking a correct result. Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null); addHandlePairToClose(handles); - final WatcherResult watcherResult = new WatcherResult(); + final WatcherResult watcherResult = new WatcherResult(handles.first); assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); diff --git a/mojo/android/system/base_run_loop.cc b/mojo/android/system/base_run_loop.cc index 6d9bd78..7993ba8 100644 --- a/mojo/android/system/base_run_loop.cc +++ b/mojo/android/system/base_run_loop.cc @@ -80,4 +80,3 @@ bool RegisterBaseRunLoop(JNIEnv* env) { } // namespace android } // namespace mojo - diff --git a/mojo/android/system/core_impl.cc b/mojo/android/system/core_impl.cc index 4a23409..7d5a402 100644 --- a/mojo/android/system/core_impl.cc +++ b/mojo/android/system/core_impl.cc @@ -27,41 +27,6 @@ static jlong GetTimeTicksNow(JNIEnv* env, return MojoGetTimeTicksNow(); } -static jint WaitMany(JNIEnv* env, - const JavaParamRef<jobject>& jcaller, - const JavaParamRef<jobject>& buffer, - jlong deadline) { - // |buffer| contains, in this order - // input: The array of N handles (MojoHandle, 4 bytes each) - // input: The array of N signals (MojoHandleSignals, 4 bytes each) - // space for output: The array of N handle states (MojoHandleSignalsState, 8 - // bytes each) - // space for output: The result index (uint32_t, 4 bytes) - uint8_t* buffer_start = - static_cast<uint8_t*>(env->GetDirectBufferAddress(buffer)); - DCHECK(buffer_start); - DCHECK_EQ(reinterpret_cast<uintptr_t>(buffer_start) % 8, 0u); - // Each handle of the input array contributes 4 (MojoHandle) + 4 - // (MojoHandleSignals) + 8 (MojoHandleSignalsState) = 16 bytes to the size of - // the buffer. - const size_t size_per_handle = 16; - const size_t buffer_size = env->GetDirectBufferCapacity(buffer); - DCHECK_EQ((buffer_size - 4) % size_per_handle, 0u); - - const size_t nb_handles = (buffer_size - 4) / size_per_handle; - const MojoHandle* handle_start = - reinterpret_cast<const MojoHandle*>(buffer_start); - const MojoHandleSignals* signals_start = - reinterpret_cast<const MojoHandleSignals*>(buffer_start + 4 * nb_handles); - MojoHandleSignalsState* states_start = - reinterpret_cast<MojoHandleSignalsState*>(buffer_start + 8 * nb_handles); - uint32_t* result_index = - reinterpret_cast<uint32_t*>(buffer_start + 16 * nb_handles); - *result_index = static_cast<uint32_t>(-1); - return MojoWaitMany(handle_start, signals_start, nb_handles, deadline, - result_index, states_start); -} - static ScopedJavaLocalRef<jobject> CreateMessagePipe( JNIEnv* env, const JavaParamRef<jobject>& jcaller, @@ -128,21 +93,16 @@ static jint Close(JNIEnv* env, return MojoClose(mojo_handle); } -static jint Wait(JNIEnv* env, - const JavaParamRef<jobject>& jcaller, - const JavaParamRef<jobject>& buffer, - jint mojo_handle, - jint signals, - jlong deadline) { - // Buffer contains space for the MojoHandleSignalsState - void* buffer_start = env->GetDirectBufferAddress(buffer); - DCHECK(buffer_start); - DCHECK_EQ(reinterpret_cast<const uintptr_t>(buffer_start) % 8, 0u); - DCHECK_EQ(sizeof(struct MojoHandleSignalsState), +static jint QueryHandleSignalsState(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jint mojo_handle, + const JavaParamRef<jobject>& buffer) { + MojoHandleSignalsState* signals_state = + static_cast<MojoHandleSignalsState*>(env->GetDirectBufferAddress(buffer)); + DCHECK(signals_state); + DCHECK_EQ(sizeof(MojoHandleSignalsState), static_cast<size_t>(env->GetDirectBufferCapacity(buffer))); - struct MojoHandleSignalsState* signals_state = - static_cast<struct MojoHandleSignalsState*>(buffer_start); - return MojoWait(mojo_handle, signals, deadline, signals_state); + return MojoQueryHandleSignalsState(mojo_handle, signals_state); } static jint WriteMessage(JNIEnv* env, diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java index 8330586..173f801 100644 --- a/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java @@ -8,6 +8,7 @@ import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.MainDex; import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignalsState; import org.chromium.mojo.system.DataPipe; import org.chromium.mojo.system.DataPipe.ConsumerHandle; import org.chromium.mojo.system.DataPipe.ProducerHandle; @@ -27,7 +28,6 @@ import org.chromium.mojo.system.Watcher; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** @@ -91,61 +91,6 @@ public class CoreImpl implements Core { } /** - * @see Core#waitMany(List, long) - */ - @Override - public WaitManyResult waitMany(List<Pair<Handle, HandleSignals>> handles, long deadline) { - // Allocate a direct buffer to allow native code not to reach back to java. The buffer - // layout will be: - // input: The array of handles (int, 4 bytes each) - // input: The array of signals (int, 4 bytes each) - // space for output: The array of handle states (2 ints, 8 bytes each) - // Space for output: The result index (int, 4 bytes) - // The handles and signals will be filled before calling the native method. When the native - // method returns, the handle states and the index will have been set. - ByteBuffer buffer = allocateDirectBuffer(handles.size() * 16 + 4); - int index = 0; - for (Pair<Handle, HandleSignals> handle : handles) { - buffer.putInt(HANDLE_SIZE * index, getMojoHandle(handle.first)); - buffer.putInt( - HANDLE_SIZE * handles.size() + FLAG_SIZE * index, handle.second.getFlags()); - index++; - } - int code = nativeWaitMany(buffer, deadline); - WaitManyResult result = new WaitManyResult(); - result.setMojoResult(filterMojoResultForWait(code)); - result.setHandleIndex(buffer.getInt(handles.size() * 16)); - if (result.getMojoResult() != MojoResult.INVALID_ARGUMENT - && result.getMojoResult() != MojoResult.RESOURCE_EXHAUSTED) { - HandleSignalsState[] states = new HandleSignalsState[handles.size()]; - for (int i = 0; i < handles.size(); ++i) { - states[i] = new HandleSignalsState( - new HandleSignals(buffer.getInt(8 * (handles.size() + i))), - new HandleSignals(buffer.getInt(8 * (handles.size() + i) + 4))); - } - result.setSignalStates(Arrays.asList(states)); - } - return result; - } - - /** - * @see Core#wait(Handle, HandleSignals, long) - */ - @Override - public WaitResult wait(Handle handle, HandleSignals signals, long deadline) { - // Allocate a direct buffer to allow native code not to reach back to java. Buffer will - // contain spaces to write the handle state. - ByteBuffer buffer = allocateDirectBuffer(8); - WaitResult result = new WaitResult(); - result.setMojoResult(filterMojoResultForWait( - nativeWait(buffer, getMojoHandle(handle), signals.getFlags(), deadline))); - HandleSignalsState signalsState = new HandleSignalsState( - new HandleSignals(buffer.getInt(0)), new HandleSignals(buffer.getInt(4))); - result.setHandleSignalsState(signalsState); - return result; - } - - /** * @see Core#createMessagePipe(MessagePipeHandle.CreateOptions) */ @Override @@ -262,6 +207,14 @@ public class CoreImpl implements Core { } } + HandleSignalsState queryHandleSignalsState(int mojoHandle) { + ByteBuffer buffer = allocateDirectBuffer(8); + int result = nativeQueryHandleSignalsState(mojoHandle, buffer); + if (result != MojoResult.OK) throw new MojoException(result); + return new HandleSignalsState( + new HandleSignals(buffer.getInt(0)), new HandleSignals(buffer.getInt(4))); + } + /** * @see MessagePipeHandle#writeMessage(ByteBuffer, List, MessagePipeHandle.WriteFlags) */ @@ -525,8 +478,6 @@ public class CoreImpl implements Core { private native long nativeGetTimeTicksNow(); - private native int nativeWaitMany(ByteBuffer buffer, long deadline); - private native ResultAnd<IntegerPair> nativeCreateMessagePipe(ByteBuffer optionsBuffer); private native ResultAnd<IntegerPair> nativeCreateDataPipe(ByteBuffer optionsBuffer); @@ -536,7 +487,7 @@ public class CoreImpl implements Core { private native int nativeClose(int mojoHandle); - private native int nativeWait(ByteBuffer buffer, int mojoHandle, int signals, long deadline); + private native int nativeQueryHandleSignalsState(int mojoHandle, ByteBuffer signalsStateBuffer); private native int nativeWriteMessage( int mojoHandle, ByteBuffer bytes, int numBytes, ByteBuffer handlesBuffer, int flags); diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java b/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java index a8870a8..4d149a4 100644 --- a/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java @@ -7,8 +7,7 @@ package org.chromium.mojo.system.impl; import android.util.Log; import org.chromium.mojo.system.Core; -import org.chromium.mojo.system.Core.HandleSignals; -import org.chromium.mojo.system.Core.WaitResult; +import org.chromium.mojo.system.Core.HandleSignalsState; import org.chromium.mojo.system.Handle; import org.chromium.mojo.system.UntypedHandle; @@ -63,11 +62,11 @@ abstract class HandleBase implements Handle { } /** - * @see org.chromium.mojo.system.Handle#wait(HandleSignals, long) + * @see org.chromium.mojo.system.Handle#querySignalsState() */ @Override - public WaitResult wait(HandleSignals signals, long deadline) { - return mCore.wait(this, signals, deadline); + public HandleSignalsState querySignalsState() { + return mCore.queryHandleSignalsState(mMojoHandle); } /** diff --git a/mojo/android/system/watcher_impl.cc b/mojo/android/system/watcher_impl.cc index 09540fc..3344447 100644 --- a/mojo/android/system/watcher_impl.cc +++ b/mojo/android/system/watcher_impl.cc @@ -16,7 +16,7 @@ #include "base/bind.h" #include "jni/WatcherImpl_jni.h" #include "mojo/public/cpp/system/handle.h" -#include "mojo/public/cpp/system/watcher.h" +#include "mojo/public/cpp/system/simple_watcher.h" namespace mojo { namespace android { @@ -27,7 +27,7 @@ namespace { class WatcherImpl { public: - WatcherImpl() : watcher_(FROM_HERE) {} + WatcherImpl() : watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC) {} ~WatcherImpl() = default; @@ -41,9 +41,8 @@ class WatcherImpl { base::Bind(&WatcherImpl::OnHandleReady, base::Unretained(this)); MojoResult result = - watcher_.Start(mojo::Handle(static_cast<MojoHandle>(mojo_handle)), + watcher_.Watch(mojo::Handle(static_cast<MojoHandle>(mojo_handle)), static_cast<MojoHandleSignals>(signals), ready_callback); - if (result != MOJO_RESULT_OK) java_watcher_.Reset(); @@ -69,7 +68,7 @@ class WatcherImpl { result); } - Watcher watcher_; + SimpleWatcher watcher_; base::android::ScopedJavaGlobalRef<jobject> java_watcher_; DISALLOW_COPY_AND_ASSIGN(WatcherImpl); diff --git a/mojo/common/data_pipe_drainer.cc b/mojo/common/data_pipe_drainer.cc index 27bd893..e705c8d 100644 --- a/mojo/common/data_pipe_drainer.cc +++ b/mojo/common/data_pipe_drainer.cc @@ -17,10 +17,10 @@ DataPipeDrainer::DataPipeDrainer(Client* client, mojo::ScopedDataPipeConsumerHandle source) : client_(client), source_(std::move(source)), - handle_watcher_(FROM_HERE), + handle_watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC), weak_factory_(this) { DCHECK(client_); - handle_watcher_.Start( + handle_watcher_.Watch( source_.get(), MOJO_HANDLE_SIGNAL_READABLE, base::Bind(&DataPipeDrainer::WaitComplete, weak_factory_.GetWeakPtr())); } diff --git a/mojo/common/data_pipe_drainer.h b/mojo/common/data_pipe_drainer.h index d0366fa..5cff820 100644 --- a/mojo/common/data_pipe_drainer.h +++ b/mojo/common/data_pipe_drainer.h @@ -11,7 +11,7 @@ #include "base/memory/weak_ptr.h" #include "mojo/common/mojo_common_export.h" #include "mojo/public/cpp/system/core.h" -#include "mojo/public/cpp/system/watcher.h" +#include "mojo/public/cpp/system/simple_watcher.h" namespace mojo { namespace common { @@ -36,7 +36,7 @@ class MOJO_COMMON_EXPORT DataPipeDrainer { Client* client_; mojo::ScopedDataPipeConsumerHandle source_; - mojo::Watcher handle_watcher_; + mojo::SimpleWatcher handle_watcher_; base::WeakPtrFactory<DataPipeDrainer> weak_factory_; diff --git a/mojo/common/data_pipe_utils.cc b/mojo/common/data_pipe_utils.cc index bed5e85..9b069b8 100644 --- a/mojo/common/data_pipe_utils.cc +++ b/mojo/common/data_pipe_utils.cc @@ -7,6 +7,7 @@ #include <utility> #include "base/bind.h" +#include "mojo/public/cpp/system/wait.h" namespace mojo { namespace common { @@ -25,10 +26,7 @@ bool BlockingCopyHelper(ScopedDataPipeConsumerHandle source, if (bytes_written < num_bytes || result != MOJO_RESULT_OK) return false; } else if (result == MOJO_RESULT_SHOULD_WAIT) { - result = Wait(source.get(), - MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, - nullptr); + result = Wait(source.get(), MOJO_HANDLE_SIGNAL_READABLE); if (result != MOJO_RESULT_OK) { // If the producer handle was closed, then treat as EOF. return result == MOJO_RESULT_FAILED_PRECONDITION; @@ -82,8 +80,7 @@ bool MOJO_COMMON_EXPORT BlockingCopyFromString( if (it == source.end()) return true; } else if (result == MOJO_RESULT_SHOULD_WAIT) { - result = Wait(destination.get(), MOJO_HANDLE_SIGNAL_WRITABLE, - MOJO_DEADLINE_INDEFINITE, nullptr); + result = Wait(destination.get(), MOJO_HANDLE_SIGNAL_WRITABLE); if (result != MOJO_RESULT_OK) { // If the consumer handle was closed, then treat as EOF. return result == MOJO_RESULT_FAILED_PRECONDITION; diff --git a/mojo/edk/embedder/README.md b/mojo/edk/embedder/README.md index 6def874..fc53bec 100644 --- a/mojo/edk/embedder/README.md +++ b/mojo/edk/embedder/README.md @@ -1,4 +1,9 @@ -# Mojo Embedder Development Kit (EDK) +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo Embedder Development Kit (EDK) +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview The Mojo EDK is a (binary-unstable) API which enables a process to use Mojo both internally and for IPC to other Mojo-embedding processes. @@ -8,6 +13,12 @@ confusingly) a direct dependency on the GN `//mojo/edk/system` target. Despite this fact, you should never reference any of the headers in `mojo/edk/system` directly, as everything there is considered to be an internal detail of the EDK. +**NOTE:** Unless you are introducing a new binary entry point into the system +(*e.g.,* a new executable with a new `main()` definition), you probably don't +need to know anything about the EDK API. Most processes defined in the Chrome +repo today already fully initialize the EDK so that Mojo's other public APIs +"just work" out of the box. + ## Basic Initialization In order to use Mojo in a given process, it's necessary to call @@ -320,8 +331,16 @@ interface Foo { Once you've bootstrapped your process connection with a real mojom interface, you can avoid any further mucking around with EDK APIs or raw message pipe handles, as everything beyond this point - including the passing of other -interface pipes - can be handled eloquently using public bindings APIs. +interface pipes - can be handled eloquently using +[public bindings APIs](/mojo#High-Level-Bindings-APIs). + +## Setting System Properties + +The public Mojo C System API exposes a +[**`MojoGetProperty`**](/mojo/public/c/system#MojoGetProperty) function for +querying global, embedder-defined property values. These can be set by calling: + +``` +mojo::edk::SetProperty(MojoPropertyType type, const void* value) +``` -See [additional Mojo documentation]( - https://www.chromium.org/developers/design-documents/mojo) for more -information. diff --git a/mojo/edk/embedder/configuration.h b/mojo/edk/embedder/configuration.h index 4b5618d..1990fb1 100644 --- a/mojo/edk/embedder/configuration.h +++ b/mojo/edk/embedder/configuration.h @@ -27,10 +27,6 @@ struct Configuration { // Maximum number of active memory mappings. The default is 1,000,000. size_t max_mapping_table_sze; - // Upper limit of |MojoWaitMany()|'s |num_handles|. The default is 1,000,000. - // Must be same as or smaller than |max_handle_table_size|. - size_t max_wait_many_num_handles; - // Maximum data size of messages sent over message pipes, in bytes. The // default is 4MB. size_t max_message_num_bytes; diff --git a/mojo/edk/embedder/embedder_unittest.cc b/mojo/edk/embedder/embedder_unittest.cc index d5a87e5..388b45c 100644 --- a/mojo/edk/embedder/embedder_unittest.cc +++ b/mojo/edk/embedder/embedder_unittest.cc @@ -35,19 +35,13 @@ #include "mojo/public/c/system/core.h" #include "mojo/public/cpp/system/handle.h" #include "mojo/public/cpp/system/message_pipe.h" +#include "mojo/public/cpp/system/wait.h" #include "testing/gtest/include/gtest/gtest.h" namespace mojo { namespace edk { namespace { -const MojoHandleSignals kSignalReadadableWritable = - MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE; - -const MojoHandleSignals kSignalAll = MOJO_HANDLE_SIGNAL_READABLE | - MOJO_HANDLE_SIGNAL_WRITABLE | - MOJO_HANDLE_SIGNAL_PEER_CLOSED; - // The multiprocess tests that use these don't compile on iOS. #if !defined(OS_IOS) const char kHelloWorld[] = "hello world"; @@ -70,50 +64,6 @@ TEST_F(EmbedderTest, ChannelBasic) { ASSERT_EQ(MOJO_RESULT_OK, MojoClose(client_mp)); } -// Test sending a MP which has read messages out of the OS pipe but which have -// not been consumed using MojoReadMessage yet. -TEST_F(EmbedderTest, SendReadableMessagePipe) { - MojoHandle server_mp, client_mp; - CreateMessagePipe(&server_mp, &client_mp); - - MojoHandle server_mp2, client_mp2; - CreateMessagePipe(&server_mp2, &client_mp2); - - // Write to server2 and wait for client2 to be readable before sending it. - // client2's MessagePipeDispatcher will have the message below in its - // message_queue_. For extra measures, also verify that this pending message - // can contain a message pipe. - MojoHandle server_mp3, client_mp3; - CreateMessagePipe(&server_mp3, &client_mp3); - - const std::string kHello = "hello"; - WriteMessageWithHandles(server_mp2, kHello, &client_mp3, 1); - - MojoHandleSignalsState state; - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(client_mp2, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); - ASSERT_EQ(kSignalReadadableWritable, state.satisfied_signals); - ASSERT_EQ(kSignalAll, state.satisfiable_signals); - - // Now send client2 - WriteMessageWithHandles(server_mp, kHello, &client_mp2, 1); - - MojoHandle port; - std::string message = ReadMessageWithHandles(client_mp, &port, 1); - EXPECT_EQ(kHello, message); - - client_mp2 = port; - message = ReadMessageWithHandles(client_mp2, &client_mp3, 1); - EXPECT_EQ(kHello, message); - - ASSERT_EQ(MOJO_RESULT_OK, MojoClose(server_mp3)); - ASSERT_EQ(MOJO_RESULT_OK, MojoClose(client_mp3)); - ASSERT_EQ(MOJO_RESULT_OK, MojoClose(server_mp2)); - ASSERT_EQ(MOJO_RESULT_OK, MojoClose(client_mp2)); - ASSERT_EQ(MOJO_RESULT_OK, MojoClose(server_mp)); - ASSERT_EQ(MOJO_RESULT_OK, MojoClose(client_mp)); -} - // Verifies that a MP with pending messages to be written can be sent and the // pending messages aren't dropped. TEST_F(EmbedderTest, SendMessagePipeWithWriteQueue) { @@ -217,10 +167,8 @@ TEST_F(EmbedderTest, PipeSetup_LaunchDeath) { // the reserved port. ignore_result(pair.PassClientHandle()); - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(parent_mp.get().value(), - MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, - nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(parent_mp.get().value(), + MOJO_HANDLE_SIGNAL_PEER_CLOSED)); } TEST_F(EmbedderTest, PipeSetup_LaunchFailure) { @@ -234,10 +182,8 @@ TEST_F(EmbedderTest, PipeSetup_LaunchFailure) { // called, any message pipes associated with it detect peer closure. process.reset(); - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(parent_mp.get().value(), - MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, - nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(parent_mp.get().value(), + MOJO_HANDLE_SIGNAL_PEER_CLOSED)); } // The sequence of messages sent is: @@ -292,9 +238,7 @@ TEST_F(EmbedderTest, MultiprocessChannels) { // 10. Wait on |mp2| (which should eventually fail) and then close it. MojoHandleSignalsState state; ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - MojoWait(mp2, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, - &state)); + WaitForSignals(mp2, MOJO_HANDLE_SIGNAL_READABLE, &state)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); @@ -336,8 +280,7 @@ DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MultiprocessChannelsClient, EmbedderTest, // 10. Wait on |mp1| (which should eventually fail) and then close it. MojoHandleSignalsState state; ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - MojoWait(mp1, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); + WaitForSignals(mp1, MOJO_HANDLE_SIGNAL_READABLE, &state)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); ASSERT_EQ(MOJO_RESULT_OK, MojoClose(mp1)); @@ -586,8 +529,7 @@ TEST_F(EmbedderTest, ClosePendingPeerConnection) { ConnectToPeerProcess(CreateServerHandle(named_handle), peer_token); ClosePeerConnection(peer_token); EXPECT_EQ(MOJO_RESULT_OK, - Wait(server_pipe.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, nullptr)); + Wait(server_pipe.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED)); base::MessageLoop message_loop; base::RunLoop run_loop; ScopedPlatformHandle client_handle; @@ -617,8 +559,8 @@ TEST_F(EmbedderTest, ClosePipeToConnectedPeer) { controller.ClosePeerConnection(); - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(server_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(server_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); EXPECT_EQ(0, controller.WaitForShutdown()); } @@ -632,8 +574,7 @@ DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ClosePipeToConnectedPeerClient, EmbedderTest, WriteMessage(client_mp, "world!"); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(client_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, nullptr)); + WaitForSignals(client_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); } TEST_F(EmbedderTest, ClosePipeToConnectingPeer) { @@ -643,16 +584,16 @@ TEST_F(EmbedderTest, ClosePipeToConnectingPeer) { MojoHandle server_mp = controller.pipe(); - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(server_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(server_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); EXPECT_EQ(0, controller.WaitForShutdown()); } DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ClosePipeToConnectingPeerClient, EmbedderTest, client_mp) { - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(client_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, nullptr)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(client_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); } #endif // !defined(OS_IOS) diff --git a/mojo/edk/embedder/entrypoints.cc b/mojo/edk/embedder/entrypoints.cc index f09c5e1..9081368 100644 --- a/mojo/edk/embedder/entrypoints.cc +++ b/mojo/edk/embedder/entrypoints.cc @@ -13,7 +13,6 @@ #include "mojo/public/c/system/functions.h" #include "mojo/public/c/system/message_pipe.h" #include "mojo/public/c/system/platform_handle.h" -#include "mojo/public/c/system/wait_set.h" using mojo::edk::internal::g_core; @@ -28,32 +27,35 @@ MojoResult MojoCloseImpl(MojoHandle handle) { return g_core->Close(handle); } -MojoResult MojoWaitImpl(MojoHandle handle, - MojoHandleSignals signals, - MojoDeadline deadline, - MojoHandleSignalsState* signals_state) { - return g_core->Wait(handle, signals, deadline, signals_state); +MojoResult MojoQueryHandleSignalsStateImpl( + MojoHandle handle, + MojoHandleSignalsState* signals_state) { + return g_core->QueryHandleSignalsState(handle, signals_state); } -MojoResult MojoWaitManyImpl(const MojoHandle* handles, - const MojoHandleSignals* signals, - uint32_t num_handles, - MojoDeadline deadline, - uint32_t* result_index, - MojoHandleSignalsState* signals_states) { - return g_core->WaitMany(handles, signals, num_handles, deadline, result_index, - signals_states); +MojoResult MojoCreateWatcherImpl(MojoWatcherCallback callback, + MojoHandle* watcher_handle) { + return g_core->CreateWatcher(callback, watcher_handle); } -MojoResult MojoWatchImpl(MojoHandle handle, +MojoResult MojoArmWatcherImpl(MojoHandle watcher_handle, + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) { + return g_core->ArmWatcher(watcher_handle, num_ready_contexts, ready_contexts, + ready_results, ready_signals_states); +} + +MojoResult MojoWatchImpl(MojoHandle watcher_handle, + MojoHandle handle, MojoHandleSignals signals, - MojoWatchCallback callback, uintptr_t context) { - return g_core->Watch(handle, signals, callback, context); + return g_core->Watch(watcher_handle, handle, signals, context); } -MojoResult MojoCancelWatchImpl(MojoHandle handle, uintptr_t context) { - return g_core->CancelWatch(handle, context); +MojoResult MojoCancelWatchImpl(MojoHandle watcher_handle, uintptr_t context) { + return g_core->CancelWatch(watcher_handle, context); } MojoResult MojoAllocMessageImpl(uint32_t num_bytes, @@ -72,30 +74,6 @@ MojoResult MojoGetMessageBufferImpl(MojoMessageHandle message, void** buffer) { return g_core->GetMessageBuffer(message, buffer); } -MojoResult MojoCreateWaitSetImpl(MojoHandle* wait_set_handle) { - return g_core->CreateWaitSet(wait_set_handle); -} - -MojoResult MojoAddHandleImpl(MojoHandle wait_set_handle, - MojoHandle handle, - MojoHandleSignals signals) { - return g_core->AddHandle(wait_set_handle, handle, signals); -} - -MojoResult MojoRemoveHandleImpl(MojoHandle wait_set_handle, MojoHandle handle) { - return g_core->RemoveHandle(wait_set_handle, handle); -} - -MojoResult MojoGetReadyHandlesImpl( - MojoHandle wait_set_handle, - uint32_t* count, - MojoHandle* handles, - MojoResult* results, - struct MojoHandleSignalsState* signals_states) { - return g_core->GetReadyHandles(wait_set_handle, count, handles, results, - signals_states); -} - MojoResult MojoCreateMessagePipeImpl( const MojoCreateMessagePipeOptions* options, MojoHandle* message_pipe_handle0, @@ -267,8 +245,7 @@ MojoSystemThunks MakeSystemThunks() { MojoSystemThunks system_thunks = {sizeof(MojoSystemThunks), MojoGetTimeTicksNowImpl, MojoCloseImpl, - MojoWaitImpl, - MojoWaitManyImpl, + MojoQueryHandleSignalsStateImpl, MojoCreateMessagePipeImpl, MojoWriteMessageImpl, MojoReadMessageImpl, @@ -283,12 +260,10 @@ MojoSystemThunks MakeSystemThunks() { MojoDuplicateBufferHandleImpl, MojoMapBufferImpl, MojoUnmapBufferImpl, - MojoCreateWaitSetImpl, - MojoAddHandleImpl, - MojoRemoveHandleImpl, - MojoGetReadyHandlesImpl, + MojoCreateWatcherImpl, MojoWatchImpl, MojoCancelWatchImpl, + MojoArmWatcherImpl, MojoFuseMessagePipesImpl, MojoWriteMessageNewImpl, MojoReadMessageNewImpl, diff --git a/mojo/edk/js/core.cc b/mojo/edk/js/core.cc index f3eec8c..baccc4c 100644 --- a/mojo/edk/js/core.cc +++ b/mojo/edk/js/core.cc @@ -21,6 +21,7 @@ #include "gin/wrappable.h" #include "mojo/edk/js/drain_data.h" #include "mojo/edk/js/handle.h" +#include "mojo/public/cpp/system/wait.h" namespace mojo { namespace edk { @@ -35,19 +36,31 @@ MojoResult CloseHandle(gin::Handle<HandleWrapper> handle) { return MOJO_RESULT_OK; } +gin::Dictionary QueryHandleSignalsState(const gin::Arguments& args, + mojo::Handle handle) { + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + if (!handle.is_valid()) { + dictionary.Set("result", MOJO_RESULT_INVALID_ARGUMENT); + } else { + HandleSignalsState state = handle.QuerySignalsState(); + dictionary.Set("result", MOJO_RESULT_OK); + dictionary.Set("satisfiedSignals", state.satisfied_signals); + dictionary.Set("satisfiableSignals", state.satisfiable_signals); + } + return dictionary; +} + gin::Dictionary WaitHandle(const gin::Arguments& args, mojo::Handle handle, - MojoHandleSignals signals, - MojoDeadline deadline) { + MojoHandleSignals signals) { v8::Isolate* isolate = args.isolate(); gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(isolate); MojoHandleSignalsState signals_state; - MojoResult result = mojo::Wait(handle, signals, deadline, &signals_state); + MojoResult result = Wait(handle, signals, &signals_state); dictionary.Set("result", result); - mojo::WaitManyResult wmv(result, 0); - if (!wmv.AreSignalsStatesValid()) { + if (result != MOJO_RESULT_OK && result != MOJO_RESULT_FAILED_PRECONDITION) { dictionary.Set("signalsState", v8::Null(isolate).As<v8::Value>()); } else { gin::Dictionary signalsStateDict = gin::Dictionary::CreateEmpty(isolate); @@ -60,40 +73,6 @@ gin::Dictionary WaitHandle(const gin::Arguments& args, return dictionary; } -gin::Dictionary WaitMany(const gin::Arguments& args, - const std::vector<mojo::Handle>& handles, - const std::vector<MojoHandleSignals>& signals, - MojoDeadline deadline) { - v8::Isolate* isolate = args.isolate(); - gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(isolate); - - std::vector<MojoHandleSignalsState> signals_states(signals.size()); - mojo::WaitManyResult wmv = - mojo::WaitMany(handles, signals, deadline, &signals_states); - dictionary.Set("result", wmv.result); - if (wmv.IsIndexValid()) { - dictionary.Set("index", wmv.index); - } else { - dictionary.Set("index", v8::Null(isolate).As<v8::Value>()); - } - if (wmv.AreSignalsStatesValid()) { - std::vector<gin::Dictionary> vec; - for (size_t i = 0; i < handles.size(); ++i) { - gin::Dictionary signalsStateDict = gin::Dictionary::CreateEmpty(isolate); - signalsStateDict.Set("satisfiedSignals", - signals_states[i].satisfied_signals); - signalsStateDict.Set("satisfiableSignals", - signals_states[i].satisfiable_signals); - vec.push_back(signalsStateDict); - } - dictionary.Set("signalsState", vec); - } else { - dictionary.Set("signalsState", v8::Null(isolate).As<v8::Value>()); - } - - return dictionary; -} - gin::Dictionary CreateMessagePipe(const gin::Arguments& args) { gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); dictionary.Set("result", MOJO_RESULT_INVALID_ARGUMENT); @@ -388,8 +367,8 @@ v8::Local<v8::Value> Core::GetModule(v8::Isolate* isolate) { // TODO(mpcomplete): Should these just be methods on the JS Handle // object? .SetMethod("close", CloseHandle) + .SetMethod("queryHandleSignalsState", QueryHandleSignalsState) .SetMethod("wait", WaitHandle) - .SetMethod("waitMany", WaitMany) .SetMethod("createMessagePipe", CreateMessagePipe) .SetMethod("writeMessage", WriteMessage) .SetMethod("readMessage", ReadMessage) @@ -424,8 +403,6 @@ v8::Local<v8::Value> Core::GetModule(v8::Isolate* isolate) { .SetValue("RESULT_BUSY", MOJO_RESULT_BUSY) .SetValue("RESULT_SHOULD_WAIT", MOJO_RESULT_SHOULD_WAIT) - .SetValue("DEADLINE_INDEFINITE", MOJO_DEADLINE_INDEFINITE) - .SetValue("HANDLE_SIGNAL_NONE", MOJO_HANDLE_SIGNAL_NONE) .SetValue("HANDLE_SIGNAL_READABLE", MOJO_HANDLE_SIGNAL_READABLE) .SetValue("HANDLE_SIGNAL_WRITABLE", MOJO_HANDLE_SIGNAL_WRITABLE) diff --git a/mojo/edk/js/drain_data.cc b/mojo/edk/js/drain_data.cc index cfd0bb5..334ced3 100644 --- a/mojo/edk/js/drain_data.cc +++ b/mojo/edk/js/drain_data.cc @@ -23,7 +23,7 @@ namespace js { DrainData::DrainData(v8::Isolate* isolate, mojo::Handle handle) : isolate_(isolate), handle_(DataPipeConsumerHandle(handle.value())), - handle_watcher_(FROM_HERE) { + handle_watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC) { v8::Handle<v8::Context> context(isolate_->GetCurrentContext()); runner_ = gin::PerContextData::From(context)->runner()->GetWeakPtr(); @@ -43,7 +43,7 @@ DrainData::~DrainData() { } void DrainData::WaitForData() { - handle_watcher_.Start( + handle_watcher_.Watch( handle_.get(), MOJO_HANDLE_SIGNAL_READABLE, base::Bind(&DrainData::DataReady, base::Unretained(this))); } diff --git a/mojo/edk/js/drain_data.h b/mojo/edk/js/drain_data.h index 6e8555c..42da90f 100644 --- a/mojo/edk/js/drain_data.h +++ b/mojo/edk/js/drain_data.h @@ -10,7 +10,7 @@ #include "gin/runner.h" #include "mojo/public/cpp/system/core.h" -#include "mojo/public/cpp/system/watcher.h" +#include "mojo/public/cpp/system/simple_watcher.h" #include "v8/include/v8.h" namespace mojo { @@ -52,7 +52,7 @@ class DrainData { v8::Isolate* isolate_; ScopedDataPipeConsumerHandle handle_; - Watcher handle_watcher_; + SimpleWatcher handle_watcher_; base::WeakPtr<gin::Runner> runner_; v8::UniquePersistent<v8::Promise::Resolver> resolver_; std::vector<std::unique_ptr<DataBuffer>> data_buffers_; diff --git a/mojo/edk/js/tests/BUILD.gn b/mojo/edk/js/tests/BUILD.gn index 41850d7..f56c4b9 100644 --- a/mojo/edk/js/tests/BUILD.gn +++ b/mojo/edk/js/tests/BUILD.gn @@ -58,7 +58,6 @@ test("mojo_js_unittests") { "//mojo/edk/test:test_support", "//mojo/public/cpp/system", "//mojo/public/interfaces/bindings/tests:test_interfaces", - "//mojo/public/interfaces/bindings/tests:test_interfaces_experimental", "//mojo/public/js:tests", ] diff --git a/mojo/edk/js/tests/js_to_cpp_tests.cc b/mojo/edk/js/tests/js_to_cpp_tests.cc index e5e6bd1..b6b74e3 100644 --- a/mojo/edk/js/tests/js_to_cpp_tests.cc +++ b/mojo/edk/js/tests/js_to_cpp_tests.cc @@ -25,6 +25,7 @@ #include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/lib/validation_errors.h" #include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/wait.h" #include "testing/gtest/include/gtest/gtest.h" namespace mojo { @@ -76,9 +77,7 @@ void CheckDataPipe(ScopedDataPipeConsumerHandle data_pipe_handle) { void CheckMessagePipe(MessagePipeHandle message_pipe_handle) { unsigned char buffer[100]; uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer)); - MojoResult result = Wait( - message_pipe_handle, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, nullptr); + MojoResult result = Wait(message_pipe_handle, MOJO_HANDLE_SIGNAL_READABLE); EXPECT_EQ(MOJO_RESULT_OK, result); result = ReadMessageRaw( message_pipe_handle, buffer, &buffer_size, 0, 0, 0); diff --git a/mojo/edk/js/tests/run_js_unittests.cc b/mojo/edk/js/tests/run_js_unittests.cc index a7b70b7..13e796b 100644 --- a/mojo/edk/js/tests/run_js_unittests.cc +++ b/mojo/edk/js/tests/run_js_unittests.cc @@ -7,6 +7,7 @@ #include "base/path_service.h" #include "gin/modules/console.h" #include "gin/modules/module_registry.h" +#include "gin/modules/timer.h" #include "gin/test/file_runner.h" #include "gin/test/gtest.h" #include "mojo/edk/js/core.h" @@ -23,6 +24,7 @@ class TestRunnerDelegate : public gin::FileRunnerDelegate { public: TestRunnerDelegate() { AddBuiltinModule(gin::Console::kModuleName, gin::Console::GetModule); + AddBuiltinModule(gin::TimerModule::kName, gin::TimerModule::GetModule); AddBuiltinModule(Core::kModuleName, Core::GetModule); AddBuiltinModule(Threading::kModuleName, Threading::GetModule); AddBuiltinModule(Support::kModuleName, Support::GetModule); @@ -45,34 +47,10 @@ void RunTest(std::string test, bool run_until_idle) { } // TODO(abarth): Should we autogenerate these stubs from GYP? -TEST(JSTest, Codec) { - RunTest("codec_unittest.js", true); -} - -TEST(JSTest, Connection) { - RunTest("connection_unittest.js", false); -} - TEST(JSTest, Core) { RunTest("core_unittest.js", true); } -TEST(JSTest, InterfacePtr) { - RunTest("interface_ptr_unittest.js", false); -} - -TEST(JSTest, SampleService) { - RunTest("sample_service_unittest.js", false); -} - -TEST(JSTest, Struct) { - RunTest("struct_unittest.js", true); -} - -TEST(JSTest, Union) { - RunTest("union_unittest.js", true); -} - TEST(JSTest, Validation) { RunTest("validation_unittest.js", true); } diff --git a/mojo/edk/js/waiting_callback.cc b/mojo/edk/js/waiting_callback.cc index fada039..6ad4bd0 100644 --- a/mojo/edk/js/waiting_callback.cc +++ b/mojo/edk/js/waiting_callback.cc @@ -32,7 +32,7 @@ gin::Handle<WaitingCallback> WaitingCallback::Create( bool one_shot) { gin::Handle<WaitingCallback> waiting_callback = gin::CreateHandle( isolate, new WaitingCallback(isolate, callback, one_shot)); - MojoResult result = waiting_callback->watcher_.Start( + MojoResult result = waiting_callback->watcher_.Watch( handle_wrapper->get(), signals, base::Bind(&WaitingCallback::OnHandleReady, base::Unretained(waiting_callback.get()))); @@ -53,7 +53,7 @@ WaitingCallback::WaitingCallback(v8::Isolate* isolate, v8::Handle<v8::Function> callback, bool one_shot) : one_shot_(one_shot), - watcher_(FROM_HERE), + watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC), weak_factory_(this) { v8::Handle<v8::Context> context = isolate->GetCurrentContext(); runner_ = gin::PerContextData::From(context)->runner()->GetWeakPtr(); diff --git a/mojo/edk/js/waiting_callback.h b/mojo/edk/js/waiting_callback.h index 1195a98..f97b389 100644 --- a/mojo/edk/js/waiting_callback.h +++ b/mojo/edk/js/waiting_callback.h @@ -12,7 +12,7 @@ #include "gin/wrappable.h" #include "mojo/edk/js/handle.h" #include "mojo/public/cpp/system/core.h" -#include "mojo/public/cpp/system/watcher.h" +#include "mojo/public/cpp/system/simple_watcher.h" namespace mojo { namespace edk { @@ -54,7 +54,7 @@ class WaitingCallback : public gin::Wrappable<WaitingCallback> { const bool one_shot_; base::WeakPtr<gin::Runner> runner_; - Watcher watcher_; + SimpleWatcher watcher_; base::WeakPtrFactory<WaitingCallback> weak_factory_; DISALLOW_COPY_AND_ASSIGN(WaitingCallback); diff --git a/mojo/edk/system/BUILD.gn b/mojo/edk/system/BUILD.gn index b0acf23..a68cd44 100644 --- a/mojo/edk/system/BUILD.gn +++ b/mojo/edk/system/BUILD.gn @@ -16,9 +16,6 @@ component("system") { sources = [ "atomic_flag.h", - "awakable.h", - "awakable_list.cc", - "awakable_list.h", "broker.h", "broker_host.cc", "broker_host.h", @@ -62,12 +59,10 @@ component("system") { "request_context.h", "shared_buffer_dispatcher.cc", "shared_buffer_dispatcher.h", - "wait_set_dispatcher.cc", - "wait_set_dispatcher.h", - "waiter.cc", - "waiter.h", - "watcher.cc", - "watcher.h", + "watch.cc", + "watch.h", + "watcher_dispatcher.cc", + "watcher_dispatcher.h", "watcher_set.cc", "watcher_set.h", ] @@ -153,7 +148,6 @@ source_set("test_utils") { test("mojo_system_unittests") { sources = [ - "awakable_list_unittest.cc", "channel_unittest.cc", "core_test_base.cc", "core_test_base.h", @@ -163,11 +157,8 @@ test("mojo_system_unittests") { "platform_handle_dispatcher_unittest.cc", "shared_buffer_dispatcher_unittest.cc", "shared_buffer_unittest.cc", - "wait_set_dispatcher_unittest.cc", - "waiter_test_utils.cc", - "waiter_test_utils.h", - "waiter_unittest.cc", - "watch_unittest.cc", + "signals_unittest.cc", + "watcher_unittest.cc", ] if (!is_ios) { @@ -187,6 +178,7 @@ test("mojo_system_unittests") { "//mojo/edk/system/ports:tests", "//mojo/edk/test:run_all_unittests", "//mojo/edk/test:test_support", + "//mojo/public/cpp/system", "//testing/gmock", "//testing/gtest", ] diff --git a/mojo/edk/system/awakable.h b/mojo/edk/system/awakable.h deleted file mode 100644 index 2cb10f5..0000000 --- a/mojo/edk/system/awakable.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MOJO_EDK_SYSTEM_AWAKABLE_H_ -#define MOJO_EDK_SYSTEM_AWAKABLE_H_ - -#include <stdint.h> - -#include "mojo/edk/system/system_impl_export.h" -#include "mojo/public/c/system/types.h" - -namespace mojo { -namespace edk { - -// An interface that may be waited on |AwakableList|. -class MOJO_SYSTEM_IMPL_EXPORT Awakable { - public: - // |Awake()| must satisfy the following contract: - // * As this is called from any thread, this must be thread-safe. - // * As this is called inside a lock, this must not call anything that takes - // "non-terminal" locks, i.e., those which are always safe to take. - // This should return false if this must not be called again for the same - // reason (e.g., for the same call to |AwakableList::Add()|). - virtual bool Awake(MojoResult result, uintptr_t context) = 0; - - protected: - Awakable() {} -}; - -} // namespace edk -} // namespace mojo - -#endif // MOJO_EDK_SYSTEM_AWAKABLE_H_ diff --git a/mojo/edk/system/awakable_list.cc b/mojo/edk/system/awakable_list.cc deleted file mode 100644 index 2045f32..0000000 --- a/mojo/edk/system/awakable_list.cc +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "mojo/edk/system/awakable_list.h" - -#include <algorithm> - -#include "base/logging.h" -#include "mojo/edk/system/awakable.h" -#include "mojo/edk/system/handle_signals_state.h" - -namespace mojo { -namespace edk { - -AwakableList::AwakableList() { -} - -AwakableList::~AwakableList() { - DCHECK(awakables_.empty()); -} - -void AwakableList::AwakeForStateChange(const HandleSignalsState& state) { - // Instead of deleting elements in-place, swap them with the last element and - // erase the elements from the end. - auto last = awakables_.end(); - for (AwakeInfoList::iterator it = awakables_.begin(); it != last;) { - bool keep = true; - if (state.satisfies(it->signals)) - keep = it->awakable->Awake(MOJO_RESULT_OK, it->context); - else if (!state.can_satisfy(it->signals)) - keep = it->awakable->Awake(MOJO_RESULT_FAILED_PRECONDITION, it->context); - - if (!keep) { - --last; - std::swap(*it, *last); - } else { - ++it; - } - } - awakables_.erase(last, awakables_.end()); - watchers_.NotifyForStateChange(state); -} - -void AwakableList::CancelAll() { - for (AwakeInfoList::iterator it = awakables_.begin(); it != awakables_.end(); - ++it) { - it->awakable->Awake(MOJO_RESULT_CANCELLED, it->context); - } - awakables_.clear(); - watchers_.NotifyClosed(); -} - -void AwakableList::Add(Awakable* awakable, - MojoHandleSignals signals, - uintptr_t context) { - awakables_.push_back(AwakeInfo(awakable, signals, context)); -} - -void AwakableList::Remove(Awakable* awakable) { - // We allow a thread to wait on the same handle multiple times simultaneously, - // so we need to scan the entire list and remove all occurrences of |waiter|. - auto last = awakables_.end(); - for (AwakeInfoList::iterator it = awakables_.begin(); it != last;) { - if (it->awakable == awakable) { - --last; - std::swap(*it, *last); - } else { - ++it; - } - } - awakables_.erase(last, awakables_.end()); -} - -MojoResult AwakableList::AddWatcher(MojoHandleSignals signals, - const Watcher::WatchCallback& callback, - uintptr_t context, - const HandleSignalsState& current_state) { - return watchers_.Add(signals, callback, context, current_state); -} - -MojoResult AwakableList::RemoveWatcher(uintptr_t context) { - return watchers_.Remove(context); -} - -} // namespace edk -} // namespace mojo diff --git a/mojo/edk/system/awakable_list.h b/mojo/edk/system/awakable_list.h deleted file mode 100644 index 355677f..0000000 --- a/mojo/edk/system/awakable_list.h +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MOJO_EDK_SYSTEM_AWAKABLE_LIST_H_ -#define MOJO_EDK_SYSTEM_AWAKABLE_LIST_H_ - -#include <stddef.h> -#include <stdint.h> - -#include <vector> - -#include "base/macros.h" -#include "mojo/edk/system/system_impl_export.h" -#include "mojo/edk/system/watcher.h" -#include "mojo/edk/system/watcher_set.h" -#include "mojo/public/c/system/types.h" - -namespace mojo { -namespace edk { - -class Awakable; -struct HandleSignalsState; - -// |AwakableList| tracks all the |Waiter|s that are waiting on a given -// handle/|Dispatcher|. There should be a |AwakableList| for each handle that -// can be waited on (in any way). In the simple case, the |AwakableList| is -// owned by the |Dispatcher|, whereas in more complex cases it is owned by the -// secondary object (see simple_dispatcher.* and the explanatory comment in -// core.cc). This class is thread-unsafe (all concurrent access must be -// protected by some lock). -class MOJO_SYSTEM_IMPL_EXPORT AwakableList { - public: - AwakableList(); - ~AwakableList(); - - void AwakeForStateChange(const HandleSignalsState& state); - void CancelAll(); - void Add(Awakable* awakable, MojoHandleSignals signals, uintptr_t context); - void Remove(Awakable* awakable); - - // Add and remove Watchers to this AwakableList. - MojoResult AddWatcher(MojoHandleSignals signals, - const Watcher::WatchCallback& callback, - uintptr_t context, - const HandleSignalsState& current_state); - MojoResult RemoveWatcher(uintptr_t context); - - private: - struct AwakeInfo { - AwakeInfo(Awakable* awakable, MojoHandleSignals signals, uintptr_t context) - : awakable(awakable), signals(signals), context(context) {} - - Awakable* awakable; - MojoHandleSignals signals; - uintptr_t context; - }; - using AwakeInfoList = std::vector<AwakeInfo>; - - AwakeInfoList awakables_; - - // TODO: Remove AwakableList and instead use WatcherSet directly in - // dispatchers. - WatcherSet watchers_; - - DISALLOW_COPY_AND_ASSIGN(AwakableList); -}; - -} // namespace edk -} // namespace mojo - -#endif // MOJO_EDK_SYSTEM_AWAKABLE_LIST_H_ diff --git a/mojo/edk/system/awakable_list_unittest.cc b/mojo/edk/system/awakable_list_unittest.cc deleted file mode 100644 index 9737fce..0000000 --- a/mojo/edk/system/awakable_list_unittest.cc +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// NOTE(vtl): Some of these tests are inherently flaky (e.g., if run on a -// heavily-loaded system). Sorry. |test::EpsilonDeadline()| may be increased to -// increase tolerance and reduce observed flakiness (though doing so reduces the -// meaningfulness of the test). - -#include "mojo/edk/system/awakable_list.h" - -#include "mojo/edk/system/handle_signals_state.h" -#include "mojo/edk/system/test_utils.h" -#include "mojo/edk/system/waiter.h" -#include "mojo/edk/system/waiter_test_utils.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace mojo { -namespace edk { -namespace { - -TEST(AwakableListTest, BasicCancel) { - MojoResult result; - uintptr_t context; - - // Cancel immediately after thread start. - { - AwakableList awakable_list; - test::SimpleWaiterThread thread(&result, &context); - awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 1); - thread.Start(); - awakable_list.CancelAll(); - // Double-remove okay: - awakable_list.Remove(thread.waiter()); - } // Join |thread|. - EXPECT_EQ(MOJO_RESULT_CANCELLED, result); - EXPECT_EQ(1u, context); - - // Cancel before after thread start. - { - AwakableList awakable_list; - test::SimpleWaiterThread thread(&result, &context); - awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 2); - awakable_list.CancelAll(); - thread.Start(); - } // Join |thread|. - EXPECT_EQ(MOJO_RESULT_CANCELLED, result); - EXPECT_EQ(2u, context); - - // Cancel some time after thread start. - { - AwakableList awakable_list; - test::SimpleWaiterThread thread(&result, &context); - awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 3); - thread.Start(); - test::Sleep(2 * test::EpsilonDeadline()); - awakable_list.CancelAll(); - } // Join |thread|. - EXPECT_EQ(MOJO_RESULT_CANCELLED, result); - EXPECT_EQ(3u, context); -} - -TEST(AwakableListTest, BasicAwakeSatisfied) { - MojoResult result; - uintptr_t context; - - // Awake immediately after thread start. - { - AwakableList awakable_list; - test::SimpleWaiterThread thread(&result, &context); - awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 1); - thread.Start(); - awakable_list.AwakeForStateChange(HandleSignalsState( - MOJO_HANDLE_SIGNAL_READABLE, - MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE)); - awakable_list.Remove(thread.waiter()); - } // Join |thread|. - EXPECT_EQ(MOJO_RESULT_OK, result); - EXPECT_EQ(1u, context); - - // Awake before after thread start. - { - AwakableList awakable_list; - test::SimpleWaiterThread thread(&result, &context); - awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 2); - awakable_list.AwakeForStateChange(HandleSignalsState( - MOJO_HANDLE_SIGNAL_WRITABLE, - MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE)); - awakable_list.Remove(thread.waiter()); - // Double-remove okay: - awakable_list.Remove(thread.waiter()); - thread.Start(); - } // Join |thread|. - EXPECT_EQ(MOJO_RESULT_OK, result); - EXPECT_EQ(2u, context); - - // Awake some time after thread start. - { - AwakableList awakable_list; - test::SimpleWaiterThread thread(&result, &context); - awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 3); - thread.Start(); - test::Sleep(2 * test::EpsilonDeadline()); - awakable_list.AwakeForStateChange(HandleSignalsState( - MOJO_HANDLE_SIGNAL_READABLE, - MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE)); - awakable_list.Remove(thread.waiter()); - } // Join |thread|. - EXPECT_EQ(MOJO_RESULT_OK, result); - EXPECT_EQ(3u, context); -} - -TEST(AwakableListTest, BasicAwakeUnsatisfiable) { - MojoResult result; - uintptr_t context; - - // Awake (for unsatisfiability) immediately after thread start. - { - AwakableList awakable_list; - test::SimpleWaiterThread thread(&result, &context); - awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 1); - thread.Start(); - awakable_list.AwakeForStateChange(HandleSignalsState( - MOJO_HANDLE_SIGNAL_NONE, MOJO_HANDLE_SIGNAL_WRITABLE)); - awakable_list.Remove(thread.waiter()); - } // Join |thread|. - EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); - EXPECT_EQ(1u, context); - - // Awake (for unsatisfiability) before after thread start. - { - AwakableList awakable_list; - test::SimpleWaiterThread thread(&result, &context); - awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 2); - awakable_list.AwakeForStateChange(HandleSignalsState( - MOJO_HANDLE_SIGNAL_READABLE, MOJO_HANDLE_SIGNAL_READABLE)); - awakable_list.Remove(thread.waiter()); - thread.Start(); - } // Join |thread|. - EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); - EXPECT_EQ(2u, context); - - // Awake (for unsatisfiability) some time after thread start. - { - AwakableList awakable_list; - test::SimpleWaiterThread thread(&result, &context); - awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 3); - thread.Start(); - test::Sleep(2 * test::EpsilonDeadline()); - awakable_list.AwakeForStateChange(HandleSignalsState( - MOJO_HANDLE_SIGNAL_NONE, MOJO_HANDLE_SIGNAL_WRITABLE)); - awakable_list.Remove(thread.waiter()); - // Double-remove okay: - awakable_list.Remove(thread.waiter()); - } // Join |thread|. - EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); - EXPECT_EQ(3u, context); -} - -TEST(AwakableListTest, MultipleAwakables) { - MojoResult result1; - MojoResult result2; - MojoResult result3; - MojoResult result4; - uintptr_t context1; - uintptr_t context2; - uintptr_t context3; - uintptr_t context4; - - // Cancel two awakables. - { - AwakableList awakable_list; - test::SimpleWaiterThread thread1(&result1, &context1); - awakable_list.Add(thread1.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 1); - thread1.Start(); - test::SimpleWaiterThread thread2(&result2, &context2); - awakable_list.Add(thread2.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 2); - thread2.Start(); - test::Sleep(2 * test::EpsilonDeadline()); - awakable_list.CancelAll(); - } // Join threads. - EXPECT_EQ(MOJO_RESULT_CANCELLED, result1); - EXPECT_EQ(1u, context1); - EXPECT_EQ(MOJO_RESULT_CANCELLED, result2); - EXPECT_EQ(2u, context2); - - // Awake one awakable, cancel other. - { - AwakableList awakable_list; - test::SimpleWaiterThread thread1(&result1, &context1); - awakable_list.Add(thread1.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 3); - thread1.Start(); - test::SimpleWaiterThread thread2(&result2, &context2); - awakable_list.Add(thread2.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 4); - thread2.Start(); - test::Sleep(2 * test::EpsilonDeadline()); - awakable_list.AwakeForStateChange(HandleSignalsState( - MOJO_HANDLE_SIGNAL_READABLE, - MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE)); - awakable_list.Remove(thread1.waiter()); - awakable_list.CancelAll(); - } // Join threads. - EXPECT_EQ(MOJO_RESULT_OK, result1); - EXPECT_EQ(3u, context1); - EXPECT_EQ(MOJO_RESULT_CANCELLED, result2); - EXPECT_EQ(4u, context2); - - // Cancel one awakable, awake other for unsatisfiability. - { - AwakableList awakable_list; - test::SimpleWaiterThread thread1(&result1, &context1); - awakable_list.Add(thread1.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 5); - thread1.Start(); - test::SimpleWaiterThread thread2(&result2, &context2); - awakable_list.Add(thread2.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 6); - thread2.Start(); - test::Sleep(2 * test::EpsilonDeadline()); - awakable_list.AwakeForStateChange(HandleSignalsState( - MOJO_HANDLE_SIGNAL_NONE, MOJO_HANDLE_SIGNAL_READABLE)); - awakable_list.Remove(thread2.waiter()); - awakable_list.CancelAll(); - } // Join threads. - EXPECT_EQ(MOJO_RESULT_CANCELLED, result1); - EXPECT_EQ(5u, context1); - EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result2); - EXPECT_EQ(6u, context2); - - // Cancel one awakable, awake other for unsatisfiability. - { - AwakableList awakable_list; - test::SimpleWaiterThread thread1(&result1, &context1); - awakable_list.Add(thread1.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 7); - thread1.Start(); - - test::Sleep(1 * test::EpsilonDeadline()); - - // Should do nothing. - awakable_list.AwakeForStateChange(HandleSignalsState( - MOJO_HANDLE_SIGNAL_NONE, - MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE)); - - test::SimpleWaiterThread thread2(&result2, &context2); - awakable_list.Add(thread2.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 8); - thread2.Start(); - - test::Sleep(1 * test::EpsilonDeadline()); - - // Awake #1. - awakable_list.AwakeForStateChange(HandleSignalsState( - MOJO_HANDLE_SIGNAL_READABLE, - MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE)); - awakable_list.Remove(thread1.waiter()); - - test::Sleep(1 * test::EpsilonDeadline()); - - test::SimpleWaiterThread thread3(&result3, &context3); - awakable_list.Add(thread3.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 9); - thread3.Start(); - - test::SimpleWaiterThread thread4(&result4, &context4); - awakable_list.Add(thread4.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 10); - thread4.Start(); - - test::Sleep(1 * test::EpsilonDeadline()); - - // Awake #2 and #3 for unsatisfiability. - awakable_list.AwakeForStateChange(HandleSignalsState( - MOJO_HANDLE_SIGNAL_NONE, MOJO_HANDLE_SIGNAL_READABLE)); - awakable_list.Remove(thread2.waiter()); - awakable_list.Remove(thread3.waiter()); - - // Cancel #4. - awakable_list.CancelAll(); - } // Join threads. - EXPECT_EQ(MOJO_RESULT_OK, result1); - EXPECT_EQ(7u, context1); - EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result2); - EXPECT_EQ(8u, context2); - EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result3); - EXPECT_EQ(9u, context3); - EXPECT_EQ(MOJO_RESULT_CANCELLED, result4); - EXPECT_EQ(10u, context4); -} - -class KeepAwakable : public Awakable { - public: - KeepAwakable() : awake_count(0) {} - - bool Awake(MojoResult result, uintptr_t context) override { - awake_count++; - return true; - } - - int awake_count; - - DISALLOW_COPY_AND_ASSIGN(KeepAwakable); -}; - -class RemoveAwakable : public Awakable { - public: - RemoveAwakable() : awake_count(0) {} - - bool Awake(MojoResult result, uintptr_t context) override { - awake_count++; - return false; - } - - int awake_count; - - DISALLOW_COPY_AND_ASSIGN(RemoveAwakable); -}; - -TEST(AwakableListTest, KeepAwakablesReturningTrue) { - KeepAwakable keep0; - KeepAwakable keep1; - RemoveAwakable remove0; - RemoveAwakable remove1; - RemoveAwakable remove2; - - HandleSignalsState hss(MOJO_HANDLE_SIGNAL_WRITABLE, - MOJO_HANDLE_SIGNAL_WRITABLE); - - AwakableList remove_all; - remove_all.Add(&remove0, MOJO_HANDLE_SIGNAL_WRITABLE, 0); - remove_all.Add(&remove1, MOJO_HANDLE_SIGNAL_WRITABLE, 0); - - remove_all.AwakeForStateChange(hss); - EXPECT_EQ(remove0.awake_count, 1); - EXPECT_EQ(remove1.awake_count, 1); - - remove_all.AwakeForStateChange(hss); - EXPECT_EQ(remove0.awake_count, 1); - EXPECT_EQ(remove1.awake_count, 1); - - AwakableList remove_first; - remove_first.Add(&remove2, MOJO_HANDLE_SIGNAL_WRITABLE, 0); - remove_first.Add(&keep0, MOJO_HANDLE_SIGNAL_WRITABLE, 0); - remove_first.Add(&keep1, MOJO_HANDLE_SIGNAL_WRITABLE, 0); - - remove_first.AwakeForStateChange(hss); - EXPECT_EQ(keep0.awake_count, 1); - EXPECT_EQ(keep1.awake_count, 1); - EXPECT_EQ(remove2.awake_count, 1); - - remove_first.AwakeForStateChange(hss); - EXPECT_EQ(keep0.awake_count, 2); - EXPECT_EQ(keep1.awake_count, 2); - EXPECT_EQ(remove2.awake_count, 1); - - remove_first.Remove(&keep0); - remove_first.Remove(&keep1); -} - -} // namespace -} // namespace edk -} // namespace mojo diff --git a/mojo/edk/system/configuration.cc b/mojo/edk/system/configuration.cc index 9aaed31..f5eb2b8 100644 --- a/mojo/edk/system/configuration.cc +++ b/mojo/edk/system/configuration.cc @@ -13,7 +13,6 @@ namespace internal { Configuration g_configuration = { 1000000, // max_handle_table_size 1000000, // max_mapping_table_sze - 1000000, // max_wait_many_num_handles 4 * 1024 * 1024, // max_message_num_bytes 10000, // max_message_num_handles 256 * 1024 * 1024, // max_data_pipe_capacity_bytes diff --git a/mojo/edk/system/core.cc b/mojo/edk/system/core.cc index 1e0bf4e..360e8c3 100644 --- a/mojo/edk/system/core.cc +++ b/mojo/edk/system/core.cc @@ -33,8 +33,7 @@ #include "mojo/edk/system/ports/node.h" #include "mojo/edk/system/request_context.h" #include "mojo/edk/system/shared_buffer_dispatcher.h" -#include "mojo/edk/system/wait_set_dispatcher.h" -#include "mojo/edk/system/waiter.h" +#include "mojo/edk/system/watcher_dispatcher.h" namespace mojo { namespace edk { @@ -48,15 +47,6 @@ const uint32_t kMaxHandlesPerMessage = 1024 * 1024; // pipes too; for now we just use a constant. This only affects bootstrap pipes. const uint64_t kUnknownPipeIdForDebug = 0x7f7f7f7f7f7f7f7fUL; -void CallWatchCallback(MojoWatchCallback callback, - uintptr_t context, - MojoResult result, - const HandleSignalsState& signals_state, - MojoWatchNotificationFlags flags) { - callback(context, result, static_cast<MojoHandleSignalsState>(signals_state), - flags); -} - MojoResult MojoPlatformHandleToScopedPlatformHandle( const MojoPlatformHandle* platform_handle, ScopedPlatformHandle* out_handle) { @@ -386,66 +376,61 @@ MojoResult Core::Close(MojoHandle handle) { return MOJO_RESULT_OK; } -MojoResult Core::Wait(MojoHandle handle, - MojoHandleSignals signals, - MojoDeadline deadline, - MojoHandleSignalsState* signals_state) { +MojoResult Core::QueryHandleSignalsState( + MojoHandle handle, + MojoHandleSignalsState* signals_state) { RequestContext request_context; - uint32_t unused = static_cast<uint32_t>(-1); - HandleSignalsState hss; - MojoResult rv = WaitManyInternal(&handle, &signals, 1, deadline, &unused, - signals_state ? &hss : nullptr); - if (rv != MOJO_RESULT_INVALID_ARGUMENT && signals_state) - *signals_state = hss; - return rv; + scoped_refptr<Dispatcher> dispatcher = GetDispatcher(handle); + if (!dispatcher || !signals_state) + return MOJO_RESULT_INVALID_ARGUMENT; + *signals_state = dispatcher->GetHandleSignalsState(); + return MOJO_RESULT_OK; } -MojoResult Core::WaitMany(const MojoHandle* handles, - const MojoHandleSignals* signals, - uint32_t num_handles, - MojoDeadline deadline, - uint32_t* result_index, - MojoHandleSignalsState* signals_state) { +MojoResult Core::CreateWatcher(MojoWatcherCallback callback, + MojoHandle* watcher_handle) { RequestContext request_context; - if (num_handles < 1) + if (!watcher_handle) return MOJO_RESULT_INVALID_ARGUMENT; - if (num_handles > GetConfiguration().max_wait_many_num_handles) + *watcher_handle = AddDispatcher(new WatcherDispatcher(callback)); + if (*watcher_handle == MOJO_HANDLE_INVALID) return MOJO_RESULT_RESOURCE_EXHAUSTED; - - uint32_t index = static_cast<uint32_t>(-1); - MojoResult rv; - if (!signals_state) { - rv = WaitManyInternal(handles, signals, num_handles, deadline, &index, - nullptr); - } else { - // Note: The |reinterpret_cast| is safe, since |HandleSignalsState| is a - // subclass of |MojoHandleSignalsState| that doesn't add any data members. - rv = WaitManyInternal(handles, signals, num_handles, deadline, &index, - reinterpret_cast<HandleSignalsState*>(signals_state)); - } - if (index != static_cast<uint32_t>(-1) && result_index) - *result_index = index; - return rv; + return MOJO_RESULT_OK; } -MojoResult Core::Watch(MojoHandle handle, +MojoResult Core::Watch(MojoHandle watcher_handle, + MojoHandle handle, MojoHandleSignals signals, - MojoWatchCallback callback, uintptr_t context) { RequestContext request_context; + scoped_refptr<Dispatcher> watcher = GetDispatcher(watcher_handle); + if (!watcher || watcher->GetType() != Dispatcher::Type::WATCHER) + return MOJO_RESULT_INVALID_ARGUMENT; scoped_refptr<Dispatcher> dispatcher = GetDispatcher(handle); if (!dispatcher) return MOJO_RESULT_INVALID_ARGUMENT; - return dispatcher->Watch( - signals, base::Bind(&CallWatchCallback, callback, context), context); + return watcher->WatchDispatcher(dispatcher, signals, context); } -MojoResult Core::CancelWatch(MojoHandle handle, uintptr_t context) { +MojoResult Core::CancelWatch(MojoHandle watcher_handle, uintptr_t context) { RequestContext request_context; - scoped_refptr<Dispatcher> dispatcher = GetDispatcher(handle); - if (!dispatcher) + scoped_refptr<Dispatcher> watcher = GetDispatcher(watcher_handle); + if (!watcher || watcher->GetType() != Dispatcher::Type::WATCHER) + return MOJO_RESULT_INVALID_ARGUMENT; + return watcher->CancelWatch(context); +} + +MojoResult Core::ArmWatcher(MojoHandle watcher_handle, + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) { + RequestContext request_context; + scoped_refptr<Dispatcher> watcher = GetDispatcher(watcher_handle); + if (!watcher || watcher->GetType() != Dispatcher::Type::WATCHER) return MOJO_RESULT_INVALID_ARGUMENT; - return dispatcher->CancelWatch(context); + return watcher->Arm(num_ready_contexts, ready_contexts, ready_results, + ready_signals_states); } MojoResult Core::AllocMessage(uint32_t num_bytes, @@ -529,83 +514,6 @@ MojoResult Core::GetProperty(MojoPropertyType type, void* value) { } } -MojoResult Core::CreateWaitSet(MojoHandle* wait_set_handle) { - RequestContext request_context; - if (!wait_set_handle) - return MOJO_RESULT_INVALID_ARGUMENT; - - scoped_refptr<WaitSetDispatcher> dispatcher = new WaitSetDispatcher(); - MojoHandle h = AddDispatcher(dispatcher); - if (h == MOJO_HANDLE_INVALID) { - LOG(ERROR) << "Handle table full"; - dispatcher->Close(); - return MOJO_RESULT_RESOURCE_EXHAUSTED; - } - - *wait_set_handle = h; - return MOJO_RESULT_OK; -} - -MojoResult Core::AddHandle(MojoHandle wait_set_handle, - MojoHandle handle, - MojoHandleSignals signals) { - RequestContext request_context; - scoped_refptr<Dispatcher> wait_set_dispatcher(GetDispatcher(wait_set_handle)); - if (!wait_set_dispatcher) - return MOJO_RESULT_INVALID_ARGUMENT; - - scoped_refptr<Dispatcher> dispatcher(GetDispatcher(handle)); - if (!dispatcher) - return MOJO_RESULT_INVALID_ARGUMENT; - - return wait_set_dispatcher->AddWaitingDispatcher(dispatcher, signals, handle); -} - -MojoResult Core::RemoveHandle(MojoHandle wait_set_handle, - MojoHandle handle) { - RequestContext request_context; - scoped_refptr<Dispatcher> wait_set_dispatcher(GetDispatcher(wait_set_handle)); - if (!wait_set_dispatcher) - return MOJO_RESULT_INVALID_ARGUMENT; - - scoped_refptr<Dispatcher> dispatcher(GetDispatcher(handle)); - if (!dispatcher) - return MOJO_RESULT_INVALID_ARGUMENT; - - return wait_set_dispatcher->RemoveWaitingDispatcher(dispatcher); -} - -MojoResult Core::GetReadyHandles(MojoHandle wait_set_handle, - uint32_t* count, - MojoHandle* handles, - MojoResult* results, - MojoHandleSignalsState* signals_states) { - RequestContext request_context; - if (!handles || !count || !(*count) || !results) - return MOJO_RESULT_INVALID_ARGUMENT; - - scoped_refptr<Dispatcher> wait_set_dispatcher(GetDispatcher(wait_set_handle)); - if (!wait_set_dispatcher) - return MOJO_RESULT_INVALID_ARGUMENT; - - DispatcherVector awoken_dispatchers; - base::StackVector<uintptr_t, 16> contexts; - contexts->assign(*count, MOJO_HANDLE_INVALID); - - MojoResult result = wait_set_dispatcher->GetReadyDispatchers( - count, &awoken_dispatchers, results, contexts->data()); - - if (result == MOJO_RESULT_OK) { - for (size_t i = 0; i < *count; i++) { - handles[i] = static_cast<MojoHandle>(contexts[i]); - if (signals_states) - signals_states[i] = awoken_dispatchers[i]->GetHandleSignalsState(); - } - } - - return result; -} - MojoResult Core::CreateMessagePipe( const MojoCreateMessagePipeOptions* options, MojoHandle* message_pipe_handle0, @@ -1097,74 +1005,6 @@ void Core::GetActiveHandlesForTest(std::vector<MojoHandle>* handles) { handles_.GetActiveHandlesForTest(handles); } -MojoResult Core::WaitManyInternal(const MojoHandle* handles, - const MojoHandleSignals* signals, - uint32_t num_handles, - MojoDeadline deadline, - uint32_t* result_index, - HandleSignalsState* signals_states) { - CHECK(handles); - CHECK(signals); - DCHECK_GT(num_handles, 0u); - if (result_index) { - DCHECK_EQ(*result_index, static_cast<uint32_t>(-1)); - } - - // The primary caller of |WaitManyInternal()| is |Wait()|, which only waits on - // a single handle. In the common case of a single handle, this avoid a heap - // allocation. - base::StackVector<scoped_refptr<Dispatcher>, 1> dispatchers; - dispatchers->reserve(num_handles); - for (uint32_t i = 0; i < num_handles; i++) { - scoped_refptr<Dispatcher> dispatcher = GetDispatcher(handles[i]); - if (!dispatcher) { - if (result_index) - *result_index = i; - return MOJO_RESULT_INVALID_ARGUMENT; - } - dispatchers->push_back(dispatcher); - } - - // TODO(vtl): Should make the waiter live (permanently) in TLS. - Waiter waiter; - waiter.Init(); - - uint32_t i; - MojoResult rv = MOJO_RESULT_OK; - for (i = 0; i < num_handles; i++) { - rv = dispatchers[i]->AddAwakable( - &waiter, signals[i], i, signals_states ? &signals_states[i] : nullptr); - if (rv != MOJO_RESULT_OK) { - if (result_index) - *result_index = i; - break; - } - } - uint32_t num_added = i; - - if (rv == MOJO_RESULT_ALREADY_EXISTS) { - rv = MOJO_RESULT_OK; // The i-th one is already "triggered". - } else if (rv == MOJO_RESULT_OK) { - uintptr_t uintptr_result = *result_index; - rv = waiter.Wait(deadline, &uintptr_result); - *result_index = static_cast<uint32_t>(uintptr_result); - } - - // Make sure no other dispatchers try to wake |waiter| for the current - // |Wait()|/|WaitMany()| call. (Only after doing this can |waiter| be - // destroyed, but this would still be required if the waiter were in TLS.) - for (i = 0; i < num_added; i++) { - dispatchers[i]->RemoveAwakable( - &waiter, signals_states ? &signals_states[i] : nullptr); - } - if (signals_states) { - for (; i < num_handles; i++) - signals_states[i] = dispatchers[i]->GetHandleSignalsState(); - } - - return rv; -} - // static void Core::PassNodeControllerToIOThread( std::unique_ptr<NodeController> node_controller) { diff --git a/mojo/edk/system/core.h b/mojo/edk/system/core.h index 1e20a87..1f6d865 100644 --- a/mojo/edk/system/core.h +++ b/mojo/edk/system/core.h @@ -27,6 +27,7 @@ #include "mojo/public/c/system/message_pipe.h" #include "mojo/public/c/system/platform_handle.h" #include "mojo/public/c/system/types.h" +#include "mojo/public/c/system/watcher.h" #include "mojo/public/cpp/system/message_pipe.h" namespace base { @@ -135,21 +136,20 @@ class MOJO_SYSTEM_IMPL_EXPORT Core { // "mojo/public/c/system/functions.h": MojoTimeTicks GetTimeTicksNow(); MojoResult Close(MojoHandle handle); - MojoResult Wait(MojoHandle handle, - MojoHandleSignals signals, - MojoDeadline deadline, - MojoHandleSignalsState* signals_state); - MojoResult WaitMany(const MojoHandle* handles, - const MojoHandleSignals* signals, - uint32_t num_handles, - MojoDeadline deadline, - uint32_t* result_index, - MojoHandleSignalsState* signals_states); - MojoResult Watch(MojoHandle handle, + MojoResult QueryHandleSignalsState(MojoHandle handle, + MojoHandleSignalsState* signals_state); + MojoResult CreateWatcher(MojoWatcherCallback callback, + MojoHandle* watcher_handle); + MojoResult Watch(MojoHandle watcher_handle, + MojoHandle handle, MojoHandleSignals signals, - MojoWatchCallback callback, uintptr_t context); - MojoResult CancelWatch(MojoHandle handle, uintptr_t context); + MojoResult CancelWatch(MojoHandle watcher_handle, uintptr_t context); + MojoResult ArmWatcher(MojoHandle watcher_handle, + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states); MojoResult AllocMessage(uint32_t num_bytes, const MojoHandle* handles, uint32_t num_handles, @@ -160,20 +160,6 @@ class MOJO_SYSTEM_IMPL_EXPORT Core { MojoResult GetProperty(MojoPropertyType type, void* value); // These methods correspond to the API functions defined in - // "mojo/public/c/system/wait_set.h": - MojoResult CreateWaitSet(MojoHandle* wait_set_handle); - MojoResult AddHandle(MojoHandle wait_set_handle, - MojoHandle handle, - MojoHandleSignals signals); - MojoResult RemoveHandle(MojoHandle wait_set_handle, - MojoHandle handle); - MojoResult GetReadyHandles(MojoHandle wait_set_handle, - uint32_t* count, - MojoHandle* handles, - MojoResult* results, - MojoHandleSignalsState* signals_states); - - // These methods correspond to the API functions defined in // "mojo/public/c/system/message_pipe.h": MojoResult CreateMessagePipe( const MojoCreateMessagePipeOptions* options, @@ -269,13 +255,6 @@ class MOJO_SYSTEM_IMPL_EXPORT Core { void GetActiveHandlesForTest(std::vector<MojoHandle>* handles); private: - MojoResult WaitManyInternal(const MojoHandle* handles, - const MojoHandleSignals* signals, - uint32_t num_handles, - MojoDeadline deadline, - uint32_t* result_index, - HandleSignalsState* signals_states); - // Used to pass ownership of our NodeController over to the IO thread in the // event that we're torn down before said thread. static void PassNodeControllerToIOThread( diff --git a/mojo/edk/system/core_test_base.cc b/mojo/edk/system/core_test_base.cc index e98a55d..7751612 100644 --- a/mojo/edk/system/core_test_base.cc +++ b/mojo/edk/system/core_test_base.cc @@ -107,28 +107,6 @@ class MockDispatcher : public Dispatcher { return MOJO_RESULT_UNIMPLEMENTED; } - MojoResult AddAwakable(Awakable* awakable, - MojoHandleSignals /*signals*/, - uintptr_t /*context*/, - HandleSignalsState* signals_state) override { - info_->IncrementAddAwakableCallCount(); - if (signals_state) - *signals_state = HandleSignalsState(); - if (info_->IsAddAwakableAllowed()) { - info_->AwakableWasAdded(awakable); - return MOJO_RESULT_OK; - } - - return MOJO_RESULT_FAILED_PRECONDITION; - } - - void RemoveAwakable(Awakable* /*awakable*/, - HandleSignalsState* signals_state) override { - info_->IncrementRemoveAwakableCallCount(); - if (signals_state) - *signals_state = HandleSignalsState(); - } - private: explicit MockDispatcher(CoreTestBase::MockHandleInfo* info) : info_(info) { CHECK(info_); @@ -174,11 +152,7 @@ CoreTestBase_MockHandleInfo::CoreTestBase_MockHandleInfo() end_write_data_call_count_(0), read_data_call_count_(0), begin_read_data_call_count_(0), - end_read_data_call_count_(0), - add_awakable_call_count_(0), - remove_awakable_call_count_(0), - add_awakable_allowed_(false) { -} + end_read_data_call_count_(0) {} CoreTestBase_MockHandleInfo::~CoreTestBase_MockHandleInfo() { } @@ -238,26 +212,6 @@ unsigned CoreTestBase_MockHandleInfo::GetEndReadDataCallCount() const { return end_read_data_call_count_; } -unsigned CoreTestBase_MockHandleInfo::GetAddAwakableCallCount() const { - base::AutoLock locker(lock_); - return add_awakable_call_count_; -} - -unsigned CoreTestBase_MockHandleInfo::GetRemoveAwakableCallCount() const { - base::AutoLock locker(lock_); - return remove_awakable_call_count_; -} - -size_t CoreTestBase_MockHandleInfo::GetAddedAwakableSize() const { - base::AutoLock locker(lock_); - return added_awakables_.size(); -} - -Awakable* CoreTestBase_MockHandleInfo::GetAddedAwakableAt(unsigned i) const { - base::AutoLock locker(lock_); - return added_awakables_[i]; -} - void CoreTestBase_MockHandleInfo::IncrementCtorCallCount() { base::AutoLock locker(lock_); ctor_call_count_++; @@ -313,31 +267,6 @@ void CoreTestBase_MockHandleInfo::IncrementEndReadDataCallCount() { end_read_data_call_count_++; } -void CoreTestBase_MockHandleInfo::IncrementAddAwakableCallCount() { - base::AutoLock locker(lock_); - add_awakable_call_count_++; -} - -void CoreTestBase_MockHandleInfo::IncrementRemoveAwakableCallCount() { - base::AutoLock locker(lock_); - remove_awakable_call_count_++; -} - -void CoreTestBase_MockHandleInfo::AllowAddAwakable(bool alllow) { - base::AutoLock locker(lock_); - add_awakable_allowed_ = alllow; -} - -bool CoreTestBase_MockHandleInfo::IsAddAwakableAllowed() const { - base::AutoLock locker(lock_); - return add_awakable_allowed_; -} - -void CoreTestBase_MockHandleInfo::AwakableWasAdded(Awakable* awakable) { - base::AutoLock locker(lock_); - added_awakables_.push_back(awakable); -} - } // namespace test } // namespace edk } // namespace mojo diff --git a/mojo/edk/system/core_test_base.h b/mojo/edk/system/core_test_base.h index 3d2346a..3d156e3 100644 --- a/mojo/edk/system/core_test_base.h +++ b/mojo/edk/system/core_test_base.h @@ -18,7 +18,6 @@ namespace mojo { namespace edk { class Core; -class Awakable; namespace test { @@ -57,11 +56,6 @@ class CoreTestBase_MockHandleInfo { unsigned GetReadDataCallCount() const; unsigned GetBeginReadDataCallCount() const; unsigned GetEndReadDataCallCount() const; - unsigned GetAddAwakableCallCount() const; - unsigned GetRemoveAwakableCallCount() const; - - size_t GetAddedAwakableSize() const; - Awakable* GetAddedAwakableAt(unsigned i) const; // For use by |MockDispatcher|: void IncrementCtorCallCount(); @@ -75,12 +69,6 @@ class CoreTestBase_MockHandleInfo { void IncrementReadDataCallCount(); void IncrementBeginReadDataCallCount(); void IncrementEndReadDataCallCount(); - void IncrementAddAwakableCallCount(); - void IncrementRemoveAwakableCallCount(); - - void AllowAddAwakable(bool alllow); - bool IsAddAwakableAllowed() const; - void AwakableWasAdded(Awakable*); private: mutable base::Lock lock_; // Protects the following members. @@ -95,11 +83,6 @@ class CoreTestBase_MockHandleInfo { unsigned read_data_call_count_; unsigned begin_read_data_call_count_; unsigned end_read_data_call_count_; - unsigned add_awakable_call_count_; - unsigned remove_awakable_call_count_; - - bool add_awakable_allowed_; - std::vector<Awakable*> added_awakables_; DISALLOW_COPY_AND_ASSIGN(CoreTestBase_MockHandleInfo); }; diff --git a/mojo/edk/system/core_unittest.cc b/mojo/edk/system/core_unittest.cc index 814ce4b..0d60b48 100644 --- a/mojo/edk/system/core_unittest.cc +++ b/mojo/edk/system/core_unittest.cc @@ -10,9 +10,9 @@ #include "base/bind.h" #include "mojo/edk/embedder/embedder_internal.h" -#include "mojo/edk/system/awakable.h" #include "mojo/edk/system/core_test_base.h" #include "mojo/edk/system/test_utils.h" +#include "mojo/public/cpp/system/wait.h" #if defined(OS_WIN) #include "base/win/windows_version.h" @@ -97,72 +97,11 @@ TEST_F(CoreTest, Basic) { ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED, core()->EndReadData(h, 0)); ASSERT_EQ(1u, info.GetEndReadDataCallCount()); - ASSERT_EQ(0u, info.GetAddAwakableCallCount()); - ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, MOJO_DEADLINE_INDEFINITE, - nullptr)); - ASSERT_EQ(1u, info.GetAddAwakableCallCount()); - ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, 0, nullptr)); - ASSERT_EQ(2u, info.GetAddAwakableCallCount()); - MojoHandleSignalsState hss = kFullMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, MOJO_DEADLINE_INDEFINITE, - &hss)); - ASSERT_EQ(3u, info.GetAddAwakableCallCount()); - ASSERT_EQ(0u, hss.satisfied_signals); - ASSERT_EQ(0u, hss.satisfiable_signals); - ASSERT_EQ( - MOJO_RESULT_FAILED_PRECONDITION, - core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, 10 * 1000, nullptr)); - ASSERT_EQ(4u, info.GetAddAwakableCallCount()); - hss = kFullMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, 10 * 1000, &hss)); - ASSERT_EQ(5u, info.GetAddAwakableCallCount()); - ASSERT_EQ(0u, hss.satisfied_signals); - ASSERT_EQ(0u, hss.satisfiable_signals); - - MojoHandleSignals handle_signals = ~MOJO_HANDLE_SIGNAL_NONE; - ASSERT_EQ( - MOJO_RESULT_FAILED_PRECONDITION, - core()->WaitMany(&h, &handle_signals, 1, MOJO_DEADLINE_INDEFINITE, - nullptr, nullptr)); - ASSERT_EQ(6u, info.GetAddAwakableCallCount()); - uint32_t result_index = static_cast<uint32_t>(-1); - ASSERT_EQ( - MOJO_RESULT_FAILED_PRECONDITION, - core()->WaitMany(&h, &handle_signals, 1, MOJO_DEADLINE_INDEFINITE, - &result_index, nullptr)); - ASSERT_EQ(7u, info.GetAddAwakableCallCount()); - ASSERT_EQ(0u, result_index); - hss = kFullMojoHandleSignalsState; - ASSERT_EQ( - MOJO_RESULT_FAILED_PRECONDITION, - core()->WaitMany(&h, &handle_signals, 1, MOJO_DEADLINE_INDEFINITE, - nullptr, &hss)); - ASSERT_EQ(8u, info.GetAddAwakableCallCount()); - ASSERT_EQ(0u, hss.satisfied_signals); - ASSERT_EQ(0u, hss.satisfiable_signals); - result_index = static_cast<uint32_t>(-1); - hss = kFullMojoHandleSignalsState; - ASSERT_EQ( - MOJO_RESULT_FAILED_PRECONDITION, - core()->WaitMany(&h, &handle_signals, 1, MOJO_DEADLINE_INDEFINITE, - &result_index, &hss)); - ASSERT_EQ(9u, info.GetAddAwakableCallCount()); - ASSERT_EQ(0u, result_index); - ASSERT_EQ(0u, hss.satisfied_signals); - ASSERT_EQ(0u, hss.satisfiable_signals); - ASSERT_EQ(0u, info.GetDtorCallCount()); ASSERT_EQ(0u, info.GetCloseCallCount()); ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h)); ASSERT_EQ(1u, info.GetCloseCallCount()); ASSERT_EQ(1u, info.GetDtorCallCount()); - - // No awakables should ever have ever been added. - ASSERT_EQ(0u, info.GetRemoveAwakableCallCount()); } TEST_F(CoreTest, InvalidArguments) { @@ -181,125 +120,6 @@ TEST_F(CoreTest, InvalidArguments) { ASSERT_EQ(1u, info.GetCloseCallCount()); } - // |Wait()|: - { - ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - core()->Wait(MOJO_HANDLE_INVALID, ~MOJO_HANDLE_SIGNAL_NONE, - MOJO_DEADLINE_INDEFINITE, nullptr)); - ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - core()->Wait(10, ~MOJO_HANDLE_SIGNAL_NONE, - MOJO_DEADLINE_INDEFINITE, nullptr)); - - MojoHandleSignalsState hss = kFullMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - core()->Wait(MOJO_HANDLE_INVALID, ~MOJO_HANDLE_SIGNAL_NONE, - MOJO_DEADLINE_INDEFINITE, &hss)); - // On invalid argument, it shouldn't modify the handle signals state. - ASSERT_EQ(kFullMojoHandleSignalsState.satisfied_signals, - hss.satisfied_signals); - ASSERT_EQ(kFullMojoHandleSignalsState.satisfiable_signals, - hss.satisfiable_signals); - hss = kFullMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - core()->Wait(10, ~MOJO_HANDLE_SIGNAL_NONE, - MOJO_DEADLINE_INDEFINITE, &hss)); - // On invalid argument, it shouldn't modify the handle signals state. - ASSERT_EQ(kFullMojoHandleSignalsState.satisfied_signals, - hss.satisfied_signals); - ASSERT_EQ(kFullMojoHandleSignalsState.satisfiable_signals, - hss.satisfiable_signals); - } - - // |WaitMany()|: - { - MojoHandle handles[2] = {MOJO_HANDLE_INVALID, MOJO_HANDLE_INVALID}; - MojoHandleSignals signals[2] = {~MOJO_HANDLE_SIGNAL_NONE, - ~MOJO_HANDLE_SIGNAL_NONE}; - ASSERT_EQ( - MOJO_RESULT_INVALID_ARGUMENT, - core()->WaitMany(handles, signals, 0, MOJO_DEADLINE_INDEFINITE, - nullptr, nullptr)); - ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - core()->WaitMany(nullptr, signals, 0, MOJO_DEADLINE_INDEFINITE, - nullptr, nullptr)); - // If |num_handles| is invalid, it should leave |result_index| and - // |signals_states| alone. - // (We use -1 internally; make sure that doesn't leak.) - uint32_t result_index = 123; - MojoHandleSignalsState hss = kFullMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - core()->WaitMany(nullptr, signals, 0, MOJO_DEADLINE_INDEFINITE, - &result_index, &hss)); - ASSERT_EQ(123u, result_index); - ASSERT_EQ(kFullMojoHandleSignalsState.satisfied_signals, - hss.satisfied_signals); - ASSERT_EQ(kFullMojoHandleSignalsState.satisfiable_signals, - hss.satisfiable_signals); - - ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - core()->WaitMany(handles, nullptr, 0, MOJO_DEADLINE_INDEFINITE, - nullptr, nullptr)); - ASSERT_EQ( - MOJO_RESULT_INVALID_ARGUMENT, - core()->WaitMany(handles, signals, 1, MOJO_DEADLINE_INDEFINITE, nullptr, - nullptr)); - // But if a handle is bad, then it should set |result_index| but still leave - // |signals_states| alone. - result_index = static_cast<uint32_t>(-1); - hss = kFullMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - core()->WaitMany( - handles, signals, 1, MOJO_DEADLINE_INDEFINITE, &result_index, - &hss)); - ASSERT_EQ(0u, result_index); - ASSERT_EQ(kFullMojoHandleSignalsState.satisfied_signals, - hss.satisfied_signals); - ASSERT_EQ(kFullMojoHandleSignalsState.satisfiable_signals, - hss.satisfiable_signals); - - MockHandleInfo info[2]; - handles[0] = CreateMockHandle(&info[0]); - - result_index = static_cast<uint32_t>(-1); - hss = kFullMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - core()->WaitMany( - handles, signals, 1, MOJO_DEADLINE_INDEFINITE, &result_index, - &hss)); - ASSERT_EQ(0u, result_index); - ASSERT_EQ(0u, hss.satisfied_signals); - ASSERT_EQ(0u, hss.satisfiable_signals); - - // On invalid argument, it'll leave |signals_states| alone. - result_index = static_cast<uint32_t>(-1); - hss = kFullMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - core()->WaitMany( - handles, signals, 2, MOJO_DEADLINE_INDEFINITE, &result_index, - &hss)); - ASSERT_EQ(1u, result_index); - ASSERT_EQ(kFullMojoHandleSignalsState.satisfied_signals, - hss.satisfied_signals); - ASSERT_EQ(kFullMojoHandleSignalsState.satisfiable_signals, - hss.satisfiable_signals); - handles[1] = handles[0] + 1; // Invalid handle. - ASSERT_EQ( - MOJO_RESULT_INVALID_ARGUMENT, - core()->WaitMany(handles, signals, 2, MOJO_DEADLINE_INDEFINITE, nullptr, - nullptr)); - handles[1] = CreateMockHandle(&info[1]); - ASSERT_EQ( - MOJO_RESULT_FAILED_PRECONDITION, - core()->WaitMany(handles, signals, 2, MOJO_DEADLINE_INDEFINITE, nullptr, - nullptr)); - - // TODO(vtl): Test one where we get "failed precondition" only for the - // second handle (and the first one is valid to wait on). - - ASSERT_EQ(MOJO_RESULT_OK, core()->Close(handles[0])); - ASSERT_EQ(MOJO_RESULT_OK, core()->Close(handles[1])); - } - // |CreateMessagePipe()|: Nothing to check (apart from things that cause // death). @@ -451,22 +271,6 @@ TEST_F(CoreTest, InvalidArgumentsDeath) { const char kMemoryCheckFailedRegex[] = "Check failed"; #endif - // |WaitMany()|: - { - MojoHandle handle = MOJO_HANDLE_INVALID; - MojoHandleSignals signals = ~MOJO_HANDLE_SIGNAL_NONE; - ASSERT_DEATH_IF_SUPPORTED( - core()->WaitMany(nullptr, &signals, 1, MOJO_DEADLINE_INDEFINITE, - nullptr, nullptr), - kMemoryCheckFailedRegex); - ASSERT_DEATH_IF_SUPPORTED( - core()->WaitMany(&handle, nullptr, 1, MOJO_DEADLINE_INDEFINITE, nullptr, - nullptr), - kMemoryCheckFailedRegex); - // TODO(vtl): |result_index| and |signals_states| are optional. Test them - // with non-null invalid pointers? - } - // |CreateMessagePipe()|: { MojoHandle h; @@ -498,14 +302,9 @@ TEST_F(CoreTest, InvalidArgumentsDeath) { } } -// TODO(vtl): test |Wait()| and |WaitMany()| properly -// - including |WaitMany()| with the same handle more than once (with -// same/different signals) - TEST_F(CoreTest, MessagePipe) { MojoHandle h[2]; MojoHandleSignalsState hss[2]; - uint32_t result_index; ASSERT_EQ(MOJO_RESULT_OK, core()->CreateMessagePipe(nullptr, &h[0], &h[1])); // Should get two distinct, valid handles. @@ -514,15 +313,10 @@ TEST_F(CoreTest, MessagePipe) { ASSERT_NE(h[0], h[1]); // Neither should be readable. - MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE, - MOJO_HANDLE_SIGNAL_READABLE}; - result_index = static_cast<uint32_t>(-1); hss[0] = kEmptyMojoHandleSignalsState; hss[1] = kEmptyMojoHandleSignalsState; - ASSERT_EQ( - MOJO_RESULT_DEADLINE_EXCEEDED, - core()->WaitMany(h, signals, 2, 0, &result_index, hss)); - ASSERT_EQ(static_cast<uint32_t>(-1), result_index); + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(h[0], &hss[0])); + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(h[1], &hss[1])); ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals); ASSERT_EQ(kAllSignals, hss[0].satisfiable_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[1].satisfied_signals); @@ -539,34 +333,6 @@ TEST_F(CoreTest, MessagePipe) { ASSERT_EQ('a', buffer[0]); ASSERT_EQ(1u, buffer_size); - // Both should be writable. - hss[0] = kEmptyMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_OK, core()->Wait(h[0], MOJO_HANDLE_SIGNAL_WRITABLE, - 1000000000, &hss[0])); - ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals); - ASSERT_EQ(kAllSignals, hss[0].satisfiable_signals); - hss[0] = kEmptyMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_OK, core()->Wait(h[1], MOJO_HANDLE_SIGNAL_WRITABLE, - 1000000000, &hss[0])); - ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals); - ASSERT_EQ(kAllSignals, hss[0].satisfiable_signals); - - // Also check that |h[1]| is writable using |WaitMany()|. - signals[0] = MOJO_HANDLE_SIGNAL_READABLE; - signals[1] = MOJO_HANDLE_SIGNAL_WRITABLE; - result_index = static_cast<uint32_t>(-1); - hss[0] = kEmptyMojoHandleSignalsState; - hss[1] = kEmptyMojoHandleSignalsState; - ASSERT_EQ( - MOJO_RESULT_OK, - core()->WaitMany(h, signals, 2, MOJO_DEADLINE_INDEFINITE, &result_index, - hss)); - ASSERT_EQ(1u, result_index); - ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals); - ASSERT_EQ(kAllSignals, hss[0].satisfiable_signals); - ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[1].satisfied_signals); - ASSERT_EQ(kAllSignals, hss[1].satisfiable_signals); - // Write to |h[1]|. buffer[0] = 'b'; ASSERT_EQ( @@ -574,22 +340,9 @@ TEST_F(CoreTest, MessagePipe) { core()->WriteMessage(h[1], buffer, 1, nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE)); - // Check that |h[0]| is now readable. - signals[0] = MOJO_HANDLE_SIGNAL_READABLE; - signals[1] = MOJO_HANDLE_SIGNAL_READABLE; - result_index = static_cast<uint32_t>(-1); - hss[0] = kEmptyMojoHandleSignalsState; - hss[1] = kEmptyMojoHandleSignalsState; - ASSERT_EQ( - MOJO_RESULT_OK, - core()->WaitMany(h, signals, 2, MOJO_DEADLINE_INDEFINITE, &result_index, - hss)); - ASSERT_EQ(0u, result_index); - ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, - hss[0].satisfied_signals); - ASSERT_EQ(kAllSignals, hss[0].satisfiable_signals); - ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[1].satisfied_signals); - ASSERT_EQ(kAllSignals, hss[1].satisfiable_signals); + // Wait for |h[0]| to become readable. + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h[0]), + MOJO_HANDLE_SIGNAL_READABLE, &hss[0])); // Read from |h[0]|. // First, get only the size. @@ -611,8 +364,7 @@ TEST_F(CoreTest, MessagePipe) { // |h[0]| should no longer be readable. hss[0] = kEmptyMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, - core()->Wait(h[0], MOJO_HANDLE_SIGNAL_READABLE, 0, &hss[0])); + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(h[0], &hss[0])); ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals); ASSERT_EQ(kAllSignals, hss[0].satisfiable_signals); @@ -627,28 +379,21 @@ TEST_F(CoreTest, MessagePipe) { ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h[0])); // Wait for |h[1]| to learn about the other end's closure. - ASSERT_EQ(MOJO_RESULT_OK, - core()->Wait(h[1], MOJO_HANDLE_SIGNAL_PEER_CLOSED, 1000000000, - &hss[0])); + EXPECT_EQ( + MOJO_RESULT_OK, + mojo::Wait(mojo::Handle(h[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss[1])); // Check that |h[1]| is no longer writable (and will never be). - hss[0] = kEmptyMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - core()->Wait(h[1], MOJO_HANDLE_SIGNAL_WRITABLE, 1000000000, - &hss[0])); - ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, - hss[0].satisfied_signals); - ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, - hss[0].satisfiable_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss[1].satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss[1].satisfiable_signals); // Check that |h[1]| is still readable (for the moment). - hss[0] = kEmptyMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_OK, core()->Wait(h[1], MOJO_HANDLE_SIGNAL_READABLE, - 1000000000, &hss[0])); - ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, - hss[0].satisfied_signals); - ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, - hss[0].satisfiable_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss[1].satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss[1].satisfiable_signals); // Discard a message from |h[1]|. ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, @@ -656,12 +401,10 @@ TEST_F(CoreTest, MessagePipe) { MOJO_READ_MESSAGE_FLAG_MAY_DISCARD)); // |h[1]| is no longer readable (and will never be). - hss[0] = kFullMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - core()->Wait(h[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000, - &hss[0])); - ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss[0].satisfied_signals); - ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss[0].satisfiable_signals); + hss[1] = kFullMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(h[1], &hss[1])); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss[1].satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss[1].satisfiable_signals); // Try writing to |h[1]|. buffer[0] = 'e'; @@ -696,9 +439,8 @@ TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing1) { core()->WriteMessage(h_passing[0], kHello, kHelloSize, nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE)); hss = kEmptyMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_OK, - core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000, - &hss)); + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); ASSERT_EQ(kAllSignals, hss.satisfiable_signals); @@ -737,9 +479,8 @@ TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing1) { core()->WriteMessage(h_passed[0], kHello, kHelloSize, nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE)); hss = kEmptyMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_OK, - core()->Wait(h_passed[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000, - &hss)); + ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passed[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); ASSERT_EQ(kAllSignals, hss.satisfiable_signals); @@ -759,9 +500,8 @@ TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing1) { &h_passed[1], 1, MOJO_WRITE_MESSAGE_FLAG_NONE)); hss = kEmptyMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_OK, - core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000, - &hss)); + ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); ASSERT_EQ(kAllSignals, hss.satisfiable_signals); @@ -792,9 +532,8 @@ TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing1) { core()->WriteMessage(h_passed[0], kHello, kHelloSize, nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE)); hss = kEmptyMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_OK, - core()->Wait(h_received, MOJO_HANDLE_SIGNAL_READABLE, 1000000000, - &hss)); + ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_received), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); ASSERT_EQ(kAllSignals, hss.satisfiable_signals); @@ -827,33 +566,15 @@ TEST_F(CoreTest, DataPipe) { // Producer should be never-readable, but already writable. hss = kEmptyMojoHandleSignalsState; - ASSERT_EQ( - MOJO_RESULT_FAILED_PRECONDITION, - core()->Wait(ph, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); - ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); - ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, - hss.satisfiable_signals); - hss = kEmptyMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_OK, core()->Wait(ph, MOJO_HANDLE_SIGNAL_WRITABLE, 0, - &hss)); + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ph, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); // Consumer should be never-writable, and not yet readable. hss = kFullMojoHandleSignalsState; - ASSERT_EQ( - MOJO_RESULT_FAILED_PRECONDITION, - core()->Wait(ch, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss)); - ASSERT_EQ(0u, hss.satisfied_signals); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | - MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, - hss.satisfiable_signals); - hss = kFullMojoHandleSignalsState; - ASSERT_EQ( - MOJO_RESULT_DEADLINE_EXCEEDED, - core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); - ASSERT_EQ(0u, hss.satisfied_signals); + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ch, &hss)); + EXPECT_EQ(0u, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfiable_signals); @@ -867,13 +588,12 @@ TEST_F(CoreTest, DataPipe) { ASSERT_EQ(2u, num_bytes); // Wait for the data to arrive to the consumer. - ASSERT_EQ(MOJO_RESULT_OK, - core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 1000000000, &hss)); + EXPECT_EQ(MOJO_RESULT_OK, + mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_READABLE, &hss)); // Consumer should now be readable. hss = kEmptyMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_OK, core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0, - &hss)); + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ch, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | @@ -924,7 +644,7 @@ TEST_F(CoreTest, DataPipe) { // Wait for the data to arrive to the consumer. ASSERT_EQ(MOJO_RESULT_OK, - core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 1000000000, &hss)); + mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_READABLE, &hss)); // Query how much data we have. num_bytes = 0; @@ -964,7 +684,7 @@ TEST_F(CoreTest, DataPipe) { // Ensure the 3 bytes were read. ASSERT_EQ(MOJO_RESULT_OK, - core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 1000000000, &hss)); + mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_READABLE, &hss)); // Try a two-phase read of the remaining three bytes with peek. Should fail. const void* read_ptr = nullptr; @@ -995,9 +715,7 @@ TEST_F(CoreTest, DataPipe) { // Consumer should now be no longer readable. hss = kFullMojoHandleSignalsState; - ASSERT_EQ( - MOJO_RESULT_DEADLINE_EXCEEDED, - core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ch, &hss)); EXPECT_EQ(0u, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, @@ -1009,14 +727,12 @@ TEST_F(CoreTest, DataPipe) { ASSERT_EQ(MOJO_RESULT_OK, core()->Close(ph)); // Wait for this to get to the consumer. - ASSERT_EQ(MOJO_RESULT_OK, - core()->Wait(ch, MOJO_HANDLE_SIGNAL_PEER_CLOSED, 1000000000, &hss)); + EXPECT_EQ(MOJO_RESULT_OK, + mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); // The consumer should now be never-readable. hss = kFullMojoHandleSignalsState; - ASSERT_EQ( - MOJO_RESULT_FAILED_PRECONDITION, - core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ch, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); @@ -1049,9 +765,8 @@ TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing2) { core()->WriteMessage(h_passing[0], kHello, kHelloSize, &ch, 1, MOJO_WRITE_MESSAGE_FLAG_NONE)); hss = kEmptyMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_OK, - core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000, - &hss)); + ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); ASSERT_EQ(kAllSignals, hss.satisfiable_signals); @@ -1083,9 +798,8 @@ TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing2) { core()->WriteData(ph, kWorld, &num_bytes, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); hss = kEmptyMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_OK, - core()->Wait(ch_received, MOJO_HANDLE_SIGNAL_READABLE, 1000000000, - &hss)); + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(ch_received), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | @@ -1103,9 +817,8 @@ TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing2) { core()->WriteMessage(h_passing[0], kWorld, kWorldSize, &ph, 1, MOJO_WRITE_MESSAGE_FLAG_NONE)); hss = kEmptyMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_OK, - core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000, - &hss)); + ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); ASSERT_EQ(kAllSignals, hss.satisfiable_signals); @@ -1137,9 +850,8 @@ TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing2) { core()->WriteData(ph_received, kHello, &num_bytes, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); hss = kEmptyMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_OK, - core()->Wait(ch_received, MOJO_HANDLE_SIGNAL_READABLE, 1000000000, - &hss)); + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(ch_received), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | @@ -1173,9 +885,8 @@ TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing2) { core()->WriteMessage(h_passing[0], kHello, kHelloSize, &ch, 1, MOJO_WRITE_MESSAGE_FLAG_NONE)); ch = MOJO_HANDLE_INVALID; - ASSERT_EQ(MOJO_RESULT_OK, - core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000, - nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE)); num_bytes = kBufferSize; num_handles = arraysize(handles); ASSERT_EQ(MOJO_RESULT_OK, @@ -1194,8 +905,8 @@ TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing2) { // Wait for |ch| to be readable. hss = kEmptyMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_OK, core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, - 1000000000, &hss)); + EXPECT_EQ(MOJO_RESULT_OK, + mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | @@ -1218,9 +929,8 @@ TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing2) { MOJO_WRITE_MESSAGE_FLAG_NONE)); ph = MOJO_HANDLE_INVALID; hss = kEmptyMojoHandleSignalsState; - ASSERT_EQ(MOJO_RESULT_OK, - core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000, - &hss)); + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); ASSERT_EQ(kAllSignals, hss.satisfiable_signals); diff --git a/mojo/edk/system/data_pipe_consumer_dispatcher.cc b/mojo/edk/system/data_pipe_consumer_dispatcher.cc index c908e3a..f338732 100644 --- a/mojo/edk/system/data_pipe_consumer_dispatcher.cc +++ b/mojo/edk/system/data_pipe_consumer_dispatcher.cc @@ -80,6 +80,7 @@ DataPipeConsumerDispatcher::DataPipeConsumerDispatcher( node_controller_(node_controller), control_port_(control_port), pipe_id_(pipe_id), + watchers_(this), shared_ring_buffer_(shared_ring_buffer) { if (initialized) { base::AutoLock lock(lock_); @@ -97,34 +98,10 @@ MojoResult DataPipeConsumerDispatcher::Close() { return CloseNoLock(); } - -MojoResult DataPipeConsumerDispatcher::Watch( - MojoHandleSignals signals, - const Watcher::WatchCallback& callback, - uintptr_t context) { - base::AutoLock lock(lock_); - - if (is_closed_ || in_transit_) - return MOJO_RESULT_INVALID_ARGUMENT; - - return awakable_list_.AddWatcher( - signals, callback, context, GetHandleSignalsStateNoLock()); -} - -MojoResult DataPipeConsumerDispatcher::CancelWatch(uintptr_t context) { - base::AutoLock lock(lock_); - - if (is_closed_ || in_transit_) - return MOJO_RESULT_INVALID_ARGUMENT; - - return awakable_list_.RemoveWatcher(context); -} - MojoResult DataPipeConsumerDispatcher::ReadData(void* elements, uint32_t* num_bytes, MojoReadDataFlags flags) { base::AutoLock lock(lock_); - new_data_available_ = false; if (!shared_ring_buffer_ || in_transit_) return MOJO_RESULT_INVALID_ARGUMENT; @@ -132,6 +109,9 @@ MojoResult DataPipeConsumerDispatcher::ReadData(void* elements, if (in_two_phase_read_) return MOJO_RESULT_BUSY; + const bool had_new_data = new_data_available_; + new_data_available_ = false; + if ((flags & MOJO_READ_DATA_FLAG_QUERY)) { if ((flags & MOJO_READ_DATA_FLAG_PEEK) || (flags & MOJO_READ_DATA_FLAG_DISCARD)) @@ -140,6 +120,9 @@ MojoResult DataPipeConsumerDispatcher::ReadData(void* elements, DVLOG_IF(2, elements) << "Query mode: ignoring non-null |elements|"; *num_bytes = static_cast<uint32_t>(bytes_available_); + + if (had_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); return MOJO_RESULT_OK; } @@ -162,12 +145,16 @@ MojoResult DataPipeConsumerDispatcher::ReadData(void* elements, all_or_none ? max_num_bytes_to_read : 0; if (min_num_bytes_to_read > bytes_available_) { + if (had_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); return peer_closed_ ? MOJO_RESULT_FAILED_PRECONDITION : MOJO_RESULT_OUT_OF_RANGE; } uint32_t bytes_to_read = std::min(max_num_bytes_to_read, bytes_available_); if (bytes_to_read == 0) { + if (had_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); return peer_closed_ ? MOJO_RESULT_FAILED_PRECONDITION : MOJO_RESULT_SHOULD_WAIT; } @@ -199,6 +186,10 @@ MojoResult DataPipeConsumerDispatcher::ReadData(void* elements, NotifyRead(bytes_to_read); } + // We may have just read the last available data and thus changed the signals + // state. + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + return MOJO_RESULT_OK; } @@ -206,7 +197,6 @@ MojoResult DataPipeConsumerDispatcher::BeginReadData(const void** buffer, uint32_t* buffer_num_bytes, MojoReadDataFlags flags) { base::AutoLock lock(lock_); - new_data_available_ = false; if (!shared_ring_buffer_ || in_transit_) return MOJO_RESULT_INVALID_ARGUMENT; @@ -219,7 +209,12 @@ MojoResult DataPipeConsumerDispatcher::BeginReadData(const void** buffer, (flags & MOJO_READ_DATA_FLAG_PEEK)) return MOJO_RESULT_INVALID_ARGUMENT; + const bool had_new_data = new_data_available_; + new_data_available_ = false; + if (bytes_available_ == 0) { + if (had_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); return peer_closed_ ? MOJO_RESULT_FAILED_PRECONDITION : MOJO_RESULT_SHOULD_WAIT; } @@ -237,6 +232,9 @@ MojoResult DataPipeConsumerDispatcher::BeginReadData(const void** buffer, *buffer_num_bytes = bytes_to_read; two_phase_max_bytes_read_ = bytes_to_read; + if (had_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + return MOJO_RESULT_OK; } @@ -250,7 +248,6 @@ MojoResult DataPipeConsumerDispatcher::EndReadData(uint32_t num_bytes_read) { CHECK(shared_ring_buffer_); - HandleSignalsState old_state = GetHandleSignalsStateNoLock(); MojoResult rv; if (num_bytes_read > two_phase_max_bytes_read_ || num_bytes_read % options_.element_num_bytes != 0) { @@ -270,9 +267,7 @@ MojoResult DataPipeConsumerDispatcher::EndReadData(uint32_t num_bytes_read) { in_two_phase_read_ = false; two_phase_max_bytes_read_ = 0; - HandleSignalsState new_state = GetHandleSignalsStateNoLock(); - if (!new_state.equals(old_state)) - awakable_list_.AwakeForStateChange(new_state); + watchers_.NotifyState(GetHandleSignalsStateNoLock()); return rv; } @@ -282,43 +277,22 @@ HandleSignalsState DataPipeConsumerDispatcher::GetHandleSignalsState() const { return GetHandleSignalsStateNoLock(); } -MojoResult DataPipeConsumerDispatcher::AddAwakable( - Awakable* awakable, - MojoHandleSignals signals, - uintptr_t context, - HandleSignalsState* signals_state) { +MojoResult DataPipeConsumerDispatcher::AddWatcherRef( + const scoped_refptr<WatcherDispatcher>& watcher, + uintptr_t context) { base::AutoLock lock(lock_); - if (!shared_ring_buffer_ || in_transit_) { - if (signals_state) - *signals_state = HandleSignalsState(); + if (is_closed_ || in_transit_) return MOJO_RESULT_INVALID_ARGUMENT; - } - UpdateSignalsStateNoLock(); - HandleSignalsState state = GetHandleSignalsStateNoLock(); - if (state.satisfies(signals)) { - if (signals_state) - *signals_state = state; - return MOJO_RESULT_ALREADY_EXISTS; - } - if (!state.can_satisfy(signals)) { - if (signals_state) - *signals_state = state; - return MOJO_RESULT_FAILED_PRECONDITION; - } - - awakable_list_.Add(awakable, signals, context); - return MOJO_RESULT_OK; + return watchers_.Add(watcher, context, GetHandleSignalsStateNoLock()); } -void DataPipeConsumerDispatcher::RemoveAwakable( - Awakable* awakable, - HandleSignalsState* signals_state) { +MojoResult DataPipeConsumerDispatcher::RemoveWatcherRef( + WatcherDispatcher* watcher, + uintptr_t context) { base::AutoLock lock(lock_); - if ((!shared_ring_buffer_ || in_transit_) && signals_state) - *signals_state = HandleSignalsState(); - else if (signals_state) - *signals_state = GetHandleSignalsStateNoLock(); - awakable_list_.Remove(awakable); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + return watchers_.Remove(watcher, context); } void DataPipeConsumerDispatcher::StartSerialize(uint32_t* num_bytes, @@ -463,7 +437,7 @@ MojoResult DataPipeConsumerDispatcher::CloseNoLock() { ring_buffer_mapping_.reset(); shared_ring_buffer_ = nullptr; - awakable_list_.CancelAll(); + watchers_.NotifyClosed(); if (!transferred_) { base::AutoUnlock unlock(lock_); node_controller_->ClosePort(control_port_); @@ -580,9 +554,8 @@ void DataPipeConsumerDispatcher::UpdateSignalsStateNoLock() { if (has_new_data) new_data_available_ = true; - if (peer_closed_ != was_peer_closed || has_new_data) { - awakable_list_.AwakeForStateChange(GetHandleSignalsStateNoLock()); - } + if (peer_closed_ != was_peer_closed || has_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); } } // namespace edk diff --git a/mojo/edk/system/data_pipe_consumer_dispatcher.h b/mojo/edk/system/data_pipe_consumer_dispatcher.h index b323c16..120c7a3 100644 --- a/mojo/edk/system/data_pipe_consumer_dispatcher.h +++ b/mojo/edk/system/data_pipe_consumer_dispatcher.h @@ -16,10 +16,10 @@ #include "mojo/edk/embedder/platform_handle_vector.h" #include "mojo/edk/embedder/platform_shared_buffer.h" #include "mojo/edk/embedder/scoped_platform_handle.h" -#include "mojo/edk/system/awakable_list.h" #include "mojo/edk/system/dispatcher.h" #include "mojo/edk/system/ports/port_ref.h" #include "mojo/edk/system/system_impl_export.h" +#include "mojo/edk/system/watcher_set.h" namespace mojo { namespace edk { @@ -43,10 +43,6 @@ class MOJO_SYSTEM_IMPL_EXPORT DataPipeConsumerDispatcher final // Dispatcher: Type GetType() const override; MojoResult Close() override; - MojoResult Watch(MojoHandleSignals signals, - const Watcher::WatchCallback& callback, - uintptr_t context) override; - MojoResult CancelWatch(uintptr_t context) override; MojoResult ReadData(void* elements, uint32_t* num_bytes, MojoReadDataFlags flags) override; @@ -55,12 +51,10 @@ class MOJO_SYSTEM_IMPL_EXPORT DataPipeConsumerDispatcher final MojoReadDataFlags flags) override; MojoResult EndReadData(uint32_t num_bytes_read) override; HandleSignalsState GetHandleSignalsState() const override; - MojoResult AddAwakable(Awakable* awakable, - MojoHandleSignals signals, - uintptr_t context, - HandleSignalsState* signals_state) override; - void RemoveAwakable(Awakable* awakable, - HandleSignalsState* signals_state) override; + MojoResult AddWatcherRef(const scoped_refptr<WatcherDispatcher>& watcher, + uintptr_t context) override; + MojoResult RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context) override; void StartSerialize(uint32_t* num_bytes, uint32_t* num_ports, uint32_t* num_handles) override; @@ -100,7 +94,7 @@ class MOJO_SYSTEM_IMPL_EXPORT DataPipeConsumerDispatcher final // Guards access to the fields below. mutable base::Lock lock_; - AwakableList awakable_list_; + WatcherSet watchers_; scoped_refptr<PlatformSharedBuffer> shared_ring_buffer_; std::unique_ptr<PlatformSharedBufferMapping> ring_buffer_mapping_; diff --git a/mojo/edk/system/data_pipe_producer_dispatcher.cc b/mojo/edk/system/data_pipe_producer_dispatcher.cc index 8c1993a..b0102a6 100644 --- a/mojo/edk/system/data_pipe_producer_dispatcher.cc +++ b/mojo/edk/system/data_pipe_producer_dispatcher.cc @@ -79,6 +79,7 @@ DataPipeProducerDispatcher::DataPipeProducerDispatcher( node_controller_(node_controller), control_port_(control_port), pipe_id_(pipe_id), + watchers_(this), shared_ring_buffer_(shared_ring_buffer), available_capacity_(options_.capacity_num_bytes) { if (initialized) { @@ -97,28 +98,6 @@ MojoResult DataPipeProducerDispatcher::Close() { return CloseNoLock(); } -MojoResult DataPipeProducerDispatcher::Watch( - MojoHandleSignals signals, - const Watcher::WatchCallback& callback, - uintptr_t context) { - base::AutoLock lock(lock_); - - if (is_closed_ || in_transit_) - return MOJO_RESULT_INVALID_ARGUMENT; - - return awakable_list_.AddWatcher( - signals, callback, context, GetHandleSignalsStateNoLock()); -} - -MojoResult DataPipeProducerDispatcher::CancelWatch(uintptr_t context) { - base::AutoLock lock(lock_); - - if (is_closed_ || in_transit_) - return MOJO_RESULT_INVALID_ARGUMENT; - - return awakable_list_.RemoveWatcher(context); -} - MojoResult DataPipeProducerDispatcher::WriteData(const void* elements, uint32_t* num_bytes, MojoWriteDataFlags flags) { @@ -149,8 +128,6 @@ MojoResult DataPipeProducerDispatcher::WriteData(const void* elements, if (num_bytes_to_write == 0) return MOJO_RESULT_SHOULD_WAIT; - HandleSignalsState old_state = GetHandleSignalsStateNoLock(); - *num_bytes = num_bytes_to_write; CHECK(ring_buffer_mapping_); @@ -176,9 +153,7 @@ MojoResult DataPipeProducerDispatcher::WriteData(const void* elements, write_offset_ = (write_offset_ + num_bytes_to_write) % options_.capacity_num_bytes; - HandleSignalsState new_state = GetHandleSignalsStateNoLock(); - if (!new_state.equals(old_state)) - awakable_list_.AwakeForStateChange(new_state); + watchers_.NotifyState(GetHandleSignalsStateNoLock()); base::AutoUnlock unlock(lock_); NotifyWrite(num_bytes_to_write); @@ -193,6 +168,11 @@ MojoResult DataPipeProducerDispatcher::BeginWriteData( base::AutoLock lock(lock_); if (!shared_ring_buffer_ || in_transit_) return MOJO_RESULT_INVALID_ARGUMENT; + + // These flags may not be used in two-phase mode. + if (flags & MOJO_WRITE_DATA_FLAG_ALL_OR_NONE) + return MOJO_RESULT_INVALID_ARGUMENT; + if (in_two_phase_write_) return MOJO_RESULT_BUSY; if (peer_closed_) @@ -247,10 +227,8 @@ MojoResult DataPipeProducerDispatcher::EndWriteData( in_two_phase_write_ = false; // If we're now writable, we *became* writable (since we weren't writable - // during the two-phase write), so awake producer awakables. - HandleSignalsState new_state = GetHandleSignalsStateNoLock(); - if (new_state.satisfies(MOJO_HANDLE_SIGNAL_WRITABLE)) - awakable_list_.AwakeForStateChange(new_state); + // during the two-phase write), so notify watchers. + watchers_.NotifyState(GetHandleSignalsStateNoLock()); return rv; } @@ -260,43 +238,22 @@ HandleSignalsState DataPipeProducerDispatcher::GetHandleSignalsState() const { return GetHandleSignalsStateNoLock(); } -MojoResult DataPipeProducerDispatcher::AddAwakable( - Awakable* awakable, - MojoHandleSignals signals, - uintptr_t context, - HandleSignalsState* signals_state) { +MojoResult DataPipeProducerDispatcher::AddWatcherRef( + const scoped_refptr<WatcherDispatcher>& watcher, + uintptr_t context) { base::AutoLock lock(lock_); - if (!shared_ring_buffer_ || in_transit_) { - if (signals_state) - *signals_state = HandleSignalsState(); + if (is_closed_ || in_transit_) return MOJO_RESULT_INVALID_ARGUMENT; - } - UpdateSignalsStateNoLock(); - HandleSignalsState state = GetHandleSignalsStateNoLock(); - if (state.satisfies(signals)) { - if (signals_state) - *signals_state = state; - return MOJO_RESULT_ALREADY_EXISTS; - } - if (!state.can_satisfy(signals)) { - if (signals_state) - *signals_state = state; - return MOJO_RESULT_FAILED_PRECONDITION; - } - - awakable_list_.Add(awakable, signals, context); - return MOJO_RESULT_OK; + return watchers_.Add(watcher, context, GetHandleSignalsStateNoLock()); } -void DataPipeProducerDispatcher::RemoveAwakable( - Awakable* awakable, - HandleSignalsState* signals_state) { +MojoResult DataPipeProducerDispatcher::RemoveWatcherRef( + WatcherDispatcher* watcher, + uintptr_t context) { base::AutoLock lock(lock_); - if ((!shared_ring_buffer_ || in_transit_) && signals_state) - *signals_state = HandleSignalsState(); - else if (signals_state) - *signals_state = GetHandleSignalsStateNoLock(); - awakable_list_.Remove(awakable); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + return watchers_.Remove(watcher, context); } void DataPipeProducerDispatcher::StartSerialize(uint32_t* num_bytes, @@ -356,7 +313,9 @@ void DataPipeProducerDispatcher::CancelTransit() { DCHECK(in_transit_); in_transit_ = false; buffer_handle_for_transit_.reset(); - awakable_list_.AwakeForStateChange(GetHandleSignalsStateNoLock()); + + HandleSignalsState state = GetHandleSignalsStateNoLock(); + watchers_.NotifyState(state); } // static @@ -439,7 +398,7 @@ MojoResult DataPipeProducerDispatcher::CloseNoLock() { ring_buffer_mapping_.reset(); shared_ring_buffer_ = nullptr; - awakable_list_.CancelAll(); + watchers_.NotifyClosed(); if (!transferred_) { base::AutoUnlock unlock(lock_); node_controller_->ClosePort(control_port_); @@ -453,8 +412,7 @@ HandleSignalsState DataPipeProducerDispatcher::GetHandleSignalsStateNoLock() lock_.AssertAcquired(); HandleSignalsState rv; if (!peer_closed_) { - if (!in_two_phase_write_ && shared_ring_buffer_ && - available_capacity_ > 0) + if (!in_two_phase_write_ && shared_ring_buffer_ && available_capacity_ > 0) rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_WRITABLE; rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_WRITABLE; } else { @@ -541,7 +499,7 @@ void DataPipeProducerDispatcher::UpdateSignalsStateNoLock() { if (peer_closed_ != was_peer_closed || available_capacity_ != previous_capacity) { - awakable_list_.AwakeForStateChange(GetHandleSignalsStateNoLock()); + watchers_.NotifyState(GetHandleSignalsStateNoLock()); } } diff --git a/mojo/edk/system/data_pipe_producer_dispatcher.h b/mojo/edk/system/data_pipe_producer_dispatcher.h index a55234a..1eddd5d 100644 --- a/mojo/edk/system/data_pipe_producer_dispatcher.h +++ b/mojo/edk/system/data_pipe_producer_dispatcher.h @@ -15,10 +15,10 @@ #include "base/synchronization/lock.h" #include "mojo/edk/embedder/platform_handle_vector.h" #include "mojo/edk/embedder/platform_shared_buffer.h" -#include "mojo/edk/system/awakable_list.h" #include "mojo/edk/system/dispatcher.h" #include "mojo/edk/system/ports/port_ref.h" #include "mojo/edk/system/system_impl_export.h" +#include "mojo/edk/system/watcher_set.h" namespace mojo { namespace edk { @@ -43,10 +43,6 @@ class MOJO_SYSTEM_IMPL_EXPORT DataPipeProducerDispatcher final // Dispatcher: Type GetType() const override; MojoResult Close() override; - MojoResult Watch(MojoHandleSignals signals, - const Watcher::WatchCallback& callback, - uintptr_t context) override; - MojoResult CancelWatch(uintptr_t context) override; MojoResult WriteData(const void* elements, uint32_t* num_bytes, MojoReadDataFlags flags) override; @@ -55,12 +51,10 @@ class MOJO_SYSTEM_IMPL_EXPORT DataPipeProducerDispatcher final MojoWriteDataFlags flags) override; MojoResult EndWriteData(uint32_t num_bytes_written) override; HandleSignalsState GetHandleSignalsState() const override; - MojoResult AddAwakable(Awakable* awakable, - MojoHandleSignals signals, - uintptr_t context, - HandleSignalsState* signals_state) override; - void RemoveAwakable(Awakable* awakable, - HandleSignalsState* signals_state) override; + MojoResult AddWatcherRef(const scoped_refptr<WatcherDispatcher>& watcher, + uintptr_t context) override; + MojoResult RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context) override; void StartSerialize(uint32_t* num_bytes, uint32_t* num_ports, uint32_t* num_handles) override; @@ -103,7 +97,7 @@ class MOJO_SYSTEM_IMPL_EXPORT DataPipeProducerDispatcher final // Guards access to the fields below. mutable base::Lock lock_; - AwakableList awakable_list_; + WatcherSet watchers_; bool buffer_requested_ = false; diff --git a/mojo/edk/system/data_pipe_unittest.cc b/mojo/edk/system/data_pipe_unittest.cc index 610aeac..79c1f75 100644 --- a/mojo/edk/system/data_pipe_unittest.cc +++ b/mojo/edk/system/data_pipe_unittest.cc @@ -16,12 +16,11 @@ #include "mojo/edk/embedder/embedder.h" #include "mojo/edk/embedder/platform_channel_pair.h" #include "mojo/edk/system/test_utils.h" -#include "mojo/edk/system/waiter.h" #include "mojo/edk/test/mojo_test_base.h" #include "mojo/public/c/system/data_pipe.h" #include "mojo/public/c/system/functions.h" #include "mojo/public/c/system/message_pipe.h" -#include "mojo/public/cpp/system/watcher.h" +#include "mojo/public/cpp/system/simple_watcher.h" #include "testing/gtest/include/gtest/gtest.h" namespace mojo { @@ -162,8 +161,7 @@ TEST_F(DataPipeTest, Basic) { // Now wait for the other side to become readable. MojoHandleSignalsState state; ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &state)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, state.satisfied_signals); @@ -249,8 +247,7 @@ TEST_F(DataPipeTest, SimpleReadWrite) { // Wait. ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | @@ -337,19 +334,12 @@ TEST_F(DataPipeTest, BasicProducerWaiting) { Create(&options); MojoHandleSignalsState hss; - // Never readable. - hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - MojoWait(producer_, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); + // Never readable. Already writable. + hss = GetSignalsState(producer_); ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); - // Already writable. - hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss)); - // Write two elements. int32_t elements[2] = {123, 456}; uint32_t num_bytes = static_cast<uint32_t>(2u * sizeof(elements[0])); @@ -358,8 +348,7 @@ TEST_F(DataPipeTest, BasicProducerWaiting) { // Wait for data to become available to the consumer. ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | @@ -419,11 +408,7 @@ TEST_F(DataPipeTest, BasicProducerWaiting) { // It should now be never-writable. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, &hss)); - hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss)); + WaitForSignals(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); } @@ -444,8 +429,7 @@ TEST_F(DataPipeTest, PeerClosedProducerWaiting) { // It should be signaled. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); } @@ -466,8 +450,7 @@ TEST_F(DataPipeTest, PeerClosedConsumerWaiting) { // It should be signaled. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); } @@ -485,8 +468,7 @@ TEST_F(DataPipeTest, BasicConsumerWaiting) { // Never writable. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_WRITABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_WRITABLE, &hss)); EXPECT_EQ(0u, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, @@ -500,8 +482,7 @@ TEST_F(DataPipeTest, BasicConsumerWaiting) { // Wait for readability. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | @@ -516,8 +497,7 @@ TEST_F(DataPipeTest, BasicConsumerWaiting) { // Should still be readable. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, @@ -534,8 +514,7 @@ TEST_F(DataPipeTest, BasicConsumerWaiting) { // Should still be readable. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, @@ -559,8 +538,7 @@ TEST_F(DataPipeTest, BasicConsumerWaiting) { // Waiting should now succeed. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | @@ -573,8 +551,7 @@ TEST_F(DataPipeTest, BasicConsumerWaiting) { // Should still be readable. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_TRUE(hss.satisfied_signals & (MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | @@ -584,8 +561,7 @@ TEST_F(DataPipeTest, BasicConsumerWaiting) { // Wait for the peer closed signal. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); @@ -605,8 +581,7 @@ TEST_F(DataPipeTest, BasicConsumerWaiting) { // Should be never-readable. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); } @@ -625,11 +600,10 @@ TEST_F(DataPipeTest, ConsumerNewDataReadable) { EXPECT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true)); // The consumer handle should appear to be readable and have new data. - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, nullptr)); EXPECT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, - MOJO_DEADLINE_INDEFINITE, nullptr)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE)); + EXPECT_TRUE(GetSignalsState(consumer_).satisfied_signals & + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE); // Now try to read a minimum of 6 elements. int32_t read_elements[6]; @@ -638,35 +612,30 @@ TEST_F(DataPipeTest, ConsumerNewDataReadable) { MojoReadData(consumer_, read_elements, &num_read_bytes, MOJO_READ_DATA_FLAG_ALL_OR_NONE)); - // The consumer should still appear to be readable, but not with new data. - EXPECT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, 0, nullptr)); - EXPECT_EQ( - MOJO_RESULT_DEADLINE_EXCEEDED, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, 0, nullptr)); + // The consumer should still appear to be readable but not with new data. + EXPECT_TRUE(GetSignalsState(consumer_).satisfied_signals & + MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_FALSE(GetSignalsState(consumer_).satisfied_signals & + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE); // Write four more elements. EXPECT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true)); EXPECT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true)); - // The consumer handle should once again appear to be readable with new data. - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, nullptr)); + // The consumer handle should once again appear to be readable. EXPECT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, - MOJO_DEADLINE_INDEFINITE, nullptr)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE)); - // Read should succeed this time. + // Try again to read a minimum of 6 elements. Should succeed this time. EXPECT_EQ(MOJO_RESULT_OK, MojoReadData(consumer_, read_elements, &num_read_bytes, MOJO_READ_DATA_FLAG_ALL_OR_NONE)); - // And once again the consumer is unreadable. - EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, 0, nullptr)); - EXPECT_EQ( - MOJO_RESULT_DEADLINE_EXCEEDED, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, 0, nullptr)); + // And now the consumer is unreadable. + EXPECT_FALSE(GetSignalsState(consumer_).satisfied_signals & + MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_FALSE(GetSignalsState(consumer_).satisfied_signals & + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE); } // Test with two-phase APIs and also closing the producer with an active @@ -697,8 +666,7 @@ TEST_F(DataPipeTest, ConsumerWaitingTwoPhase) { // Wait for readability. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | @@ -719,8 +687,7 @@ TEST_F(DataPipeTest, ConsumerWaitingTwoPhase) { // Should still be readable. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, @@ -743,8 +710,7 @@ TEST_F(DataPipeTest, ConsumerWaitingTwoPhase) { // Should be never-readable. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); } @@ -761,9 +727,7 @@ TEST_F(DataPipeTest, BasicTwoPhaseWaiting) { MojoHandleSignalsState hss; // It should be writable. - hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss)); + hss = GetSignalsState(producer_); ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); @@ -775,17 +739,13 @@ TEST_F(DataPipeTest, BasicTwoPhaseWaiting) { EXPECT_GE(num_bytes, static_cast<uint32_t>(1u * sizeof(int32_t))); // At this point, it shouldn't be writable. - hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, - MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss)); + hss = GetSignalsState(producer_); ASSERT_EQ(0u, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); // It shouldn't be readable yet either (we'll wait later). - hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); + hss = GetSignalsState(consumer_); ASSERT_EQ(0u, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, @@ -795,9 +755,7 @@ TEST_F(DataPipeTest, BasicTwoPhaseWaiting) { ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(1u * sizeof(int32_t))); // It should immediately be writable again. - hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss)); + hss = GetSignalsState(producer_); ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); @@ -805,8 +763,7 @@ TEST_F(DataPipeTest, BasicTwoPhaseWaiting) { // It should become readable. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | @@ -824,8 +781,7 @@ TEST_F(DataPipeTest, BasicTwoPhaseWaiting) { // It should be readable. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | @@ -843,17 +799,13 @@ TEST_F(DataPipeTest, BasicTwoPhaseWaiting) { ASSERT_EQ(static_cast<uint32_t>(1u * sizeof(int32_t)), num_bytes); // At this point, it should still be writable. - hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss)); + hss = GetSignalsState(producer_); ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); // But not readable. - hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); + hss = GetSignalsState(consumer_); ASSERT_EQ(0u, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, @@ -863,9 +815,7 @@ TEST_F(DataPipeTest, BasicTwoPhaseWaiting) { ASSERT_EQ(MOJO_RESULT_OK, EndReadData(0u)); // It should be readable again. - hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); + hss = GetSignalsState(consumer_); ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, @@ -911,8 +861,7 @@ TEST_F(DataPipeTest, AllOrNone) { // of data to become available. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE | @@ -1002,8 +951,7 @@ TEST_F(DataPipeTest, AllOrNone) { // Wait. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, @@ -1067,8 +1015,7 @@ TEST_F(DataPipeTest, WrapAround) { // Wait for data. ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_TRUE(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, @@ -1095,8 +1042,7 @@ TEST_F(DataPipeTest, WrapAround) { while (total_num_bytes < 90) { // Wait to write. ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, &hss)); ASSERT_EQ(hss.satisfied_signals, MOJO_HANDLE_SIGNAL_WRITABLE); ASSERT_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED); @@ -1231,8 +1177,7 @@ TEST_F(DataPipeTest, TwoPhaseWriteReadCloseConsumer) { // TODO(vtl): (See corresponding TODO in AllOrNone.) hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | @@ -1252,8 +1197,7 @@ TEST_F(DataPipeTest, TwoPhaseWriteReadCloseConsumer) { // Wait for producer to know that the consumer is closed. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); @@ -1323,8 +1267,7 @@ TEST_F(DataPipeTest, WriteCloseProducerReadNoData) { // must also know about all the data that was sent.) hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); @@ -1384,8 +1327,7 @@ TEST_F(DataPipeTest, TwoPhaseReadMemoryStable) { // Wait for the data. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | @@ -1411,8 +1353,7 @@ TEST_F(DataPipeTest, TwoPhaseReadMemoryStable) { // must also have received the extra data). hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, @@ -1499,8 +1440,7 @@ TEST_F(DataPipeTest, TwoPhaseMoreInvalidArguments) { // TODO(vtl): (See corresponding TODO in AllOrNone.) hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | @@ -1569,8 +1509,7 @@ TEST_F(DataPipeTest, SendProducer) { // Wait for the data. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | @@ -1594,8 +1533,8 @@ TEST_F(DataPipeTest, SendProducer) { MojoWriteMessage(pipe0, nullptr, 0, &producer_, 1, MOJO_WRITE_MESSAGE_FLAG_NONE)); producer_ = MOJO_HANDLE_INVALID; - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1, MOJO_HANDLE_SIGNAL_READABLE, &hss)); uint32_t num_handles = 1; ASSERT_EQ(MOJO_RESULT_OK, MojoReadMessage(pipe1, nullptr, 0, &producer_, &num_handles, @@ -1612,8 +1551,7 @@ TEST_F(DataPipeTest, SendProducer) { // Wait for it. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | @@ -1653,8 +1591,7 @@ TEST_F(DataPipeTest, ConsumerWithClosedProducerSent) { // Now wait for the other side to become readable and to see the peer closed. MojoHandleSignalsState state; ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, &state)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, state.satisfied_signals); @@ -1671,8 +1608,8 @@ TEST_F(DataPipeTest, ConsumerWithClosedProducerSent) { MojoWriteMessage(pipe0, nullptr, 0, &consumer_, 1, MOJO_WRITE_MESSAGE_FLAG_NONE)); consumer_ = MOJO_HANDLE_INVALID; - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1, MOJO_HANDLE_SIGNAL_READABLE, &state)); uint32_t num_handles = 1; ASSERT_EQ(MOJO_RESULT_OK, MojoReadMessage(pipe1, nullptr, 0, &consumer_, &num_handles, @@ -1680,8 +1617,7 @@ TEST_F(DataPipeTest, ConsumerWithClosedProducerSent) { ASSERT_EQ(num_handles, 1u); ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, &state)); + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, state.satisfied_signals); @@ -1716,8 +1652,8 @@ bool WriteAllData(MojoHandle producer, } MojoHandleSignalsState hss = MojoHandleSignalsState(); - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(producer, MOJO_HANDLE_SIGNAL_WRITABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + EXPECT_EQ(MOJO_RESULT_OK, test::MojoTestBase::WaitForSignals( + producer, MOJO_HANDLE_SIGNAL_WRITABLE, &hss)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); @@ -1754,8 +1690,8 @@ bool ReadAllData(MojoHandle consumer, } MojoHandleSignalsState hss = MojoHandleSignalsState(); - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(consumer, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + EXPECT_EQ(MOJO_RESULT_OK, test::MojoTestBase::WaitForSignals( + consumer, MOJO_HANDLE_SIGNAL_READABLE, &hss)); // Peer could have become closed while we're still waiting for data. EXPECT_TRUE(MOJO_HANDLE_SIGNAL_READABLE & hss.satisfied_signals); EXPECT_TRUE(hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE); @@ -1814,8 +1750,8 @@ TEST_F(DataPipeTest, Multiprocess) { // Receive the consumer from the other side. producer_ = MOJO_HANDLE_INVALID; MojoHandleSignalsState hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(server_mp, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(server_mp, MOJO_HANDLE_SIGNAL_READABLE, &hss)); MojoHandle handles[2]; uint32_t num_handles = arraysize(handles); ASSERT_EQ(MOJO_RESULT_OK, @@ -1844,8 +1780,8 @@ DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MultiprocessClient, DataPipeTest, client_mp) { // Receive the data pipe from the other side. MojoHandle consumer = MOJO_HANDLE_INVALID; MojoHandleSignalsState hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(client_mp, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(client_mp, MOJO_HANDLE_SIGNAL_READABLE, &hss)); MojoHandle handles[2]; uint32_t num_handles = arraysize(handles); ASSERT_EQ(MOJO_RESULT_OK, @@ -1880,8 +1816,8 @@ DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MultiprocessClient, DataPipeTest, client_mp) { // Receive the producer from the other side. MojoHandle producer = MOJO_HANDLE_INVALID; hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(client_mp, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(client_mp, MOJO_HANDLE_SIGNAL_READABLE, &hss)); num_handles = arraysize(handles); ASSERT_EQ(MOJO_RESULT_OK, MojoReadMessage(client_mp, nullptr, 0, handles, &num_handles, @@ -1921,8 +1857,7 @@ DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReadAndCloseConsumer, DataPipeTest, h) { std::string expected_message = ReadMessageWithHandles(h, &c, 1); // Wait for the consumer to become readable. - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(c, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(c, MOJO_HANDLE_SIGNAL_READABLE)); // Drain the consumer and expect to find the given message. uint32_t num_bytes = static_cast<uint32_t>(expected_message.size()); @@ -1989,8 +1924,7 @@ TEST_F(DataPipeTest, CreateInChild) { std::string expected_message = ReadMessageWithHandles(child, &c, 1); // Wait for the consumer to become readable. - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(c, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(c, MOJO_HANDLE_SIGNAL_READABLE)); // Drain the consumer and expect to find the given message. uint32_t num_bytes = static_cast<uint32_t>(expected_message.size()); @@ -2017,19 +1951,17 @@ DEFINE_TEST_CLIENT_TEST_WITH_PIPE(DataPipeStatusChangeInTransitClient, MojoHandle* producers = &handles[0]; MojoHandle* consumers = &handles[3]; - // Wait on producer 0 using MojoWait. + // Wait on producer 0 EXPECT_EQ(MOJO_RESULT_OK, - MojoWait(producers[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, nullptr)); + WaitForSignals(producers[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED)); - // Wait on consumer 0 using MojoWait. + // Wait on consumer 0 EXPECT_EQ(MOJO_RESULT_OK, - MojoWait(consumers[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, nullptr)); + WaitForSignals(consumers[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED)); base::MessageLoop message_loop; - // Wait on producer 1 and consumer 1 using Watchers. + // Wait on producer 1 and consumer 1 using SimpleWatchers. { base::RunLoop run_loop; int count = 0; @@ -2040,12 +1972,16 @@ DEFINE_TEST_CLIENT_TEST_WITH_PIPE(DataPipeStatusChangeInTransitClient, loop->Quit(); }, &run_loop, &count); - Watcher producer_watcher(FROM_HERE), consumer_watcher(FROM_HERE); - producer_watcher.Start( - Handle(producers[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, callback); - consumer_watcher.Start( - Handle(consumers[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, callback); + SimpleWatcher producer_watcher(FROM_HERE, + SimpleWatcher::ArmingPolicy::AUTOMATIC); + SimpleWatcher consumer_watcher(FROM_HERE, + SimpleWatcher::ArmingPolicy::AUTOMATIC); + producer_watcher.Watch(Handle(producers[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, + callback); + consumer_watcher.Watch(Handle(consumers[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, + callback); run_loop.Run(); + EXPECT_EQ(2, count); } // Wait on producer 2 by polling with MojoWriteData. diff --git a/mojo/edk/system/dispatcher.cc b/mojo/edk/system/dispatcher.cc index 7d701b2..7cdbe91 100644 --- a/mojo/edk/system/dispatcher.cc +++ b/mojo/edk/system/dispatcher.cc @@ -22,9 +22,9 @@ Dispatcher::DispatcherInTransit::DispatcherInTransit( Dispatcher::DispatcherInTransit::~DispatcherInTransit() {} -MojoResult Dispatcher::Watch(MojoHandleSignals signals, - const Watcher::WatchCallback& callback, - uintptr_t context) { +MojoResult Dispatcher::WatchDispatcher(scoped_refptr<Dispatcher> dispatcher, + MojoHandleSignals signals, + uintptr_t context) { return MOJO_RESULT_INVALID_ARGUMENT; } @@ -32,6 +32,13 @@ MojoResult Dispatcher::CancelWatch(uintptr_t context) { return MOJO_RESULT_INVALID_ARGUMENT; } +MojoResult Dispatcher::Arm(uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + MojoResult Dispatcher::WriteMessage(std::unique_ptr<MessageForTransit> message, MojoWriteMessageFlags flags) { return MOJO_RESULT_INVALID_ARGUMENT; @@ -115,16 +122,15 @@ HandleSignalsState Dispatcher::GetHandleSignalsState() const { return HandleSignalsState(); } -MojoResult Dispatcher::AddAwakable(Awakable* awakable, - MojoHandleSignals signals, - uintptr_t context, - HandleSignalsState* signals_state) { +MojoResult Dispatcher::AddWatcherRef( + const scoped_refptr<WatcherDispatcher>& watcher, + uintptr_t context) { return MOJO_RESULT_INVALID_ARGUMENT; } -void Dispatcher::RemoveAwakable(Awakable* awakable, - HandleSignalsState* handle_signals_state) { - NOTREACHED(); +MojoResult Dispatcher::RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context) { + return MOJO_RESULT_INVALID_ARGUMENT; } void Dispatcher::StartSerialize(uint32_t* num_bytes, diff --git a/mojo/edk/system/dispatcher.h b/mojo/edk/system/dispatcher.h index 9dca67f..db1f1f1 100644 --- a/mojo/edk/system/dispatcher.h +++ b/mojo/edk/system/dispatcher.h @@ -20,7 +20,7 @@ #include "mojo/edk/system/handle_signals_state.h" #include "mojo/edk/system/ports/name.h" #include "mojo/edk/system/system_impl_export.h" -#include "mojo/edk/system/watcher.h" +#include "mojo/edk/system/watch.h" #include "mojo/public/c/system/buffer.h" #include "mojo/public/c/system/data_pipe.h" #include "mojo/public/c/system/message_pipe.h" @@ -29,15 +29,13 @@ namespace mojo { namespace edk { -class Awakable; class Dispatcher; class MessageForTransit; using DispatcherVector = std::vector<scoped_refptr<Dispatcher>>; // A |Dispatcher| implements Mojo EDK calls that are associated with a -// particular MojoHandle, with the exception of MojoWait and MojoWaitMany ( -// which are implemented directly in Core.). +// particular MojoHandle. class MOJO_SYSTEM_IMPL_EXPORT Dispatcher : public base::RefCountedThreadSafe<Dispatcher> { public: @@ -56,7 +54,7 @@ class MOJO_SYSTEM_IMPL_EXPORT Dispatcher DATA_PIPE_PRODUCER, DATA_PIPE_CONSUMER, SHARED_BUFFER, - WAIT_SET, + WATCHER, // "Private" types (not exposed via the public interface): PLATFORM_HANDLE = -1, @@ -67,13 +65,16 @@ class MOJO_SYSTEM_IMPL_EXPORT Dispatcher virtual Type GetType() const = 0; virtual MojoResult Close() = 0; - ///////////// Watch API //////////////////// - - virtual MojoResult Watch(MojoHandleSignals signals, - const Watcher::WatchCallback& callback, - uintptr_t context); + ///////////// Watcher API //////////////////// + virtual MojoResult WatchDispatcher(scoped_refptr<Dispatcher> dispatcher, + MojoHandleSignals signals, + uintptr_t context); virtual MojoResult CancelWatch(uintptr_t context); + virtual MojoResult Arm(uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states); ///////////// Message pipe API ///////////// @@ -158,31 +159,17 @@ class MOJO_SYSTEM_IMPL_EXPORT Dispatcher // threads. virtual HandleSignalsState GetHandleSignalsState() const; - // Adds an awakable to this dispatcher, which will be woken up when this - // object changes state to satisfy |signals| with context |context|. It will - // also be woken up when it becomes impossible for the object to ever satisfy - // |signals| with a suitable error status. - // - // If |signals_state| is non-null, on *failure* |*signals_state| will be set - // to the current handle signals state (on success, it is left untouched). - // - // Returns: - // - |MOJO_RESULT_OK| if the awakable was added; - // - |MOJO_RESULT_ALREADY_EXISTS| if |signals| is already satisfied; - // - |MOJO_RESULT_INVALID_ARGUMENT| if the dispatcher has been closed; and - // - |MOJO_RESULT_FAILED_PRECONDITION| if it is not (or no longer) possible - // that |signals| will ever be satisfied. - virtual MojoResult AddAwakable(Awakable* awakable, - MojoHandleSignals signals, - uintptr_t context, - HandleSignalsState* signals_state); - - // Removes an awakable from this dispatcher. (It is valid to call this - // multiple times for the same |awakable| on the same object, so long as - // |AddAwakable()| was called at most once.) If |signals_state| is non-null, - // |*signals_state| will be set to the current handle signals state. - virtual void RemoveAwakable(Awakable* awakable, - HandleSignalsState* signals_state); + // Adds a WatcherDispatcher reference to this dispatcher, to be notified of + // all subsequent changes to handle state including signal changes or closure. + // The reference is associated with a |context| for disambiguation of + // removals. + virtual MojoResult AddWatcherRef( + const scoped_refptr<WatcherDispatcher>& watcher, + uintptr_t context); + + // Removes a WatcherDispatcher reference from this dispatcher. + virtual MojoResult RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context); // Informs the caller of the total serialized size (in bytes) and the total // number of platform handles and ports needed to transfer this dispatcher diff --git a/mojo/edk/system/handle_signals_state.h b/mojo/edk/system/handle_signals_state.h index 1c47a28..f241278 100644 --- a/mojo/edk/system/handle_signals_state.h +++ b/mojo/edk/system/handle_signals_state.h @@ -5,45 +5,9 @@ #ifndef MOJO_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_ #define MOJO_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_ -#include "mojo/edk/system/system_impl_export.h" -#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/handle_signals_state.h" -namespace mojo { -namespace edk { - -// Just "add" some constructors and methods to the C struct -// |MojoHandleSignalsState| (for convenience). This should add no overhead. -struct MOJO_SYSTEM_IMPL_EXPORT HandleSignalsState final - : public MojoHandleSignalsState { - HandleSignalsState() { - satisfied_signals = MOJO_HANDLE_SIGNAL_NONE; - satisfiable_signals = MOJO_HANDLE_SIGNAL_NONE; - } - HandleSignalsState(MojoHandleSignals satisfied, - MojoHandleSignals satisfiable) { - satisfied_signals = satisfied; - satisfiable_signals = satisfiable; - } - - bool equals(const HandleSignalsState& other) const { - return satisfied_signals == other.satisfied_signals && - satisfiable_signals == other.satisfiable_signals; - } - - bool satisfies(MojoHandleSignals signals) const { - return !!(satisfied_signals & signals); - } - - bool can_satisfy(MojoHandleSignals signals) const { - return !!(satisfiable_signals & signals); - } - - // (Copy and assignment allowed.) -}; -static_assert(sizeof(HandleSignalsState) == sizeof(MojoHandleSignalsState), - "HandleSignalsState should add no overhead"); - -} // namespace edk -} // namespace mojo +// TODO(rockot): Remove this header and use the C++ system library type +// directly inside the EDK. #endif // MOJO_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_ diff --git a/mojo/edk/system/message_pipe_dispatcher.cc b/mojo/edk/system/message_pipe_dispatcher.cc index f27336b..1db56c0 100644 --- a/mojo/edk/system/message_pipe_dispatcher.cc +++ b/mojo/edk/system/message_pipe_dispatcher.cc @@ -164,7 +164,8 @@ MessagePipeDispatcher::MessagePipeDispatcher(NodeController* node_controller, : node_controller_(node_controller), port_(port), pipe_id_(pipe_id), - endpoint_(endpoint) { + endpoint_(endpoint), + watchers_(this) { DVLOG(2) << "Creating new MessagePipeDispatcher for port " << port.name() << " [pipe_id=" << pipe_id << "; endpoint=" << endpoint << "]"; @@ -182,7 +183,7 @@ bool MessagePipeDispatcher::Fuse(MessagePipeDispatcher* other) { base::AutoLock lock(signal_lock_); port0 = port_; port_closed_.Set(true); - awakables_.CancelAll(); + watchers_.NotifyClosed(); } ports::PortRef port1; @@ -190,7 +191,7 @@ bool MessagePipeDispatcher::Fuse(MessagePipeDispatcher* other) { base::AutoLock lock(other->signal_lock_); port1 = other->port_; other->port_closed_.Set(true); - other->awakables_.CancelAll(); + other->watchers_.NotifyClosed(); } // Both ports are always closed by this call. @@ -209,27 +210,6 @@ MojoResult MessagePipeDispatcher::Close() { return CloseNoLock(); } -MojoResult MessagePipeDispatcher::Watch(MojoHandleSignals signals, - const Watcher::WatchCallback& callback, - uintptr_t context) { - base::AutoLock lock(signal_lock_); - - if (port_closed_ || in_transit_) - return MOJO_RESULT_INVALID_ARGUMENT; - - return awakables_.AddWatcher( - signals, callback, context, GetHandleSignalsStateNoLock()); -} - -MojoResult MessagePipeDispatcher::CancelWatch(uintptr_t context) { - base::AutoLock lock(signal_lock_); - - if (port_closed_ || in_transit_) - return MOJO_RESULT_INVALID_ARGUMENT; - - return awakables_.RemoveWatcher(context); -} - MojoResult MessagePipeDispatcher::WriteMessage( std::unique_ptr<MessageForTransit> message, MojoWriteMessageFlags flags) { @@ -299,6 +279,12 @@ MojoResult MessagePipeDispatcher::ReadMessage( } if (no_space) { + if (may_discard) { + // May have been the last message on the pipe. Need to update signals just + // in case. + base::AutoLock lock(signal_lock_); + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + } // |*num_handles| (and/or |*num_bytes| if |read_any_size| is false) wasn't // sufficient to hold this message's data. The message will still be in // queue unless MOJO_READ_MESSAGE_FLAG_MAY_DISCARD was set. @@ -319,6 +305,13 @@ MojoResult MessagePipeDispatcher::ReadMessage( // Alright! We have a message and the caller has provided sufficient storage // in which to receive it. + { + // We need to update anyone watching our signals in case that was the last + // available message. + base::AutoLock lock(signal_lock_); + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + } + std::unique_ptr<PortsMessage> msg( static_cast<PortsMessage*>(ports_message.release())); @@ -396,63 +389,21 @@ MessagePipeDispatcher::GetHandleSignalsState() const { return GetHandleSignalsStateNoLock(); } -MojoResult MessagePipeDispatcher::AddAwakable( - Awakable* awakable, - MojoHandleSignals signals, - uintptr_t context, - HandleSignalsState* signals_state) { +MojoResult MessagePipeDispatcher::AddWatcherRef( + const scoped_refptr<WatcherDispatcher>& watcher, + uintptr_t context) { base::AutoLock lock(signal_lock_); - - if (port_closed_ || in_transit_) { - if (signals_state) - *signals_state = HandleSignalsState(); + if (port_closed_ || in_transit_) return MOJO_RESULT_INVALID_ARGUMENT; - } - - HandleSignalsState state = GetHandleSignalsStateNoLock(); - - DVLOG(2) << "Getting signal state for pipe " << pipe_id_ << " endpoint " - << endpoint_ << " [awakable=" << awakable << "; port=" - << port_.name() << "; signals=" << signals << "; satisfied=" - << state.satisfied_signals << "; satisfiable=" - << state.satisfiable_signals << "]"; - - if (state.satisfies(signals)) { - if (signals_state) - *signals_state = state; - DVLOG(2) << "Signals already set for " << port_.name(); - return MOJO_RESULT_ALREADY_EXISTS; - } - if (!state.can_satisfy(signals)) { - if (signals_state) - *signals_state = state; - DVLOG(2) << "Signals impossible to satisfy for " << port_.name(); - return MOJO_RESULT_FAILED_PRECONDITION; - } - - DVLOG(2) << "Adding awakable to pipe " << pipe_id_ << " endpoint " - << endpoint_ << " [awakable=" << awakable << "; port=" - << port_.name() << "; signals=" << signals << "]"; - - awakables_.Add(awakable, signals, context); - return MOJO_RESULT_OK; + return watchers_.Add(watcher, context, GetHandleSignalsStateNoLock()); } -void MessagePipeDispatcher::RemoveAwakable(Awakable* awakable, - HandleSignalsState* signals_state) { +MojoResult MessagePipeDispatcher::RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context) { base::AutoLock lock(signal_lock_); - if (port_closed_ || in_transit_) { - if (signals_state) - *signals_state = HandleSignalsState(); - } else if (signals_state) { - *signals_state = GetHandleSignalsStateNoLock(); - } - - DVLOG(2) << "Removing awakable from pipe " << pipe_id_ << " endpoint " - << endpoint_ << " [awakable=" << awakable << "; port=" - << port_.name() << "]"; - - awakables_.Remove(awakable); + if (port_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + return watchers_.Remove(watcher, context); } void MessagePipeDispatcher::StartSerialize(uint32_t* num_bytes, @@ -496,7 +447,7 @@ void MessagePipeDispatcher::CancelTransit() { in_transit_.Set(false); // Something may have happened while we were waiting for potential transit. - awakables_.AwakeForStateChange(GetHandleSignalsStateNoLock()); + watchers_.NotifyState(GetHandleSignalsStateNoLock()); } // static @@ -531,7 +482,7 @@ MojoResult MessagePipeDispatcher::CloseNoLock() { return MOJO_RESULT_INVALID_ARGUMENT; port_closed_.Set(true); - awakables_.CancelAll(); + watchers_.NotifyClosed(); if (!port_transferred_) { base::AutoUnlock unlock(signal_lock_); @@ -596,7 +547,7 @@ void MessagePipeDispatcher::OnPortStatusChanged() { } #endif - awakables_.AwakeForStateChange(GetHandleSignalsStateNoLock()); + watchers_.NotifyState(GetHandleSignalsStateNoLock()); } } // namespace edk diff --git a/mojo/edk/system/message_pipe_dispatcher.h b/mojo/edk/system/message_pipe_dispatcher.h index 6743222..574ad66 100644 --- a/mojo/edk/system/message_pipe_dispatcher.h +++ b/mojo/edk/system/message_pipe_dispatcher.h @@ -12,10 +12,10 @@ #include "base/macros.h" #include "mojo/edk/system/atomic_flag.h" -#include "mojo/edk/system/awakable_list.h" #include "mojo/edk/system/dispatcher.h" #include "mojo/edk/system/message_for_transit.h" #include "mojo/edk/system/ports/port_ref.h" +#include "mojo/edk/system/watcher_set.h" namespace mojo { namespace edk { @@ -48,10 +48,6 @@ class MessagePipeDispatcher : public Dispatcher { // Dispatcher: Type GetType() const override; MojoResult Close() override; - MojoResult Watch(MojoHandleSignals signals, - const Watcher::WatchCallback& callback, - uintptr_t context) override; - MojoResult CancelWatch(uintptr_t context) override; MojoResult WriteMessage(std::unique_ptr<MessageForTransit> message, MojoWriteMessageFlags flags) override; MojoResult ReadMessage(std::unique_ptr<MessageForTransit>* message, @@ -61,12 +57,10 @@ class MessagePipeDispatcher : public Dispatcher { MojoReadMessageFlags flags, bool read_any_size) override; HandleSignalsState GetHandleSignalsState() const override; - MojoResult AddAwakable(Awakable* awakable, - MojoHandleSignals signals, - uintptr_t context, - HandleSignalsState* signals_state) override; - void RemoveAwakable(Awakable* awakable, - HandleSignalsState* signals_state) override; + MojoResult AddWatcherRef(const scoped_refptr<WatcherDispatcher>& watcher, + uintptr_t context) override; + MojoResult RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context) override; void StartSerialize(uint32_t* num_bytes, uint32_t* num_ports, uint32_t* num_handles) override; @@ -110,7 +104,7 @@ class MessagePipeDispatcher : public Dispatcher { bool port_transferred_ = false; AtomicFlag port_closed_; - AwakableList awakables_; + WatcherSet watchers_; DISALLOW_COPY_AND_ASSIGN(MessagePipeDispatcher); }; diff --git a/mojo/edk/system/message_pipe_perftest.cc b/mojo/edk/system/message_pipe_perftest.cc index a6ce370..9866c47 100644 --- a/mojo/edk/system/message_pipe_perftest.cc +++ b/mojo/edk/system/message_pipe_perftest.cc @@ -47,8 +47,7 @@ class MessagePipePerfTest : public test::MojoTestBase { 0, MOJO_WRITE_MESSAGE_FLAG_NONE), MOJO_RESULT_OK); HandleSignalsState hss; - CHECK_EQ(MojoWait(mp, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE, - &hss), + CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss), MOJO_RESULT_OK); uint32_t read_buffer_size = static_cast<uint32_t>(read_buffer_.size()); CHECK_EQ(MojoReadMessage(mp, &read_buffer_[0], &read_buffer_size, nullptr, @@ -98,9 +97,7 @@ class MessagePipePerfTest : public test::MojoTestBase { while (true) { // Wait for our end of the message pipe to be readable. HandleSignalsState hss; - MojoResult result = - MojoWait(mp, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss); + MojoResult result = WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss); if (result != MOJO_RESULT_OK) { rv = result; break; diff --git a/mojo/edk/system/message_pipe_unittest.cc b/mojo/edk/system/message_pipe_unittest.cc index 8f48950..e6f1ff6 100644 --- a/mojo/edk/system/message_pipe_unittest.cc +++ b/mojo/edk/system/message_pipe_unittest.cc @@ -100,8 +100,8 @@ TEST_F(MessagePipeTest, Basic) { ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0]))); MojoHandleSignalsState state; - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); // Read from port 0. buffer[0] = 123; @@ -124,8 +124,8 @@ TEST_F(MessagePipeTest, Basic) { buffer[1] = 0; ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe0_, buffer, sizeof(buffer[0]))); - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &state)); // Read from port 1 with buffer size 0 (should get the size of next message). // Also test that giving a null buffer is okay when the buffer size is 0. @@ -154,8 +154,8 @@ TEST_F(MessagePipeTest, Basic) { ASSERT_EQ(123456789, buffer[0]); ASSERT_EQ(456, buffer[1]); - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &state)); // Read again from port 1. buffer[0] = 123; @@ -179,8 +179,8 @@ TEST_F(MessagePipeTest, Basic) { MojoClose(pipe0_); pipe0_ = MOJO_HANDLE_INVALID; - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, &state)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state)); // Try to write from port 1 (to port 0). buffer[0] = 456789012; @@ -215,8 +215,8 @@ TEST_F(MessagePipeTest, CloseWithQueuedIncomingMessages) { } MojoHandleSignalsState state; - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); // Port 0 shouldn't be empty. buffer_size = 0; @@ -241,8 +241,8 @@ TEST_F(MessagePipeTest, DiscardMode) { ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0]))); MojoHandleSignalsState state; - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); // Read/discard from port 0 (no buffer); get size. buffer_size = 0; @@ -261,8 +261,8 @@ TEST_F(MessagePipeTest, DiscardMode) { ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0]))); - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); // Read from port 0 (buffer big enough). buffer[0] = 123; @@ -283,8 +283,8 @@ TEST_F(MessagePipeTest, DiscardMode) { buffer[1] = 0; ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0]))); - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); // Read/discard from port 0 (buffer too small); get size. buffer_size = 1; @@ -302,8 +302,8 @@ TEST_F(MessagePipeTest, DiscardMode) { buffer[1] = 0; ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0]))); - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); // Discard from port 0. buffer_size = 1; @@ -323,41 +323,27 @@ TEST_F(MessagePipeTest, BasicWaiting) { const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer)); uint32_t buffer_size; - // Always writable (until the other port is closed). - hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, - &hss)); - ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); - ASSERT_EQ(kAllSignals, hss.satisfiable_signals); - hss = MojoHandleSignalsState(); - - // Not yet readable. - ASSERT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, - MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); + // Always writable (until the other port is closed). Not yet readable. Peer + // not closed. + hss = GetSignalsState(pipe0_); ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); ASSERT_EQ(kAllSignals, hss.satisfiable_signals); - - // The peer is not closed. hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, - MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, 0, &hss)); - ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); - ASSERT_EQ(kAllSignals, hss.satisfiable_signals); // Write from port 0 (to port 1), to make port 1 readable. buffer[0] = 123456789; ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe0_, buffer, kBufferSize)); // Port 1 should already be readable now. - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); ASSERT_EQ(kAllSignals, hss.satisfiable_signals); // ... and still writable. hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_WRITABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_WRITABLE, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); ASSERT_EQ(kAllSignals, hss.satisfiable_signals); @@ -368,8 +354,8 @@ TEST_F(MessagePipeTest, BasicWaiting) { // Port 1 should be signaled with peer closed. hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, &hss)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, @@ -379,8 +365,7 @@ TEST_F(MessagePipeTest, BasicWaiting) { hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_WRITABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_WRITABLE, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, @@ -388,8 +373,8 @@ TEST_F(MessagePipeTest, BasicWaiting) { // But it should still be readable. hss = MojoHandleSignalsState(); - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, @@ -404,8 +389,7 @@ TEST_F(MessagePipeTest, BasicWaiting) { // Now port 1 should no longer be readable. hss = MojoHandleSignalsState(); ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); } @@ -453,9 +437,7 @@ TEST_F(MessagePipeTest, WriteAndReadMessageObject) { EXPECT_EQ(MOJO_RESULT_OK, MojoWriteMessageNew(a, message, MOJO_WRITE_MESSAGE_FLAG_NONE)); - EXPECT_EQ(MOJO_RESULT_OK, - MojoWait(b, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE, - nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(b, MOJO_HANDLE_SIGNAL_READABLE)); uint32_t num_bytes = 0; uint32_t num_handles = 0; EXPECT_EQ(MOJO_RESULT_OK, @@ -489,8 +471,7 @@ DEFINE_TEST_CLIENT_TEST_WITH_PIPE(HandlePingPong, MessagePipeTest, h) { WriteMessageWithHandles(h, "", handles, kPingPongHandlesPerIteration); } - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE)); char msg[4]; uint32_t num_bytes = 4; EXPECT_EQ(MOJO_RESULT_OK, ReadMessage(h, msg, &num_bytes)); @@ -675,9 +656,7 @@ TEST_F(FuseMessagePipeTest, FuseAfterPeerClosure) { EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(b)); EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(c)); - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(d, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, nullptr)); - + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(d, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); } @@ -700,9 +679,7 @@ TEST_F(FuseMessagePipeTest, FuseAfterPeerWriteAndClosure) { EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(c)); EXPECT_EQ(kTestMessage, ReadMessage(d)); - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(d, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, nullptr)); - + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(d, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); } diff --git a/mojo/edk/system/multiprocess_message_pipe_unittest.cc b/mojo/edk/system/multiprocess_message_pipe_unittest.cc index 498980c..37248d1 100644 --- a/mojo/edk/system/multiprocess_message_pipe_unittest.cc +++ b/mojo/edk/system/multiprocess_message_pipe_unittest.cc @@ -31,7 +31,8 @@ #include "mojo/public/c/system/buffer.h" #include "mojo/public/c/system/functions.h" #include "mojo/public/c/system/types.h" -#include "mojo/public/cpp/system/watcher.h" +#include "mojo/public/cpp/system/simple_watcher.h" +#include "mojo/public/cpp/system/wait.h" #include "testing/gtest/include/gtest/gtest.h" @@ -91,9 +92,7 @@ DEFINE_TEST_CLIENT_WITH_PIPE(EchoEcho, MultiprocessMessagePipeTest, h) { for (;; rv = (rv + 1) % 100) { // Wait for our end of the message pipe to be readable. HandleSignalsState hss; - MojoResult result = - MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss); + MojoResult result = WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss); if (result != MOJO_RESULT_OK) { // It was closed, probably. CHECK_EQ(result, MOJO_RESULT_FAILED_PRECONDITION); @@ -139,8 +138,7 @@ TEST_P(MultiprocessMessagePipeTestWithPeerSupport, Basic) { HandleSignalsState hss; ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); // The child may or may not have closed its end of the message pipe and died // (and we may or may not know it yet), so our end may or may not appear as // writable. @@ -179,8 +177,7 @@ TEST_P(MultiprocessMessagePipeTestWithPeerSupport, QueueMessages) { for (size_t i = 0; i < kNumMessages; i++) { HandleSignalsState hss; ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); // The child may or may not have closed its end of the message pipe and // died (and we may or may not know it yet), so our end may or may not // appear as writable. @@ -208,8 +205,7 @@ TEST_P(MultiprocessMessagePipeTestWithPeerSupport, QueueMessages) { // "quitquitquit"). HandleSignalsState hss; ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); END_CHILD_AND_EXPECT_EXIT_CODE(static_cast<int>(kNumMessages % 100)); @@ -219,8 +215,7 @@ DEFINE_TEST_CLIENT_WITH_PIPE(CheckSharedBuffer, MultiprocessMessagePipeTest, h) { // Wait for the first message from our parent. HandleSignalsState hss; - CHECK_EQ(MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss), + CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss), MOJO_RESULT_OK); // In this test, the parent definitely doesn't close its end of the message // pipe before we do. @@ -265,8 +260,7 @@ DEFINE_TEST_CLIENT_WITH_PIPE(CheckSharedBuffer, MultiprocessMessagePipeTest, // Now wait for our parent to send us a message. hss = HandleSignalsState(); - CHECK_EQ(MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss), + CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss), MOJO_RESULT_OK); CHECK_EQ(hss.satisfied_signals, MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); @@ -322,8 +316,7 @@ TEST_F(MultiprocessMessagePipeTest, SharedBufferPassing) { // Wait for a message from the child. HandleSignalsState hss; ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); @@ -359,8 +352,7 @@ TEST_F(MultiprocessMessagePipeTest, SharedBufferPassing) { // Wait for |h| to become readable, which should fail. hss = HandleSignalsState(); ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); END_CHILD() @@ -369,8 +361,7 @@ TEST_F(MultiprocessMessagePipeTest, SharedBufferPassing) { DEFINE_TEST_CLIENT_WITH_PIPE(CheckPlatformHandleFile, MultiprocessMessagePipeTest, h) { HandleSignalsState hss; - CHECK_EQ(MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss), + CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss), MOJO_RESULT_OK); CHECK_EQ(hss.satisfied_signals, MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); @@ -455,8 +446,7 @@ TEST_P(MultiprocessMessagePipeTestWithPipeCount, PlatformHandlePassing) { // Wait for it to become readable, which should fail. HandleSignalsState hss; ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); END_CHILD() @@ -474,8 +464,7 @@ INSTANTIATE_TEST_CASE_P(PipeCount, DEFINE_TEST_CLIENT_WITH_PIPE(CheckMessagePipe, MultiprocessMessagePipeTest, h) { // Wait for the first message from our parent. HandleSignalsState hss; - CHECK_EQ(MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss), + CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss), MOJO_RESULT_OK); // In this test, the parent definitely doesn't close its end of the message // pipe before we do. @@ -495,8 +484,7 @@ DEFINE_TEST_CLIENT_WITH_PIPE(CheckMessagePipe, MultiprocessMessagePipeTest, h) { CHECK_EQ(num_handlers, 1u); // Read data from the received message pipe. - CHECK_EQ(MojoWait(handles[0], MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss), + CHECK_EQ(WaitForSignals(handles[0], MOJO_HANDLE_SIGNAL_READABLE, &hss), MOJO_RESULT_OK); CHECK_EQ(hss.satisfied_signals, MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); @@ -547,8 +535,7 @@ TEST_P(MultiprocessMessagePipeTestWithPeerSupport, MessagePipePassing) { // Wait for a message from the child. HandleSignalsState hss; ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(mp1, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(mp1, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); @@ -585,8 +572,7 @@ TEST_P(MultiprocessMessagePipeTestWithPeerSupport, MessagePipeTwoPassing) { // Wait for a message from the child. HandleSignalsState hss; ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(mp1, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(mp1, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); @@ -604,8 +590,7 @@ TEST_P(MultiprocessMessagePipeTestWithPeerSupport, MessagePipeTwoPassing) { DEFINE_TEST_CLIENT_WITH_PIPE(DataPipeConsumer, MultiprocessMessagePipeTest, h) { // Wait for the first message from our parent. HandleSignalsState hss; - CHECK_EQ(MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss), + CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss), MOJO_RESULT_OK); // In this test, the parent definitely doesn't close its end of the message // pipe before we do. @@ -625,8 +610,7 @@ DEFINE_TEST_CLIENT_WITH_PIPE(DataPipeConsumer, MultiprocessMessagePipeTest, h) { CHECK_EQ(num_handlers, 1u); // Read data from the received message pipe. - CHECK_EQ(MojoWait(handles[0], MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss), + CHECK_EQ(WaitForSignals(handles[0], MOJO_HANDLE_SIGNAL_READABLE, &hss), MOJO_RESULT_OK); CHECK_EQ(hss.satisfied_signals, MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); @@ -677,8 +661,7 @@ TEST_F(MultiprocessMessagePipeTest, DataPipeConsumer) { // Wait for a message from the child. HandleSignalsState hss; ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(mp1, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &hss)); + WaitForSignals(mp1, MOJO_HANDLE_SIGNAL_READABLE, &hss)); EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); @@ -795,16 +778,15 @@ DEFINE_TEST_CLIENT_WITH_PIPE(EchoServiceFactoryClient, MojoHandle p; ReadMessageWithHandles(h, &p, 1); - std::vector<MojoHandle> handles(2); - handles[0] = h; - handles[1] = p; + std::vector<Handle> handles(2); + handles[0] = Handle(h); + handles[1] = Handle(p); std::vector<MojoHandleSignals> signals(2, MOJO_HANDLE_SIGNAL_READABLE); for (;;) { - uint32_t index; - CHECK_EQ(MojoWaitMany(handles.data(), signals.data(), - static_cast<uint32_t>(handles.size()), - MOJO_DEADLINE_INDEFINITE, &index, nullptr), - MOJO_RESULT_OK); + size_t index; + CHECK_EQ( + mojo::WaitMany(handles.data(), signals.data(), handles.size(), &index), + MOJO_RESULT_OK); DCHECK_LE(index, handles.size()); if (index == 0) { // If data is available on the first pipe, it should be an exit command. @@ -814,16 +796,16 @@ DEFINE_TEST_CLIENT_WITH_PIPE(EchoServiceFactoryClient, // If the second pipe, it should be a new handle requesting echo service. MojoHandle echo_request; ReadMessageWithHandles(p, &echo_request, 1); - handles.push_back(echo_request); + handles.push_back(Handle(echo_request)); signals.push_back(MOJO_HANDLE_SIGNAL_READABLE); } else { // Otherwise it was one of our established echo pipes. Echo! - WriteMessage(handles[index], ReadMessage(handles[index])); + WriteMessage(handles[index].value(), ReadMessage(handles[index].value())); } } for (size_t i = 1; i < handles.size(); ++i) - CloseHandle(handles[i]); + CloseHandle(handles[i].value()); return 0; } @@ -924,9 +906,7 @@ TEST_P(MultiprocessMessagePipeTestWithPeerSupport, PingPongPipe) { EXPECT_EQ("bye", ReadMessage(p0)); // We should still be able to observe peer closure from the other end. - EXPECT_EQ(MOJO_RESULT_OK, - MojoWait(p0, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(p0, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); } // Parses commands from the parent pipe and does whatever it's asked to do. @@ -1110,10 +1090,7 @@ DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReceivePipeWithClosedPeer, MultiprocessMessagePipeTest, h) { MojoHandle p; EXPECT_EQ("foo", ReadMessageWithHandles(h, &p, 1)); - - auto result = MojoWait(p, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, nullptr); - EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(p, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); } TEST_P(MultiprocessMessagePipeTestWithPeerSupport, SendPipeThenClosePeer) { @@ -1159,9 +1136,8 @@ DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReceivePipeWithClosedPeerFromOtherChild, ReadMessageWithHandles(application_client, &service_client, 1)); // Wait for the service client to signal closure. - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(service_client, - MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(service_client, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(service_client)); EXPECT_EQ(MOJO_RESULT_OK, MojoClose(application_client)); @@ -1207,8 +1183,7 @@ TEST_P(MultiprocessMessagePipeTestWithPeerSupport, SendClosePeerSend) { EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); // We should be able to detect peer closure on |a|. - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(a, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(a, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); } DEFINE_TEST_CLIENT_TEST_WITH_PIPE(WriteCloseSendPeerClient, @@ -1251,8 +1226,8 @@ TEST_P(MultiprocessMessagePipeTestWithPeerSupport, WriteCloseSendPeer) { EXPECT_EQ("qux", ReadMessage(p)); // Expect to have peer closure signaled. - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(p, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(p, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); WriteMessage(h, "quit"); END_CHILD() @@ -1265,21 +1240,22 @@ DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MessagePipeStatusChangeInTransitClient, MojoHandle handles[4]; EXPECT_EQ("o_O", ReadMessageWithHandles(parent, handles, 4)); - // Wait on handle 0 using MojoWait. - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(handles[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(handles[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED)); base::MessageLoop message_loop; - // Wait on handle 1 using a Watcher. + // Wait on handle 1 using a SimpleWatcher. { base::RunLoop run_loop; - Watcher watcher(FROM_HERE); - watcher.Start(Handle(handles[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, - base::Bind([] (base::RunLoop* loop, MojoResult result) { - EXPECT_EQ(MOJO_RESULT_OK, result); - loop->Quit(); - }, &run_loop)); + SimpleWatcher watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + watcher.Watch(Handle(handles[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, + base::Bind( + [](base::RunLoop* loop, MojoResult result) { + EXPECT_EQ(MOJO_RESULT_OK, result); + loop->Quit(); + }, + &run_loop)); run_loop.Run(); } @@ -1347,8 +1323,7 @@ TEST_F(MultiprocessMessagePipeTest, NotifyBadMessage) { WriteMessageWithHandles(child2, "hi", &d, 1); // Read a message from the pipe we sent to child1 and flag it as bad. - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(a, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, nullptr)); + ASSERT_EQ(MOJO_RESULT_OK, WaitForSignals(a, MOJO_HANDLE_SIGNAL_READABLE)); uint32_t num_bytes = 0; MojoMessageHandle message; ASSERT_EQ(MOJO_RESULT_OK, @@ -1360,8 +1335,7 @@ TEST_F(MultiprocessMessagePipeTest, NotifyBadMessage) { EXPECT_EQ(MOJO_RESULT_OK, MojoFreeMessage(message)); // Read a message from the pipe we sent to child2 and flag it as bad. - ASSERT_EQ(MOJO_RESULT_OK, MojoWait(c, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, nullptr)); + ASSERT_EQ(MOJO_RESULT_OK, WaitForSignals(c, MOJO_HANDLE_SIGNAL_READABLE)); ASSERT_EQ(MOJO_RESULT_OK, MojoReadMessageNew(c, &message, &num_bytes, nullptr, 0, MOJO_READ_MESSAGE_FLAG_NONE)); diff --git a/mojo/edk/system/node_controller.cc b/mojo/edk/system/node_controller.cc index 7bdb571..73b16b1 100644 --- a/mojo/edk/system/node_controller.cc +++ b/mojo/edk/system/node_controller.cc @@ -694,10 +694,17 @@ void NodeController::SendPeerMessage(const ports::NodeName& name, return; } - // If we don't know who the peer is, queue the message for delivery. If this - // is the first message queued for the peer, we also ask the broker to - // introduce us to them. + // If we don't know who the peer is and we are the broker, we can only assume + // the peer is invalid, i.e., it's either a junk name or has already been + // disconnected. + scoped_refptr<NodeChannel> broker = GetBrokerChannel(); + if (!broker) { + DVLOG(1) << "Dropping message for unknown peer: " << name; + return; + } + // If we aren't the broker, assume we just need to be introduced and queue + // until that can be either confirmed or denied by the broker. bool needs_introduction = false; { base::AutoLock lock(peers_lock_); @@ -705,15 +712,8 @@ void NodeController::SendPeerMessage(const ports::NodeName& name, needs_introduction = queue.empty(); queue.emplace(std::move(channel_message)); } - - if (needs_introduction) { - scoped_refptr<NodeChannel> broker = GetBrokerChannel(); - if (!broker) { - DVLOG(1) << "Dropping message for unknown peer: " << name; - return; - } + if (needs_introduction) broker->RequestIntroduction(name); - } } void NodeController::AcceptIncomingMessages() { @@ -935,6 +935,16 @@ void NodeController::OnAcceptParent(const ports::NodeName& from_node, return; } + { + base::AutoLock lock(reserved_ports_lock_); + auto it = pending_child_tokens_.find(from_node); + if (it != pending_child_tokens_.end()) { + std::string token = std::move(it->second); + pending_child_tokens_.erase(it); + pending_child_tokens_[child_name] = std::move(token); + } + } + scoped_refptr<NodeChannel> channel = it->second; pending_children_.erase(it); @@ -1155,6 +1165,7 @@ void NodeController::OnRequestPortMerge( return; } local_port = it->second.port; + reserved_ports_.erase(it); } int rv = node_->MergePorts(local_port, from_node, connector_port_name); diff --git a/mojo/edk/system/ports/BUILD.gn b/mojo/edk/system/ports/BUILD.gn index 37b2548..5c82761 100644 --- a/mojo/edk/system/ports/BUILD.gn +++ b/mojo/edk/system/ports/BUILD.gn @@ -21,6 +21,7 @@ source_set("ports") { "port.cc", "port.h", "port_ref.cc", + "port_ref.h", "user_data.h", ] diff --git a/mojo/edk/system/request_context.cc b/mojo/edk/system/request_context.cc index 6370ab1..5de65d7 100644 --- a/mojo/edk/system/request_context.cc +++ b/mojo/edk/system/request_context.cc @@ -36,27 +36,34 @@ RequestContext::~RequestContext() { // since we're starting over at the bottom of the stack. tls_context_->Set(nullptr); - MojoWatchNotificationFlags flags = MOJO_WATCH_NOTIFICATION_FLAG_NONE; + MojoWatcherNotificationFlags flags = MOJO_WATCHER_NOTIFICATION_FLAG_NONE; if (source_ == Source::SYSTEM) - flags |= MOJO_WATCH_NOTIFICATION_FLAG_FROM_SYSTEM; + flags |= MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM; - // We run all cancellation finalizers first. This is necessary because it's - // possible that one of the cancelled watchers has other pending finalizers + // We send all cancellation notifications first. This is necessary because + // it's possible that cancelled watches have other pending notifications // attached to this RequestContext. // - // From the application's perspective the watch has already been cancelled, - // so we have to honor our contract which guarantees no more notifications. - for (const scoped_refptr<Watcher>& watcher : - watch_cancel_finalizers_.container()) - watcher->Cancel(); + // From the application's perspective the watch is cancelled as soon as this + // notification is received, and dispatching the cancellation notification + // updates some internal Watch state to ensure no further notifications + // fire. Because notifications on a single Watch are mutually exclusive, + // this is sufficient to guarantee that MOJO_RESULT_CANCELLED is the last + // notification received; which is the guarantee the API makes. + for (const scoped_refptr<Watch>& watch : + watch_cancel_finalizers_.container()) { + static const HandleSignalsState closed_state = {0, 0}; + + // Establish a new RequestContext to capture and run any new notifications + // triggered by the callback invocation. + RequestContext inner_context(source_); + watch->InvokeCallback(MOJO_RESULT_CANCELLED, closed_state, flags); + } for (const WatchNotifyFinalizer& watch : - watch_notify_finalizers_.container()) { - // Establish a new request context for the extent of each callback to - // ensure that they don't themselves invoke callbacks while holding a - // watcher lock. - RequestContext request_context(source_); - watch.watcher->MaybeInvokeCallback(watch.result, watch.state, flags); + watch_notify_finalizers_.container()) { + RequestContext inner_context(source_); + watch.watch->InvokeCallback(watch.result, watch.state, flags); } } else { // It should be impossible for nested contexts to have finalizers. @@ -71,18 +78,17 @@ RequestContext* RequestContext::current() { return g_current_context.Pointer()->Get(); } -void RequestContext::AddWatchNotifyFinalizer( - scoped_refptr<Watcher> watcher, - MojoResult result, - const HandleSignalsState& state) { +void RequestContext::AddWatchNotifyFinalizer(scoped_refptr<Watch> watch, + MojoResult result, + const HandleSignalsState& state) { DCHECK(IsCurrent()); watch_notify_finalizers_->push_back( - WatchNotifyFinalizer(std::move(watcher), result, state)); + WatchNotifyFinalizer(std::move(watch), result, state)); } -void RequestContext::AddWatchCancelFinalizer(scoped_refptr<Watcher> watcher) { +void RequestContext::AddWatchCancelFinalizer(scoped_refptr<Watch> watch) { DCHECK(IsCurrent()); - watch_cancel_finalizers_->push_back(std::move(watcher)); + watch_cancel_finalizers_->push_back(std::move(watch)); } bool RequestContext::IsCurrent() const { @@ -90,10 +96,10 @@ bool RequestContext::IsCurrent() const { } RequestContext::WatchNotifyFinalizer::WatchNotifyFinalizer( - scoped_refptr<Watcher> watcher, + scoped_refptr<Watch> watch, MojoResult result, const HandleSignalsState& state) - : watcher(std::move(watcher)), result(result), state(state) {} + : watch(std::move(watch)), result(result), state(state) {} RequestContext::WatchNotifyFinalizer::WatchNotifyFinalizer( const WatchNotifyFinalizer& other) = default; diff --git a/mojo/edk/system/request_context.h b/mojo/edk/system/request_context.h index 7aa0e69..d1f43bd 100644 --- a/mojo/edk/system/request_context.h +++ b/mojo/edk/system/request_context.h @@ -9,7 +9,7 @@ #include "base/macros.h" #include "mojo/edk/system/handle_signals_state.h" #include "mojo/edk/system/system_impl_export.h" -#include "mojo/edk/system/watcher.h" +#include "mojo/edk/system/watch.h" namespace base { template<typename T> class ThreadLocalPointer; @@ -49,43 +49,44 @@ class MOJO_SYSTEM_IMPL_EXPORT RequestContext { // Adds a finalizer to this RequestContext corresponding to a watch callback // which should be triggered in response to some handle state change. If - // the Watcher hasn't been cancelled by the time this RequestContext is + // the WatcherDispatcher hasn't been closed by the time this RequestContext is // destroyed, its WatchCallback will be invoked with |result| and |state| // arguments. - void AddWatchNotifyFinalizer(scoped_refptr<Watcher> watcher, + void AddWatchNotifyFinalizer(scoped_refptr<Watch> watch, MojoResult result, const HandleSignalsState& state); - // Adds a finalizer to this RequestContext which cancels a watch. - void AddWatchCancelFinalizer(scoped_refptr<Watcher> watcher); + // Adds a finalizer to this RequestContext corresponding to a watch callback + // which should be triggered to notify of watch cancellation. This appends to + // a separate finalizer list from AddWatchNotifyFinalizer, as pending + // cancellations must always preempt other pending notifications. + void AddWatchCancelFinalizer(scoped_refptr<Watch> watch); private: // Is this request context the current one? bool IsCurrent() const; struct WatchNotifyFinalizer { - WatchNotifyFinalizer(scoped_refptr<Watcher> watcher, + WatchNotifyFinalizer(scoped_refptr<Watch> watch, MojoResult result, const HandleSignalsState& state); WatchNotifyFinalizer(const WatchNotifyFinalizer& other); ~WatchNotifyFinalizer(); - scoped_refptr<Watcher> watcher; + scoped_refptr<Watch> watch; MojoResult result; HandleSignalsState state; }; - // Chosen by fair dice roll. - // - // TODO: We should measure the distribution of # of finalizers typical to - // any RequestContext and adjust this number accordingly. It's probably - // almost always 1, but 4 seems like a harmless upper bound for now. - static const size_t kStaticWatchFinalizersCapacity = 4; + // NOTE: This upper bound was chosen somewhat arbitrarily after observing some + // rare worst-case behavior in Chrome. A vast majority of RequestContexts only + // ever accumulate 0 or 1 finalizers. + static const size_t kStaticWatchFinalizersCapacity = 8; using WatchNotifyFinalizerList = base::StackVector<WatchNotifyFinalizer, kStaticWatchFinalizersCapacity>; using WatchCancelFinalizerList = - base::StackVector<scoped_refptr<Watcher>, kStaticWatchFinalizersCapacity>; + base::StackVector<scoped_refptr<Watch>, kStaticWatchFinalizersCapacity>; const Source source_; diff --git a/mojo/edk/system/signals_unittest.cc b/mojo/edk/system/signals_unittest.cc new file mode 100644 index 0000000..e8b0cd1 --- /dev/null +++ b/mojo/edk/system/signals_unittest.cc @@ -0,0 +1,76 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/functions.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/c/system/types.h" + +namespace mojo { +namespace edk { +namespace { + +using SignalsTest = test::MojoTestBase; + +TEST_F(SignalsTest, QueryInvalidArguments) { + MojoHandleSignalsState state = {0, 0}; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoQueryHandleSignalsState(MOJO_HANDLE_INVALID, &state)); + + MojoHandle a, b; + CreateMessagePipe(&a, &b); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoQueryHandleSignalsState(a, nullptr)); +} + +TEST_F(SignalsTest, QueryMessagePipeSignals) { + MojoHandleSignalsState state = {0, 0}; + + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(a, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfiable_signals); + + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfiable_signals); + + WriteMessage(a, "ok"); + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(b, MOJO_HANDLE_SIGNAL_READABLE)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfiable_signals); + + EXPECT_EQ("ok", ReadMessage(b)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfiable_signals); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(b, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/wait_set_dispatcher.cc b/mojo/edk/system/wait_set_dispatcher.cc deleted file mode 100644 index edca415..0000000 --- a/mojo/edk/system/wait_set_dispatcher.cc +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "mojo/edk/system/wait_set_dispatcher.h" - -#include <stdint.h> - -#include <algorithm> -#include <utility> - -#include "base/logging.h" -#include "mojo/edk/system/awakable.h" - -namespace mojo { -namespace edk { - -class WaitSetDispatcher::Waiter final : public Awakable { - public: - explicit Waiter(WaitSetDispatcher* dispatcher) : dispatcher_(dispatcher) {} - ~Waiter() {} - - // |Awakable| implementation. - bool Awake(MojoResult result, uintptr_t context) override { - // Note: This is called with various Mojo locks held. - dispatcher_->WakeDispatcher(result, context); - // Removes |this| from the dispatcher's list of waiters. - return false; - } - - private: - WaitSetDispatcher* const dispatcher_; -}; - -WaitSetDispatcher::WaitState::WaitState() {} - -WaitSetDispatcher::WaitState::WaitState(const WaitState& other) = default; - -WaitSetDispatcher::WaitState::~WaitState() {} - -WaitSetDispatcher::WaitSetDispatcher() - : waiter_(new WaitSetDispatcher::Waiter(this)) {} - -Dispatcher::Type WaitSetDispatcher::GetType() const { - return Type::WAIT_SET; -} - -MojoResult WaitSetDispatcher::Close() { - base::AutoLock lock(lock_); - - if (is_closed_) - return MOJO_RESULT_INVALID_ARGUMENT; - is_closed_ = true; - - { - base::AutoLock locker(awakable_lock_); - awakable_list_.CancelAll(); - } - - for (const auto& entry : waiting_dispatchers_) - entry.second.dispatcher->RemoveAwakable(waiter_.get(), nullptr); - waiting_dispatchers_.clear(); - - base::AutoLock locker(awoken_lock_); - awoken_queue_.clear(); - processed_dispatchers_.clear(); - - return MOJO_RESULT_OK; -} - -MojoResult WaitSetDispatcher::AddWaitingDispatcher( - const scoped_refptr<Dispatcher>& dispatcher, - MojoHandleSignals signals, - uintptr_t context) { - if (dispatcher == this) - return MOJO_RESULT_INVALID_ARGUMENT; - - base::AutoLock lock(lock_); - - if (is_closed_) - return MOJO_RESULT_INVALID_ARGUMENT; - - uintptr_t dispatcher_handle = reinterpret_cast<uintptr_t>(dispatcher.get()); - auto it = waiting_dispatchers_.find(dispatcher_handle); - if (it != waiting_dispatchers_.end()) { - return MOJO_RESULT_ALREADY_EXISTS; - } - - const MojoResult result = dispatcher->AddAwakable(waiter_.get(), signals, - dispatcher_handle, nullptr); - if (result == MOJO_RESULT_INVALID_ARGUMENT) { - // Dispatcher is closed. - return result; - } else if (result != MOJO_RESULT_OK) { - WakeDispatcher(result, dispatcher_handle); - } - - WaitState state; - state.dispatcher = dispatcher; - state.context = context; - state.signals = signals; - bool inserted = waiting_dispatchers_.insert( - std::make_pair(dispatcher_handle, state)).second; - DCHECK(inserted); - - return MOJO_RESULT_OK; -} - -MojoResult WaitSetDispatcher::RemoveWaitingDispatcher( - const scoped_refptr<Dispatcher>& dispatcher) { - uintptr_t dispatcher_handle = reinterpret_cast<uintptr_t>(dispatcher.get()); - - base::AutoLock lock(lock_); - if (is_closed_) - return MOJO_RESULT_INVALID_ARGUMENT; - - auto it = waiting_dispatchers_.find(dispatcher_handle); - if (it == waiting_dispatchers_.end()) - return MOJO_RESULT_NOT_FOUND; - - dispatcher->RemoveAwakable(waiter_.get(), nullptr); - // At this point, it should not be possible for |waiter_| to be woken with - // |dispatcher|. - waiting_dispatchers_.erase(it); - - base::AutoLock locker(awoken_lock_); - int num_erased = 0; - for (auto it = awoken_queue_.begin(); it != awoken_queue_.end();) { - if (it->first == dispatcher_handle) { - it = awoken_queue_.erase(it); - num_erased++; - } else { - ++it; - } - } - // The dispatcher should only exist in the queue once. - DCHECK_LE(num_erased, 1); - processed_dispatchers_.erase( - std::remove(processed_dispatchers_.begin(), processed_dispatchers_.end(), - dispatcher_handle), - processed_dispatchers_.end()); - - return MOJO_RESULT_OK; -} - -MojoResult WaitSetDispatcher::GetReadyDispatchers( - uint32_t* count, - DispatcherVector* dispatchers, - MojoResult* results, - uintptr_t* contexts) { - base::AutoLock lock(lock_); - - if (is_closed_) - return MOJO_RESULT_INVALID_ARGUMENT; - - dispatchers->clear(); - - // Re-queue any already retrieved dispatchers. These should be the dispatchers - // that were returned on the last call to this function. This loop is - // necessary to preserve the logically level-triggering behaviour of waiting - // in Mojo. In particular, if no action is taken on a signal, that signal - // continues to be satisfied, and therefore a |MojoWait()| on that - // handle/signal continues to return immediately. - std::deque<uintptr_t> pending; - { - base::AutoLock locker(awoken_lock_); - pending.swap(processed_dispatchers_); - } - for (uintptr_t d : pending) { - auto it = waiting_dispatchers_.find(d); - // Anything in |processed_dispatchers_| should also be in - // |waiting_dispatchers_| since dispatchers are removed from both in - // |RemoveWaitingDispatcherImplNoLock()|. - DCHECK(it != waiting_dispatchers_.end()); - - // |awoken_mutex_| cannot be held here because - // |Dispatcher::AddAwakable()| acquires the Dispatcher's mutex. This - // mutex is held while running |WakeDispatcher()| below, which needs to - // acquire |awoken_mutex_|. Holding |awoken_mutex_| here would result in - // a deadlock. - const MojoResult result = it->second.dispatcher->AddAwakable( - waiter_.get(), it->second.signals, d, nullptr); - - if (result == MOJO_RESULT_INVALID_ARGUMENT) { - // Dispatcher is closed. Implicitly remove it from the wait set since - // it may be impossible to remove using |MojoRemoveHandle()|. - waiting_dispatchers_.erase(it); - } else if (result != MOJO_RESULT_OK) { - WakeDispatcher(result, d); - } - } - - const uint32_t max_woken = *count; - uint32_t num_woken = 0; - - base::AutoLock locker(awoken_lock_); - while (!awoken_queue_.empty() && num_woken < max_woken) { - uintptr_t d = awoken_queue_.front().first; - MojoResult result = awoken_queue_.front().second; - awoken_queue_.pop_front(); - - auto it = waiting_dispatchers_.find(d); - DCHECK(it != waiting_dispatchers_.end()); - - results[num_woken] = result; - dispatchers->push_back(it->second.dispatcher); - if (contexts) - contexts[num_woken] = it->second.context; - - if (result != MOJO_RESULT_CANCELLED) { - processed_dispatchers_.push_back(d); - } else { - // |MOJO_RESULT_CANCELLED| indicates that the dispatcher was closed. - // Return it, but also implcitly remove it from the wait set. - waiting_dispatchers_.erase(it); - } - - num_woken++; - } - - *count = num_woken; - if (!num_woken) - return MOJO_RESULT_SHOULD_WAIT; - - return MOJO_RESULT_OK; -} - -HandleSignalsState WaitSetDispatcher::GetHandleSignalsState() const { - base::AutoLock lock(lock_); - return GetHandleSignalsStateNoLock(); -} - -HandleSignalsState WaitSetDispatcher::GetHandleSignalsStateNoLock() const { - lock_.AssertAcquired(); - if (is_closed_) - return HandleSignalsState(); - - HandleSignalsState rv; - rv.satisfiable_signals = MOJO_HANDLE_SIGNAL_READABLE; - base::AutoLock locker(awoken_lock_); - if (!awoken_queue_.empty() || !processed_dispatchers_.empty()) - rv.satisfied_signals = MOJO_HANDLE_SIGNAL_READABLE; - return rv; -} - -MojoResult WaitSetDispatcher::AddAwakable(Awakable* awakable, - MojoHandleSignals signals, - uintptr_t context, - HandleSignalsState* signals_state) { - base::AutoLock lock(lock_); - // |awakable_lock_| is acquired here instead of immediately before adding to - // |awakable_list_| because we need to check the signals state and add to - // |awakable_list_| as an atomic operation. If the pair isn't atomic, it is - // possible for the signals state to change after it is checked, but before - // the awakable is added. In that case, the added awakable won't be signalled. - base::AutoLock awakable_locker(awakable_lock_); - HandleSignalsState state(GetHandleSignalsStateNoLock()); - if (state.satisfies(signals)) { - if (signals_state) - *signals_state = state; - return MOJO_RESULT_ALREADY_EXISTS; - } - if (!state.can_satisfy(signals)) { - if (signals_state) - *signals_state = state; - return MOJO_RESULT_FAILED_PRECONDITION; - } - - awakable_list_.Add(awakable, signals, context); - return MOJO_RESULT_OK; -} - -void WaitSetDispatcher::RemoveAwakable(Awakable* awakable, - HandleSignalsState* signals_state) { - { - base::AutoLock locker(awakable_lock_); - awakable_list_.Remove(awakable); - } - if (signals_state) - *signals_state = GetHandleSignalsState(); -} - -bool WaitSetDispatcher::BeginTransit() { - // You can't transfer wait sets! - return false; -} - -WaitSetDispatcher::~WaitSetDispatcher() { - DCHECK(waiting_dispatchers_.empty()); - DCHECK(awoken_queue_.empty()); - DCHECK(processed_dispatchers_.empty()); -} - -void WaitSetDispatcher::WakeDispatcher(MojoResult result, uintptr_t context) { - { - base::AutoLock locker(awoken_lock_); - - if (result == MOJO_RESULT_ALREADY_EXISTS) - result = MOJO_RESULT_OK; - - awoken_queue_.push_back(std::make_pair(context, result)); - } - - base::AutoLock locker(awakable_lock_); - HandleSignalsState signals_state; - signals_state.satisfiable_signals = MOJO_HANDLE_SIGNAL_READABLE; - signals_state.satisfied_signals = MOJO_HANDLE_SIGNAL_READABLE; - awakable_list_.AwakeForStateChange(signals_state); -} - -} // namespace edk -} // namespace mojo diff --git a/mojo/edk/system/wait_set_dispatcher.h b/mojo/edk/system/wait_set_dispatcher.h deleted file mode 100644 index 619a1be..0000000 --- a/mojo/edk/system/wait_set_dispatcher.h +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MOJO_EDK_SYSTEM_WAIT_SET_DISPATCHER_H_ -#define MOJO_EDK_SYSTEM_WAIT_SET_DISPATCHER_H_ - -#include <stdint.h> - -#include <deque> -#include <memory> -#include <unordered_map> -#include <utility> - -#include "base/macros.h" -#include "base/synchronization/lock.h" -#include "mojo/edk/system/awakable_list.h" -#include "mojo/edk/system/dispatcher.h" -#include "mojo/edk/system/system_impl_export.h" - -namespace mojo { -namespace edk { - -class MOJO_SYSTEM_IMPL_EXPORT WaitSetDispatcher : public Dispatcher { - public: - WaitSetDispatcher(); - - // Dispatcher: - Type GetType() const override; - MojoResult Close() override; - MojoResult AddWaitingDispatcher(const scoped_refptr<Dispatcher>& dispatcher, - MojoHandleSignals signals, - uintptr_t context) override; - MojoResult RemoveWaitingDispatcher( - const scoped_refptr<Dispatcher>& dispatcher) override; - MojoResult GetReadyDispatchers(uint32_t* count, - DispatcherVector* dispatchers, - MojoResult* results, - uintptr_t* contexts) override; - HandleSignalsState GetHandleSignalsState() const override; - MojoResult AddAwakable(Awakable* awakable, - MojoHandleSignals signals, - uintptr_t context, - HandleSignalsState* signals_state) override; - void RemoveAwakable(Awakable* awakable, - HandleSignalsState* signals_state) override; - bool BeginTransit() override; - - private: - // Internal implementation of Awakable. - class Waiter; - - struct WaitState { - WaitState(); - WaitState(const WaitState& other); - ~WaitState(); - - scoped_refptr<Dispatcher> dispatcher; - MojoHandleSignals signals; - uintptr_t context; - }; - - ~WaitSetDispatcher() override; - - HandleSignalsState GetHandleSignalsStateNoLock() const; - - // Signal that the dispatcher indexed by |context| has been woken up with - // |result| and is now ready. - void WakeDispatcher(MojoResult result, uintptr_t context); - - // Guards |is_closed_|, |waiting_dispatchers_|, and |waiter_|. - // - // TODO: Consider removing this. - mutable base::Lock lock_; - bool is_closed_ = false; - - // Map of dispatchers being waited on. Key is a Dispatcher* casted to a - // uintptr_t, and should be treated as an opaque value and not casted back. - std::unordered_map<uintptr_t, WaitState> waiting_dispatchers_; - - // Separate lock that can be locked without locking |lock_|. - mutable base::Lock awoken_lock_; - // List of dispatchers that have been woken up. Any dispatcher in this queue - // will NOT currently be waited on. - std::deque<std::pair<uintptr_t, MojoResult>> awoken_queue_; - // List of dispatchers that have been woken up and retrieved. - std::deque<uintptr_t> processed_dispatchers_; - - // Separate lock that can be locked without locking |lock_|. - base::Lock awakable_lock_; - // List of dispatchers being waited on. - AwakableList awakable_list_; - - // Waiter used to wait on dispatchers. - std::unique_ptr<Waiter> waiter_; - - DISALLOW_COPY_AND_ASSIGN(WaitSetDispatcher); -}; - -} // namespace edk -} // namespace mojo - -#endif // MOJO_EDK_SYSTEM_WAIT_SET_DISPATCHER_H_ diff --git a/mojo/edk/system/wait_set_dispatcher_unittest.cc b/mojo/edk/system/wait_set_dispatcher_unittest.cc deleted file mode 100644 index 42ac865..0000000 --- a/mojo/edk/system/wait_set_dispatcher_unittest.cc +++ /dev/null @@ -1,493 +0,0 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "mojo/edk/system/wait_set_dispatcher.h" - -#include <stddef.h> -#include <stdint.h> - -#include <algorithm> - -#include "base/macros.h" -#include "base/memory/ref_counted.h" -#include "mojo/edk/embedder/embedder_internal.h" -#include "mojo/edk/system/core.h" -#include "mojo/edk/system/message_for_transit.h" -#include "mojo/edk/system/message_pipe_dispatcher.h" -#include "mojo/edk/system/request_context.h" -#include "mojo/edk/system/test_utils.h" -#include "mojo/edk/system/waiter.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace mojo { -namespace edk { -namespace { - -class WaitSetDispatcherTest : public ::testing::Test { - public: - WaitSetDispatcherTest() {} - ~WaitSetDispatcherTest() override {} - - void SetUp() override { - CreateMessagePipe(&dispatcher0_, &dispatcher1_); - } - - void TearDown() override { - for (auto& d : dispatchers_to_close_) - d->Close(); - } - - MojoResult GetOneReadyDispatcher( - const scoped_refptr<WaitSetDispatcher>& wait_set, - scoped_refptr<Dispatcher>* ready_dispatcher, - uintptr_t* context) { - uint32_t count = 1; - MojoResult dispatcher_result = MOJO_RESULT_UNKNOWN; - DispatcherVector dispatchers; - MojoResult result = wait_set->GetReadyDispatchers( - &count, &dispatchers, &dispatcher_result, context); - if (result == MOJO_RESULT_OK) { - CHECK_EQ(1u, dispatchers.size()); - *ready_dispatcher = dispatchers[0]; - return dispatcher_result; - } - return result; - } - - void CreateMessagePipe(scoped_refptr<MessagePipeDispatcher>* d0, - scoped_refptr<MessagePipeDispatcher>* d1) { - MojoHandle h0, h1; - EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(nullptr, &h0, &h1)); - - Core* core = mojo::edk::internal::g_core; - *d0 = scoped_refptr<MessagePipeDispatcher>( - static_cast<MessagePipeDispatcher*>(core->GetDispatcher(h0).get())); - *d1 = scoped_refptr<MessagePipeDispatcher>( - static_cast<MessagePipeDispatcher*>(core->GetDispatcher(h1).get())); - pipe_id_generator_++; - - dispatchers_to_close_.push_back(*d0); - dispatchers_to_close_.push_back(*d1); - } - - void CloseOnShutdown(const scoped_refptr<Dispatcher>& dispatcher) { - dispatchers_to_close_.push_back(dispatcher); - } - - void WriteMessage(MessagePipeDispatcher* dispatcher, - const void* bytes, - size_t num_bytes) { - Core* core = mojo::edk::internal::g_core; - MojoMessageHandle msg; - ASSERT_EQ(MOJO_RESULT_OK, - core->AllocMessage(static_cast<uint32_t>(num_bytes), nullptr, 0, - MOJO_ALLOC_MESSAGE_FLAG_NONE, &msg)); - void* buffer; - ASSERT_EQ(MOJO_RESULT_OK, core->GetMessageBuffer(msg, &buffer)); - memcpy(buffer, bytes, num_bytes); - - std::unique_ptr<MessageForTransit> message( - reinterpret_cast<MessageForTransit*>(msg)); - ASSERT_EQ(MOJO_RESULT_OK, - dispatcher->WriteMessage(std::move(message), - MOJO_WRITE_MESSAGE_FLAG_NONE)); - } - - void ReadMessage(MessagePipeDispatcher* dispatcher, - void* bytes, - uint32_t* num_bytes) { - std::unique_ptr<MessageForTransit> message; - ASSERT_EQ(MOJO_RESULT_OK, - dispatcher->ReadMessage(&message, num_bytes, nullptr, 0, - MOJO_READ_MESSAGE_FLAG_NONE, false)); - memcpy(bytes, message->bytes(), *num_bytes); - } - - protected: - scoped_refptr<MessagePipeDispatcher> dispatcher0_; - scoped_refptr<MessagePipeDispatcher> dispatcher1_; - - private: - // We keep an active RequestContext for the duration of each test. It's unused - // since these tests don't rely on the MojoWatch API. - const RequestContext request_context_; - - static uint64_t pipe_id_generator_; - DispatcherVector dispatchers_to_close_; - - DISALLOW_COPY_AND_ASSIGN(WaitSetDispatcherTest); -}; - -// static -uint64_t WaitSetDispatcherTest::pipe_id_generator_ = 1; - -TEST_F(WaitSetDispatcherTest, Basic) { - scoped_refptr<WaitSetDispatcher> wait_set = new WaitSetDispatcher(); - CloseOnShutdown(wait_set); - ASSERT_EQ(MOJO_RESULT_OK, - wait_set->AddWaitingDispatcher(dispatcher0_, - MOJO_HANDLE_SIGNAL_READABLE, 1)); - ASSERT_EQ(MOJO_RESULT_OK, - wait_set->AddWaitingDispatcher(dispatcher1_, - MOJO_HANDLE_SIGNAL_WRITABLE, 2)); - - Waiter w; - uintptr_t context = 0; - w.Init(); - HandleSignalsState hss; - // |dispatcher1_| should already be writable. - EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, - wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals); - - scoped_refptr<Dispatcher> woken_dispatcher; - EXPECT_EQ(MOJO_RESULT_OK, - GetOneReadyDispatcher(wait_set, &woken_dispatcher, &context)); - EXPECT_EQ(dispatcher1_, woken_dispatcher); - EXPECT_EQ(2u, context); - // If a ready dispatcher isn't removed, it will continue to be returned. - EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, - wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); - woken_dispatcher = nullptr; - context = 0; - EXPECT_EQ(MOJO_RESULT_OK, - GetOneReadyDispatcher(wait_set, &woken_dispatcher, &context)); - EXPECT_EQ(dispatcher1_, woken_dispatcher); - EXPECT_EQ(2u, context); - ASSERT_EQ(MOJO_RESULT_OK, wait_set->RemoveWaitingDispatcher(dispatcher1_)); - - // No ready dispatcher. - hss = HandleSignalsState(); - EXPECT_EQ(MOJO_RESULT_OK, - wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); - EXPECT_FALSE(hss.satisfies(MOJO_HANDLE_SIGNAL_READABLE)); - EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, w.Wait(0, nullptr)); - EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, - GetOneReadyDispatcher(wait_set, &woken_dispatcher, nullptr)); - - // Write to |dispatcher1_|, which should make |dispatcher0_| readable. - char buffer[] = "abcd"; - w.Init(); - WriteMessage(dispatcher1_.get(), buffer, sizeof(buffer)); - EXPECT_EQ(MOJO_RESULT_OK, w.Wait(MOJO_DEADLINE_INDEFINITE, nullptr)); - woken_dispatcher = nullptr; - context = 0; - EXPECT_EQ(MOJO_RESULT_OK, - GetOneReadyDispatcher(wait_set, &woken_dispatcher, &context)); - EXPECT_EQ(dispatcher0_, woken_dispatcher); - EXPECT_EQ(1u, context); - - // Again, if a ready dispatcher isn't removed, it will continue to be - // returned. - woken_dispatcher = nullptr; - EXPECT_EQ(MOJO_RESULT_OK, - GetOneReadyDispatcher(wait_set, &woken_dispatcher, nullptr)); - EXPECT_EQ(dispatcher0_, woken_dispatcher); - - wait_set->RemoveAwakable(&w, nullptr); -} - -TEST_F(WaitSetDispatcherTest, HandleWithoutRemoving) { - scoped_refptr<WaitSetDispatcher> wait_set = new WaitSetDispatcher(); - CloseOnShutdown(wait_set); - ASSERT_EQ(MOJO_RESULT_OK, - wait_set->AddWaitingDispatcher(dispatcher0_, - MOJO_HANDLE_SIGNAL_READABLE, 1)); - - Waiter w; - uintptr_t context = 0; - w.Init(); - HandleSignalsState hss; - // No ready dispatcher. - hss = HandleSignalsState(); - EXPECT_EQ(MOJO_RESULT_OK, - wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); - EXPECT_FALSE(hss.satisfies(MOJO_HANDLE_SIGNAL_READABLE)); - EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, w.Wait(0, nullptr)); - scoped_refptr<Dispatcher> woken_dispatcher; - EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, - GetOneReadyDispatcher(wait_set, &woken_dispatcher, nullptr)); - - // The tested behaviour below should be repeatable. - for (size_t i = 0; i < 3; i++) { - // Write to |dispatcher1_|, which should make |dispatcher0_| readable. - char buffer[] = "abcd"; - w.Init(); - WriteMessage(dispatcher1_.get(), buffer, sizeof(buffer)); - EXPECT_EQ(MOJO_RESULT_OK, w.Wait(MOJO_DEADLINE_INDEFINITE, nullptr)); - woken_dispatcher = nullptr; - context = 0; - EXPECT_EQ(MOJO_RESULT_OK, - GetOneReadyDispatcher(wait_set, &woken_dispatcher, &context)); - EXPECT_EQ(dispatcher0_, woken_dispatcher); - EXPECT_EQ(1u, context); - - // Read from |dispatcher0_| which should change it's state to non-readable. - char read_buffer[sizeof(buffer) + 5]; - uint32_t num_bytes = sizeof(read_buffer); - ReadMessage(dispatcher0_.get(), read_buffer, &num_bytes); - EXPECT_EQ(sizeof(buffer), num_bytes); - - // No dispatchers are ready. - w.Init(); - woken_dispatcher = nullptr; - context = 0; - EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, - GetOneReadyDispatcher(wait_set, &woken_dispatcher, &context)); - EXPECT_FALSE(woken_dispatcher); - EXPECT_EQ(0u, context); - EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, w.Wait(0, nullptr)); - } - - wait_set->RemoveAwakable(&w, nullptr); -} - -TEST_F(WaitSetDispatcherTest, MultipleReady) { - scoped_refptr<WaitSetDispatcher> wait_set = new WaitSetDispatcher(); - CloseOnShutdown(wait_set); - - scoped_refptr<MessagePipeDispatcher> mp1_dispatcher0; - scoped_refptr<MessagePipeDispatcher> mp1_dispatcher1; - CreateMessagePipe(&mp1_dispatcher0, &mp1_dispatcher1); - - ASSERT_EQ(MOJO_RESULT_OK, - wait_set->AddWaitingDispatcher(dispatcher0_, - MOJO_HANDLE_SIGNAL_READABLE, 0)); - ASSERT_EQ(MOJO_RESULT_OK, - wait_set->AddWaitingDispatcher(dispatcher1_, - MOJO_HANDLE_SIGNAL_WRITABLE, 0)); - ASSERT_EQ(MOJO_RESULT_OK, - wait_set->AddWaitingDispatcher(mp1_dispatcher0, - MOJO_HANDLE_SIGNAL_WRITABLE, 0)); - ASSERT_EQ(MOJO_RESULT_OK, - wait_set->AddWaitingDispatcher(mp1_dispatcher1, - MOJO_HANDLE_SIGNAL_WRITABLE, 0)); - - Waiter w; - w.Init(); - HandleSignalsState hss; - // The three writable dispatchers should be ready. - EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, - wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals); - - scoped_refptr<Dispatcher> woken_dispatcher; - EXPECT_EQ(MOJO_RESULT_OK, - GetOneReadyDispatcher(wait_set, &woken_dispatcher, nullptr)); - // Don't know which dispatcher was returned, just that it was one of the - // writable ones. - EXPECT_TRUE(woken_dispatcher == dispatcher1_ || - woken_dispatcher == mp1_dispatcher0 || - woken_dispatcher == mp1_dispatcher1); - - DispatcherVector dispatchers_vector; - uint32_t count = 4; - MojoResult results[4]; - EXPECT_EQ(MOJO_RESULT_OK, - wait_set->GetReadyDispatchers(&count, - &dispatchers_vector, - results, - nullptr)); - EXPECT_EQ(3u, count); - std::sort(dispatchers_vector.begin(), dispatchers_vector.end()); - DispatcherVector expected_dispatchers; - expected_dispatchers.push_back(dispatcher1_); - expected_dispatchers.push_back(mp1_dispatcher0); - expected_dispatchers.push_back(mp1_dispatcher1); - std::sort(expected_dispatchers.begin(), expected_dispatchers.end()); - EXPECT_EQ(expected_dispatchers, dispatchers_vector); - - // If a ready dispatcher isn't removed, it will continue to be returned. - EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, - wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals); - count = 4; - dispatchers_vector.clear(); - EXPECT_EQ(MOJO_RESULT_OK, - wait_set->GetReadyDispatchers(&count, - &dispatchers_vector, - results, - nullptr)); - EXPECT_EQ(3u, count); - std::sort(dispatchers_vector.begin(), dispatchers_vector.end()); - EXPECT_EQ(expected_dispatchers, dispatchers_vector); - - // Remove one. It shouldn't be returned any longer. - ASSERT_EQ(MOJO_RESULT_OK, - wait_set->RemoveWaitingDispatcher(expected_dispatchers.back())); - expected_dispatchers.pop_back(); - EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, - wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals); - count = 4; - dispatchers_vector.clear(); - EXPECT_EQ(MOJO_RESULT_OK, - wait_set->GetReadyDispatchers(&count, - &dispatchers_vector, - results, - nullptr)); - EXPECT_EQ(2u, count); - std::sort(dispatchers_vector.begin(), dispatchers_vector.end()); - EXPECT_EQ(expected_dispatchers, dispatchers_vector); - - // Write to |dispatcher1_|, which should make |dispatcher0_| readable. - char buffer[] = "abcd"; - w.Init(); - WriteMessage(dispatcher1_.get(), buffer, sizeof(buffer)); - { - Waiter mp_w; - mp_w.Init(); - // Wait for |dispatcher0_| to be readable. - if (dispatcher0_->AddAwakable(&mp_w, MOJO_HANDLE_SIGNAL_READABLE, 0, - nullptr) == MOJO_RESULT_OK) { - EXPECT_EQ(MOJO_RESULT_OK, mp_w.Wait(MOJO_DEADLINE_INDEFINITE, 0)); - dispatcher0_->RemoveAwakable(&mp_w, nullptr); - } - } - expected_dispatchers.push_back(dispatcher0_); - std::sort(expected_dispatchers.begin(), expected_dispatchers.end()); - EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, - wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals); - count = 4; - dispatchers_vector.clear(); - EXPECT_EQ(MOJO_RESULT_OK, - wait_set->GetReadyDispatchers(&count, - &dispatchers_vector, - results, - nullptr)); - EXPECT_EQ(3u, count); - std::sort(dispatchers_vector.begin(), dispatchers_vector.end()); - EXPECT_EQ(expected_dispatchers, dispatchers_vector); -} - -TEST_F(WaitSetDispatcherTest, InvalidParams) { - scoped_refptr<WaitSetDispatcher> wait_set = new WaitSetDispatcher(); - - // Can't add a wait set to itself. - EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - wait_set->AddWaitingDispatcher(wait_set, - MOJO_HANDLE_SIGNAL_READABLE, 0)); - - // Can't add twice. - EXPECT_EQ(MOJO_RESULT_OK, - wait_set->AddWaitingDispatcher(dispatcher0_, - MOJO_HANDLE_SIGNAL_READABLE, 0)); - EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, - wait_set->AddWaitingDispatcher(dispatcher0_, - MOJO_HANDLE_SIGNAL_READABLE, 0)); - - // Remove a dispatcher that wasn't added. - EXPECT_EQ(MOJO_RESULT_NOT_FOUND, - wait_set->RemoveWaitingDispatcher(dispatcher1_)); - - // Add to a closed wait set. - wait_set->Close(); - EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - wait_set->AddWaitingDispatcher(dispatcher0_, - MOJO_HANDLE_SIGNAL_READABLE, 0)); -} - -TEST_F(WaitSetDispatcherTest, NotSatisfiable) { - scoped_refptr<WaitSetDispatcher> wait_set = new WaitSetDispatcher(); - CloseOnShutdown(wait_set); - - // Wait sets can only satisfy MOJO_HANDLE_SIGNAL_READABLE. - Waiter w; - w.Init(); - HandleSignalsState hss; - EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss)); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_NONE, hss.satisfied_signals); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals); - - hss = HandleSignalsState(); - EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_PEER_CLOSED, 0, &hss)); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_NONE, hss.satisfied_signals); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals); -} - -TEST_F(WaitSetDispatcherTest, ClosedDispatchers) { - scoped_refptr<WaitSetDispatcher> wait_set = new WaitSetDispatcher(); - CloseOnShutdown(wait_set); - - Waiter w; - w.Init(); - HandleSignalsState hss; - // A dispatcher that was added and then closed will be cancelled. - ASSERT_EQ(MOJO_RESULT_OK, - wait_set->AddWaitingDispatcher(dispatcher0_, - MOJO_HANDLE_SIGNAL_READABLE, 0)); - EXPECT_EQ(MOJO_RESULT_OK, - wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss)); - dispatcher0_->Close(); - EXPECT_EQ(MOJO_RESULT_OK, w.Wait(MOJO_DEADLINE_INDEFINITE, nullptr)); - EXPECT_TRUE( - wait_set->GetHandleSignalsState().satisfies(MOJO_HANDLE_SIGNAL_READABLE)); - scoped_refptr<Dispatcher> woken_dispatcher; - EXPECT_EQ(MOJO_RESULT_CANCELLED, - GetOneReadyDispatcher(wait_set, &woken_dispatcher, nullptr)); - EXPECT_EQ(dispatcher0_, woken_dispatcher); - - // Dispatcher will be implicitly removed because it may be impossible to - // remove explicitly. - woken_dispatcher = nullptr; - EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, - GetOneReadyDispatcher(wait_set, &woken_dispatcher, nullptr)); - EXPECT_EQ(MOJO_RESULT_NOT_FOUND, - wait_set->RemoveWaitingDispatcher(dispatcher0_)); - - // A dispatcher that's not satisfiable should give an error. - w.Init(); - EXPECT_EQ(MOJO_RESULT_OK, - wait_set->AddWaitingDispatcher(dispatcher1_, - MOJO_HANDLE_SIGNAL_READABLE, 0)); - EXPECT_EQ(MOJO_RESULT_OK, w.Wait(MOJO_DEADLINE_INDEFINITE, nullptr)); - EXPECT_TRUE( - wait_set->GetHandleSignalsState().satisfies(MOJO_HANDLE_SIGNAL_READABLE)); - EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - GetOneReadyDispatcher(wait_set, &woken_dispatcher, nullptr)); - EXPECT_EQ(dispatcher1_, woken_dispatcher); - - wait_set->RemoveAwakable(&w, nullptr); -} - -TEST_F(WaitSetDispatcherTest, NestedSets) { - scoped_refptr<WaitSetDispatcher> wait_set = new WaitSetDispatcher(); - CloseOnShutdown(wait_set); - scoped_refptr<WaitSetDispatcher> nested_wait_set = new WaitSetDispatcher(); - CloseOnShutdown(nested_wait_set); - - Waiter w; - w.Init(); - EXPECT_EQ(MOJO_RESULT_OK, - wait_set->AddWaitingDispatcher(nested_wait_set, - MOJO_HANDLE_SIGNAL_READABLE, 0)); - EXPECT_EQ(MOJO_RESULT_OK, - wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, nullptr)); - EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, w.Wait(0, nullptr)); - - // Writable signal is immediately satisfied by the message pipe. - w.Init(); - EXPECT_EQ(MOJO_RESULT_OK, - nested_wait_set->AddWaitingDispatcher( - dispatcher0_, MOJO_HANDLE_SIGNAL_WRITABLE, 0)); - EXPECT_EQ(MOJO_RESULT_OK, w.Wait(0, nullptr)); - scoped_refptr<Dispatcher> woken_dispatcher; - EXPECT_EQ(MOJO_RESULT_OK, - GetOneReadyDispatcher(wait_set, &woken_dispatcher, nullptr)); - EXPECT_EQ(nested_wait_set, woken_dispatcher); - - wait_set->RemoveAwakable(&w, nullptr); -} - -} // namespace -} // namespace edk -} // namespace mojo diff --git a/mojo/edk/system/waiter.cc b/mojo/edk/system/waiter.cc deleted file mode 100644 index d98f3c6..0000000 --- a/mojo/edk/system/waiter.cc +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "mojo/edk/system/waiter.h" - -#include <stdint.h> - -#include <limits> - -#include "base/logging.h" -#include "base/time/time.h" - -namespace mojo { -namespace edk { - -Waiter::Waiter() - : cv_(&lock_), -#if DCHECK_IS_ON() - initialized_(false), -#endif - awoken_(false), - awake_result_(MOJO_RESULT_INTERNAL), - awake_context_(static_cast<uint32_t>(-1)) { -} - -Waiter::~Waiter() { -} - -void Waiter::Init() { -#if DCHECK_IS_ON() - initialized_ = true; -#endif - awoken_ = false; - // NOTE(vtl): If performance ever becomes an issue, we can disable the setting - // of |awake_result_| (except the first one in |Awake()|) in Release builds. - awake_result_ = MOJO_RESULT_INTERNAL; -} - -// TODO(vtl): Fast-path the |deadline == 0| case? -MojoResult Waiter::Wait(MojoDeadline deadline, uintptr_t* context) { - base::AutoLock locker(lock_); - -#if DCHECK_IS_ON() - DCHECK(initialized_); - // It'll need to be re-initialized after this. - initialized_ = false; -#endif - - // Fast-path the already-awoken case: - if (awoken_) { - DCHECK_NE(awake_result_, MOJO_RESULT_INTERNAL); - if (context) - *context = awake_context_; - return awake_result_; - } - - // |MojoDeadline| is actually a |uint64_t|, but we need a signed quantity. - // Treat any out-of-range deadline as "forever" (which is wrong, but okay - // since 2^63 microseconds is ~300000 years). Note that this also takes care - // of the |MOJO_DEADLINE_INDEFINITE| (= 2^64 - 1) case. - if (deadline > static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) { - do { - cv_.Wait(); - } while (!awoken_); - } else { - // NOTE(vtl): This is very inefficient on POSIX, since pthreads condition - // variables take an absolute deadline. - const base::TimeTicks end_time = - base::TimeTicks::Now() + - base::TimeDelta::FromMicroseconds(static_cast<int64_t>(deadline)); - do { - base::TimeTicks now_time = base::TimeTicks::Now(); - if (now_time >= end_time) - return MOJO_RESULT_DEADLINE_EXCEEDED; - - cv_.TimedWait(end_time - now_time); - } while (!awoken_); - } - - DCHECK_NE(awake_result_, MOJO_RESULT_INTERNAL); - if (context) - *context = awake_context_; - return awake_result_; -} - -bool Waiter::Awake(MojoResult result, uintptr_t context) { - base::AutoLock locker(lock_); - - if (awoken_) - return true; - - awoken_ = true; - awake_result_ = result; - awake_context_ = context; - cv_.Signal(); - // |cv_.Wait()|/|cv_.TimedWait()| will return after |lock_| is released. - return true; -} - -} // namespace edk -} // namespace mojo diff --git a/mojo/edk/system/waiter.h b/mojo/edk/system/waiter.h deleted file mode 100644 index 897ecbe..0000000 --- a/mojo/edk/system/waiter.h +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MOJO_EDK_SYSTEM_WAITER_H_ -#define MOJO_EDK_SYSTEM_WAITER_H_ - -#include <stdint.h> - -#include "base/logging.h" -#include "base/macros.h" -#include "base/synchronization/condition_variable.h" -#include "base/synchronization/lock.h" -#include "mojo/edk/system/awakable.h" -#include "mojo/edk/system/system_impl_export.h" -#include "mojo/public/c/system/types.h" - -namespace mojo { -namespace edk { - -// IMPORTANT (all-caps gets your attention, right?): |Waiter| methods are called -// under other locks, in particular, |Dispatcher::lock_|s, so |Waiter| methods -// must never call out to other objects (in particular, |Dispatcher|s). This -// class is thread-safe. -class MOJO_SYSTEM_IMPL_EXPORT Waiter final : public Awakable { - public: - Waiter(); - ~Waiter(); - - // A |Waiter| can be used multiple times; |Init()| should be called before - // each time it's used. - void Init(); - - // Waits until a suitable |Awake()| is called. (|context| may be null, in - // which case, obviously no context is ever returned.) - // Returns: - // - The result given to the first call to |Awake()| (possibly before this - // call to |Wait()|); in this case, |*context| is set to the value passed - // to that call to |Awake()|. - // - |MOJO_RESULT_DEADLINE_EXCEEDED| if the deadline was exceeded; in this - // case |*context| is not modified. - // - // Usually, the context passed to |Awake()| will be the value passed to - // |Dispatcher::AddAwakable()|, which is usually the index to the array of - // handles passed to |MojoWaitMany()| (or 0 for |MojoWait()|). - // - // Typical |Awake()| results are: - // - |MOJO_RESULT_OK| if one of the flags passed to - // |MojoWait()|/|MojoWaitMany()| (hence |Dispatcher::AddAwakable()|) was - // satisfied; - // - |MOJO_RESULT_CANCELLED| if a handle (on which - // |MojoWait()|/|MojoWaitMany()| was called) was closed (hence the - // dispatcher closed); and - // - |MOJO_RESULT_FAILED_PRECONDITION| if one of the set of flags passed to - // |MojoWait()|/|MojoWaitMany()| cannot or can no longer be satisfied by - // the corresponding handle (e.g., if the other end of a message or data - // pipe is closed). - MojoResult Wait(MojoDeadline deadline, uintptr_t* context); - - // Wake the waiter up with the given result and context (or no-op if it's been - // woken up already). - bool Awake(MojoResult result, uintptr_t context) override; - - private: - base::Lock lock_; // Protects the following members. - base::ConditionVariable cv_; // Associated to |lock_|. -#if DCHECK_IS_ON() - bool initialized_; -#endif - bool awoken_; - MojoResult awake_result_; - uintptr_t awake_context_; - - DISALLOW_COPY_AND_ASSIGN(Waiter); -}; - -} // namespace edk -} // namespace mojo - -#endif // MOJO_EDK_SYSTEM_WAITER_H_ diff --git a/mojo/edk/system/waiter_test_utils.cc b/mojo/edk/system/waiter_test_utils.cc deleted file mode 100644 index c7681e9..0000000 --- a/mojo/edk/system/waiter_test_utils.cc +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "mojo/edk/system/waiter_test_utils.h" - -namespace mojo { -namespace edk { -namespace test { - -SimpleWaiterThread::SimpleWaiterThread(MojoResult* result, uintptr_t* context) - : base::SimpleThread("waiter_thread"), result_(result), context_(context) { - waiter_.Init(); - *result_ = 5420734; // Totally invalid result. - *context_ = 23489023; // "Random". -} - -SimpleWaiterThread::~SimpleWaiterThread() { - Join(); -} - -void SimpleWaiterThread::Run() { - *result_ = waiter_.Wait(MOJO_DEADLINE_INDEFINITE, context_); -} - -WaiterThread::WaiterThread(scoped_refptr<Dispatcher> dispatcher, - MojoHandleSignals handle_signals, - MojoDeadline deadline, - uintptr_t context, - bool* did_wait_out, - MojoResult* result_out, - uintptr_t* context_out, - HandleSignalsState* signals_state_out) - : base::SimpleThread("waiter_thread"), - dispatcher_(dispatcher), - handle_signals_(handle_signals), - deadline_(deadline), - context_(context), - did_wait_out_(did_wait_out), - result_out_(result_out), - context_out_(context_out), - signals_state_out_(signals_state_out) { - *did_wait_out_ = false; - // Initialize these with invalid results (so that we'll be sure to catch any - // case where they're not set). - *result_out_ = 8542346; - *context_out_ = 89023444; - *signals_state_out_ = HandleSignalsState(~0u, ~0u); -} - -WaiterThread::~WaiterThread() { - Join(); -} - -void WaiterThread::Run() { - waiter_.Init(); - - *result_out_ = dispatcher_->AddAwakable(&waiter_, handle_signals_, context_, - signals_state_out_); - if (*result_out_ != MOJO_RESULT_OK) - return; - - *did_wait_out_ = true; - *result_out_ = waiter_.Wait(deadline_, context_out_); - dispatcher_->RemoveAwakable(&waiter_, signals_state_out_); -} - -} // namespace test -} // namespace edk -} // namespace mojo diff --git a/mojo/edk/system/waiter_test_utils.h b/mojo/edk/system/waiter_test_utils.h deleted file mode 100644 index eda1396..0000000 --- a/mojo/edk/system/waiter_test_utils.h +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MOJO_EDK_SYSTEM_WAITER_TEST_UTILS_H_ -#define MOJO_EDK_SYSTEM_WAITER_TEST_UTILS_H_ - -#include <stdint.h> - -#include "base/macros.h" -#include "base/memory/ref_counted.h" -#include "base/threading/simple_thread.h" -#include "mojo/edk/system/dispatcher.h" -#include "mojo/edk/system/handle_signals_state.h" -#include "mojo/edk/system/waiter.h" -#include "mojo/public/c/system/types.h" - -namespace mojo { -namespace edk { -namespace test { - -// This is a very simple thread that has a |Waiter|, on which it waits -// indefinitely (and records the result). It will create and initialize the -// |Waiter| on creation, but the caller must start the thread with |Start()|. It -// will join the thread on destruction. -// -// One usually uses it like: -// -// MojoResult result; -// { -// AwakableList awakable_list; -// test::SimpleWaiterThread thread(&result); -// awakable_list.Add(thread.waiter(), ...); -// thread.Start(); -// ... some stuff to wake the waiter ... -// awakable_list.Remove(thread.waiter()); -// } // Join |thread|. -// EXPECT_EQ(..., result); -// -// There's a bit of unrealism in its use: In this sort of usage, calls such as -// |Waiter::Init()|, |AddAwakable()|, and |RemoveAwakable()| are done in the -// main (test) thread, not the waiter thread (as would actually happen in real -// code). (We accept this unrealism for simplicity, since |AwakableList| is -// thread-unsafe so making it more realistic would require adding nontrivial -// synchronization machinery.) -class SimpleWaiterThread : public base::SimpleThread { - public: - // For the duration of the lifetime of this object, |*result| belongs to it - // (in the sense that it will write to it whenever it wants). - SimpleWaiterThread(MojoResult* result, uintptr_t* context); - ~SimpleWaiterThread() override; // Joins the thread. - - Waiter* waiter() { return &waiter_; } - - private: - void Run() override; - - MojoResult* const result_; - uintptr_t* const context_; - Waiter waiter_; - - DISALLOW_COPY_AND_ASSIGN(SimpleWaiterThread); -}; - -// This is a more complex and realistic thread that has a |Waiter|, on which it -// waits for the given deadline (with the given flags). Unlike -// |SimpleWaiterThread|, it requires the machinery of |Dispatcher|. -class WaiterThread : public base::SimpleThread { - public: - // Note: |*did_wait_out|, |*result_out|, |*context_out| and - // |*signals_state_out| "belong" to this object (i.e., may be modified by, on - // some other thread) while it's alive. - WaiterThread(scoped_refptr<Dispatcher> dispatcher, - MojoHandleSignals handle_signals, - MojoDeadline deadline, - uintptr_t context, - bool* did_wait_out, - MojoResult* result_out, - uintptr_t* context_out, - HandleSignalsState* signals_state_out); - ~WaiterThread() override; - - private: - void Run() override; - - const scoped_refptr<Dispatcher> dispatcher_; - const MojoHandleSignals handle_signals_; - const MojoDeadline deadline_; - const uint32_t context_; - bool* const did_wait_out_; - MojoResult* const result_out_; - uintptr_t* const context_out_; - HandleSignalsState* const signals_state_out_; - - Waiter waiter_; - - DISALLOW_COPY_AND_ASSIGN(WaiterThread); -}; - -} // namespace test -} // namespace edk -} // namespace mojo - -#endif // MOJO_EDK_SYSTEM_WAITER_TEST_UTILS_H_ diff --git a/mojo/edk/system/waiter_unittest.cc b/mojo/edk/system/waiter_unittest.cc deleted file mode 100644 index aa928ff..0000000 --- a/mojo/edk/system/waiter_unittest.cc +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// NOTE(vtl): Some of these tests are inherently flaky (e.g., if run on a -// heavily-loaded system). Sorry. |test::EpsilonDeadline()| may be increased to -// increase tolerance and reduce observed flakiness (though doing so reduces the -// meaningfulness of the test). - -#include "mojo/edk/system/waiter.h" - -#include <stdint.h> - -#include "base/macros.h" -#include "base/synchronization/lock.h" -#include "base/threading/simple_thread.h" -#include "mojo/edk/system/test_utils.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace mojo { -namespace edk { -namespace { - -const unsigned kPollTimeMs = 10; - -class WaitingThread : public base::SimpleThread { - public: - explicit WaitingThread(MojoDeadline deadline) - : base::SimpleThread("waiting_thread"), - deadline_(deadline), - done_(false), - result_(MOJO_RESULT_UNKNOWN), - context_(static_cast<uintptr_t>(-1)) { - waiter_.Init(); - } - - ~WaitingThread() override { Join(); } - - void WaitUntilDone(MojoResult* result, - uintptr_t* context, - MojoDeadline* elapsed) { - for (;;) { - { - base::AutoLock locker(lock_); - if (done_) { - *result = result_; - *context = context_; - *elapsed = elapsed_; - break; - } - } - - test::Sleep(test::DeadlineFromMilliseconds(kPollTimeMs)); - } - } - - Waiter* waiter() { return &waiter_; } - - private: - void Run() override { - test::Stopwatch stopwatch; - MojoResult result; - uintptr_t context = static_cast<uintptr_t>(-1); - MojoDeadline elapsed; - - stopwatch.Start(); - result = waiter_.Wait(deadline_, &context); - elapsed = stopwatch.Elapsed(); - - { - base::AutoLock locker(lock_); - done_ = true; - result_ = result; - context_ = context; - elapsed_ = elapsed; - } - } - - const MojoDeadline deadline_; - Waiter waiter_; // Thread-safe. - - base::Lock lock_; // Protects the following members. - bool done_; - MojoResult result_; - uintptr_t context_; - MojoDeadline elapsed_; - - DISALLOW_COPY_AND_ASSIGN(WaitingThread); -}; - -TEST(WaiterTest, Basic) { - MojoResult result; - uintptr_t context; - MojoDeadline elapsed; - - // Finite deadline. - - // Awake immediately after thread start. - { - WaitingThread thread(10 * test::EpsilonDeadline()); - thread.Start(); - thread.waiter()->Awake(MOJO_RESULT_OK, 1); - thread.WaitUntilDone(&result, &context, &elapsed); - EXPECT_EQ(MOJO_RESULT_OK, result); - EXPECT_EQ(1u, context); - EXPECT_LT(elapsed, test::EpsilonDeadline()); - } - - // Awake before after thread start. - { - WaitingThread thread(10 * test::EpsilonDeadline()); - thread.waiter()->Awake(MOJO_RESULT_CANCELLED, 2); - thread.Start(); - thread.WaitUntilDone(&result, &context, &elapsed); - EXPECT_EQ(MOJO_RESULT_CANCELLED, result); - EXPECT_EQ(2u, context); - EXPECT_LT(elapsed, test::EpsilonDeadline()); - } - - // Awake some time after thread start. - { - WaitingThread thread(10 * test::EpsilonDeadline()); - thread.Start(); - test::Sleep(2 * test::EpsilonDeadline()); - thread.waiter()->Awake(1, 3); - thread.WaitUntilDone(&result, &context, &elapsed); - EXPECT_EQ(1u, result); - EXPECT_EQ(3u, context); - EXPECT_GT(elapsed, (2 - 1) * test::EpsilonDeadline()); - EXPECT_LT(elapsed, (2 + 1) * test::EpsilonDeadline()); - } - - // Awake some longer time after thread start. - { - WaitingThread thread(10 * test::EpsilonDeadline()); - thread.Start(); - test::Sleep(5 * test::EpsilonDeadline()); - thread.waiter()->Awake(2, 4); - thread.WaitUntilDone(&result, &context, &elapsed); - EXPECT_EQ(2u, result); - EXPECT_EQ(4u, context); - EXPECT_GT(elapsed, (5 - 1) * test::EpsilonDeadline()); - EXPECT_LT(elapsed, (5 + 1) * test::EpsilonDeadline()); - } - - // Don't awake -- time out (on another thread). - { - WaitingThread thread(2 * test::EpsilonDeadline()); - thread.Start(); - thread.WaitUntilDone(&result, &context, &elapsed); - EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, result); - EXPECT_EQ(static_cast<uintptr_t>(-1), context); - EXPECT_GT(elapsed, (2 - 1) * test::EpsilonDeadline()); - EXPECT_LT(elapsed, (2 + 1) * test::EpsilonDeadline()); - } - - // No (indefinite) deadline. - - // Awake immediately after thread start. - { - WaitingThread thread(MOJO_DEADLINE_INDEFINITE); - thread.Start(); - thread.waiter()->Awake(MOJO_RESULT_OK, 5); - thread.WaitUntilDone(&result, &context, &elapsed); - EXPECT_EQ(MOJO_RESULT_OK, result); - EXPECT_EQ(5u, context); - EXPECT_LT(elapsed, test::EpsilonDeadline()); - } - - // Awake before after thread start. - { - WaitingThread thread(MOJO_DEADLINE_INDEFINITE); - thread.waiter()->Awake(MOJO_RESULT_CANCELLED, 6); - thread.Start(); - thread.WaitUntilDone(&result, &context, &elapsed); - EXPECT_EQ(MOJO_RESULT_CANCELLED, result); - EXPECT_EQ(6u, context); - EXPECT_LT(elapsed, test::EpsilonDeadline()); - } - - // Awake some time after thread start. - { - WaitingThread thread(MOJO_DEADLINE_INDEFINITE); - thread.Start(); - test::Sleep(2 * test::EpsilonDeadline()); - thread.waiter()->Awake(1, 7); - thread.WaitUntilDone(&result, &context, &elapsed); - EXPECT_EQ(1u, result); - EXPECT_EQ(7u, context); - EXPECT_GT(elapsed, (2 - 1) * test::EpsilonDeadline()); - EXPECT_LT(elapsed, (2 + 1) * test::EpsilonDeadline()); - } - - // Awake some longer time after thread start. - { - WaitingThread thread(MOJO_DEADLINE_INDEFINITE); - thread.Start(); - test::Sleep(5 * test::EpsilonDeadline()); - thread.waiter()->Awake(2, 8); - thread.WaitUntilDone(&result, &context, &elapsed); - EXPECT_EQ(2u, result); - EXPECT_EQ(8u, context); - EXPECT_GT(elapsed, (5 - 1) * test::EpsilonDeadline()); - EXPECT_LT(elapsed, (5 + 1) * test::EpsilonDeadline()); - } -} - -TEST(WaiterTest, TimeOut) { - test::Stopwatch stopwatch; - MojoDeadline elapsed; - - Waiter waiter; - uintptr_t context = 123; - - waiter.Init(); - stopwatch.Start(); - EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, &context)); - elapsed = stopwatch.Elapsed(); - EXPECT_LT(elapsed, test::EpsilonDeadline()); - EXPECT_EQ(123u, context); - - waiter.Init(); - stopwatch.Start(); - EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, - waiter.Wait(2 * test::EpsilonDeadline(), &context)); - elapsed = stopwatch.Elapsed(); - EXPECT_GT(elapsed, (2 - 1) * test::EpsilonDeadline()); - EXPECT_LT(elapsed, (2 + 1) * test::EpsilonDeadline()); - EXPECT_EQ(123u, context); - - waiter.Init(); - stopwatch.Start(); - EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, - waiter.Wait(5 * test::EpsilonDeadline(), &context)); - elapsed = stopwatch.Elapsed(); - EXPECT_GT(elapsed, (5 - 1) * test::EpsilonDeadline()); - EXPECT_LT(elapsed, (5 + 1) * test::EpsilonDeadline()); - EXPECT_EQ(123u, context); -} - -// The first |Awake()| should always win. -TEST(WaiterTest, MultipleAwakes) { - MojoResult result; - uintptr_t context; - MojoDeadline elapsed; - - { - WaitingThread thread(MOJO_DEADLINE_INDEFINITE); - thread.Start(); - thread.waiter()->Awake(MOJO_RESULT_OK, 1); - thread.waiter()->Awake(1, 2); - thread.WaitUntilDone(&result, &context, &elapsed); - EXPECT_EQ(MOJO_RESULT_OK, result); - EXPECT_EQ(1u, context); - EXPECT_LT(elapsed, test::EpsilonDeadline()); - } - - { - WaitingThread thread(MOJO_DEADLINE_INDEFINITE); - thread.waiter()->Awake(1, 3); - thread.Start(); - thread.waiter()->Awake(MOJO_RESULT_OK, 4); - thread.WaitUntilDone(&result, &context, &elapsed); - EXPECT_EQ(1u, result); - EXPECT_EQ(3u, context); - EXPECT_LT(elapsed, test::EpsilonDeadline()); - } - - { - WaitingThread thread(MOJO_DEADLINE_INDEFINITE); - thread.Start(); - thread.waiter()->Awake(10, 5); - test::Sleep(2 * test::EpsilonDeadline()); - thread.waiter()->Awake(20, 6); - thread.WaitUntilDone(&result, &context, &elapsed); - EXPECT_EQ(10u, result); - EXPECT_EQ(5u, context); - EXPECT_LT(elapsed, test::EpsilonDeadline()); - } - - { - WaitingThread thread(10 * test::EpsilonDeadline()); - thread.Start(); - test::Sleep(1 * test::EpsilonDeadline()); - thread.waiter()->Awake(MOJO_RESULT_FAILED_PRECONDITION, 7); - test::Sleep(2 * test::EpsilonDeadline()); - thread.waiter()->Awake(MOJO_RESULT_OK, 8); - thread.WaitUntilDone(&result, &context, &elapsed); - EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); - EXPECT_EQ(7u, context); - EXPECT_GT(elapsed, (1 - 1) * test::EpsilonDeadline()); - EXPECT_LT(elapsed, (1 + 1) * test::EpsilonDeadline()); - } -} - -} // namespace -} // namespace edk -} // namespace mojo diff --git a/mojo/edk/system/watch.cc b/mojo/edk/system/watch.cc new file mode 100644 index 0000000..cf08ac3 --- /dev/null +++ b/mojo/edk/system/watch.cc @@ -0,0 +1,83 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/system/watch.h" + +#include "mojo/edk/system/request_context.h" +#include "mojo/edk/system/watcher_dispatcher.h" + +namespace mojo { +namespace edk { + +Watch::Watch(const scoped_refptr<WatcherDispatcher>& watcher, + const scoped_refptr<Dispatcher>& dispatcher, + uintptr_t context, + MojoHandleSignals signals) + : watcher_(watcher), + dispatcher_(dispatcher), + context_(context), + signals_(signals) {} + +bool Watch::NotifyState(const HandleSignalsState& state, + bool allowed_to_call_callback) { + AssertWatcherLockAcquired(); + + // NOTE: This method must NEVER call into |dispatcher_| directly, because it + // may be called while |dispatcher_| holds a lock. + + MojoResult rv = MOJO_RESULT_SHOULD_WAIT; + RequestContext* const request_context = RequestContext::current(); + if (state.satisfies(signals_)) { + rv = MOJO_RESULT_OK; + if (allowed_to_call_callback && rv != last_known_result_) { + request_context->AddWatchNotifyFinalizer(this, MOJO_RESULT_OK, state); + } + } else if (!state.can_satisfy(signals_)) { + rv = MOJO_RESULT_FAILED_PRECONDITION; + if (allowed_to_call_callback && rv != last_known_result_) { + request_context->AddWatchNotifyFinalizer( + this, MOJO_RESULT_FAILED_PRECONDITION, state); + } + } + + last_known_signals_state_ = + *static_cast<const MojoHandleSignalsState*>(&state); + last_known_result_ = rv; + return ready(); +} + +void Watch::Cancel() { + RequestContext::current()->AddWatchCancelFinalizer(this); +} + +void Watch::InvokeCallback(MojoResult result, + const HandleSignalsState& state, + MojoWatcherNotificationFlags flags) { + // We hold the lock through invocation to ensure that only one notification + // callback runs for this context at any given time. + base::AutoLock lock(notification_lock_); + if (result == MOJO_RESULT_CANCELLED) { + // Make sure cancellation is the last notification we dispatch. + DCHECK(!is_cancelled_); + is_cancelled_ = true; + } else if (is_cancelled_) { + return; + } + + // NOTE: This will acquire |watcher_|'s internal lock. It's safe because a + // thread can only enter InvokeCallback() from within a RequestContext + // destructor where no dispatcher locks are held. + watcher_->InvokeWatchCallback(context_, result, state, flags); +} + +Watch::~Watch() {} + +#if DCHECK_IS_ON() +void Watch::AssertWatcherLockAcquired() const { + watcher_->lock_.AssertAcquired(); +} +#endif + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/watch.h b/mojo/edk/system/watch.h new file mode 100644 index 0000000..f277de9 --- /dev/null +++ b/mojo/edk/system/watch.h @@ -0,0 +1,124 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_SYSTEM_WATCH_H_ +#define MOJO_EDK_SYSTEM_WATCH_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/system/atomic_flag.h" +#include "mojo/edk/system/handle_signals_state.h" + +namespace mojo { +namespace edk { + +class Dispatcher; +class WatcherDispatcher; + +// Encapsulates the state associated with a single watch context within a +// watcher. +// +// Every Watch has its own cancellation state, and is captured by RequestContext +// notification finalizers to avoid redundant context resolution during +// finalizer execution. +class Watch : public base::RefCountedThreadSafe<Watch> { + public: + // Constructs a Watch which represents a watch within |watcher| associated + // with |context|, watching |dispatcher| for |signals|. + Watch(const scoped_refptr<WatcherDispatcher>& watcher, + const scoped_refptr<Dispatcher>& dispatcher, + uintptr_t context, + MojoHandleSignals signals); + + // Notifies the Watch of a potential state change. + // + // If |allowed_to_call_callback| is true, this may add a notification + // finalizer to the current RequestContext to invoke the watcher's callback + // with this watch's context. See return values below. + // + // This is called directly by WatcherDispatcher whenever the Watch's observed + // dispatcher notifies the WatcherDispatcher of a state change. + // + // Returns |true| if the Watch entered or remains in a ready state as a result + // of the state change. If |allowed_to_call_callback| was true in this case, + // the Watch will have also attached a notification finalizer to the current + // RequestContext. + // + // Returns |false| if the + bool NotifyState(const HandleSignalsState& state, + bool allowed_to_call_callback); + + // Notifies the watch of cancellation ASAP. This will always be the last + // notification sent for the watch. + void Cancel(); + + // Finalizer method for RequestContexts. This method is invoked once for every + // notification finalizer added to a RequestContext by this object. This calls + // down into the WatcherDispatcher to do the actual notification call. + void InvokeCallback(MojoResult result, + const HandleSignalsState& state, + MojoWatcherNotificationFlags flags); + + const scoped_refptr<Dispatcher>& dispatcher() const { return dispatcher_; } + uintptr_t context() const { return context_; } + + MojoResult last_known_result() const { + AssertWatcherLockAcquired(); + return last_known_result_; + } + + MojoHandleSignalsState last_known_signals_state() const { + AssertWatcherLockAcquired(); + return last_known_signals_state_; + } + + bool ready() const { + AssertWatcherLockAcquired(); + return last_known_result_ == MOJO_RESULT_OK || + last_known_result_ == MOJO_RESULT_FAILED_PRECONDITION; + } + + private: + friend class base::RefCountedThreadSafe<Watch>; + + ~Watch(); + +#if DCHECK_IS_ON() + void AssertWatcherLockAcquired() const; +#else + void AssertWatcherLockAcquired() const {} +#endif + + const scoped_refptr<WatcherDispatcher> watcher_; + const scoped_refptr<Dispatcher> dispatcher_; + const uintptr_t context_; + const MojoHandleSignals signals_; + + // The result code with which this Watch would notify if currently armed, + // based on the last known signaling state of |dispatcher_|. Guarded by the + // owning WatcherDispatcher's lock. + MojoResult last_known_result_ = MOJO_RESULT_UNKNOWN; + + // The last known signaling state of |dispatcher_|. Guarded by the owning + // WatcherDispatcher's lock. + MojoHandleSignalsState last_known_signals_state_ = {0, 0}; + + // Guards |is_cancelled_| below and mutually excludes individual watch + // notification executions for this same watch context. + // + // Note that this should only be acquired from a RequestContext finalizer to + // ensure that no other internal locks are already held. + base::Lock notification_lock_; + + // Guarded by |notification_lock_|. + bool is_cancelled_ = false; + + DISALLOW_COPY_AND_ASSIGN(Watch); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_WATCH_H_ diff --git a/mojo/edk/system/watch_unittest.cc b/mojo/edk/system/watch_unittest.cc deleted file mode 100644 index ec28d94..0000000 --- a/mojo/edk/system/watch_unittest.cc +++ /dev/null @@ -1,480 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include <functional> - -#include "base/macros.h" -#include "base/memory/ref_counted.h" -#include "base/message_loop/message_loop.h" -#include "base/run_loop.h" -#include "base/single_thread_task_runner.h" -#include "base/threading/thread_task_runner_handle.h" -#include "mojo/edk/system/request_context.h" -#include "mojo/edk/test/mojo_test_base.h" -#include "mojo/public/c/system/functions.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace mojo { -namespace edk { -namespace { - -void IgnoreResult(uintptr_t context, - MojoResult result, - MojoHandleSignalsState signals, - MojoWatchNotificationFlags flags) { -} - -// A test helper class for watching a handle. The WatchHelper instance is used -// as a watch context for a single watch callback. -class WatchHelper { - public: - using Callback = - std::function<void(MojoResult result, MojoHandleSignalsState state)>; - - WatchHelper() : task_runner_(base::ThreadTaskRunnerHandle::Get()) {} - ~WatchHelper() { - CHECK(!watching_); - } - - void Watch(MojoHandle handle, - MojoHandleSignals signals, - const Callback& callback) { - CHECK(!watching_); - - handle_ = handle; - callback_ = callback; - watching_ = true; - CHECK_EQ(MOJO_RESULT_OK, MojoWatch(handle_, signals, &WatchHelper::OnNotify, - reinterpret_cast<uintptr_t>(this))); - } - - bool is_watching() const { return watching_; } - - void Cancel() { - CHECK_EQ(MOJO_RESULT_OK, - MojoCancelWatch(handle_, reinterpret_cast<uintptr_t>(this))); - CHECK(watching_); - watching_ = false; - } - - private: - static void OnNotify(uintptr_t context, - MojoResult result, - MojoHandleSignalsState state, - MojoWatchNotificationFlags flags) { - WatchHelper* watcher = reinterpret_cast<WatchHelper*>(context); - watcher->task_runner_->PostTask( - FROM_HERE, - base::Bind(&NotifyOnMainThread, context, result, state, flags)); - } - - static void NotifyOnMainThread(uintptr_t context, - MojoResult result, - MojoHandleSignalsState state, - MojoWatchNotificationFlags flags) { - WatchHelper* watcher = reinterpret_cast<WatchHelper*>(context); - CHECK(watcher->watching_); - if (result == MOJO_RESULT_CANCELLED) - watcher->watching_ = false; - watcher->callback_(result, state); - } - - scoped_refptr<base::SingleThreadTaskRunner> task_runner_; - bool watching_ = false; - MojoHandle handle_; - Callback callback_; - - DISALLOW_COPY_AND_ASSIGN(WatchHelper); -}; - -class WatchTest : public test::MojoTestBase { - public: - WatchTest() {} - ~WatchTest() override {} - - protected: - - private: - base::MessageLoop message_loop_; - - DISALLOW_COPY_AND_ASSIGN(WatchTest); -}; - -TEST_F(WatchTest, NotifyBasic) { - MojoHandle a, b; - CreateMessagePipe(&a, &b); - - base::RunLoop loop; - WatchHelper b_watcher; - b_watcher.Watch( - b, MOJO_HANDLE_SIGNAL_READABLE, - [&] (MojoResult result, MojoHandleSignalsState state) { - EXPECT_EQ(MOJO_RESULT_OK, result); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, - state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE); - EXPECT_TRUE(b_watcher.is_watching()); - loop.Quit(); - }); - - WriteMessage(a, "Hello!"); - loop.Run(); - - EXPECT_TRUE(b_watcher.is_watching()); - b_watcher.Cancel(); - - CloseHandle(a); - CloseHandle(b); -} - -TEST_F(WatchTest, NotifyUnsatisfiable) { - MojoHandle a, b; - CreateMessagePipe(&a, &b); - - base::RunLoop loop; - WatchHelper b_watcher; - b_watcher.Watch( - b, MOJO_HANDLE_SIGNAL_READABLE, - [&] (MojoResult result, MojoHandleSignalsState state) { - EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); - EXPECT_EQ(0u, - state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE); - EXPECT_EQ(0u, - state.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE); - EXPECT_TRUE(b_watcher.is_watching()); - loop.Quit(); - }); - - CloseHandle(a); - loop.Run(); - - b_watcher.Cancel(); - - CloseHandle(b); -} - -TEST_F(WatchTest, NotifyCancellation) { - MojoHandle a, b; - CreateMessagePipe(&a, &b); - - base::RunLoop loop; - WatchHelper b_watcher; - b_watcher.Watch( - b, MOJO_HANDLE_SIGNAL_READABLE, - [&] (MojoResult result, MojoHandleSignalsState state) { - EXPECT_EQ(MOJO_RESULT_CANCELLED, result); - EXPECT_EQ(0u, state.satisfied_signals); - EXPECT_EQ(0u, state.satisfiable_signals); - EXPECT_FALSE(b_watcher.is_watching()); - loop.Quit(); - }); - - CloseHandle(b); - loop.Run(); - - CloseHandle(a); -} - -TEST_F(WatchTest, InvalidArguemnts) { - MojoHandle a, b; - CreateMessagePipe(&a, &b); - - uintptr_t context = reinterpret_cast<uintptr_t>(this); - EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(a, MOJO_HANDLE_SIGNAL_READABLE, - &IgnoreResult, context)); - - // Can't cancel a watch that doesn't exist. - EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoCancelWatch(a, ~context)); - EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoCancelWatch(b, context)); - - CloseHandle(a); - CloseHandle(b); - - // Can't watch a handle that doesn't exist. - EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - MojoWatch(a, MOJO_HANDLE_SIGNAL_READABLE, &IgnoreResult, context)); - EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - MojoWatch(b, MOJO_HANDLE_SIGNAL_READABLE, &IgnoreResult, context)); -} - -TEST_F(WatchTest, NoDuplicateContext) { - MojoHandle a, b; - CreateMessagePipe(&a, &b); - - // Try to add the same watch twice; should fail. - uintptr_t context = reinterpret_cast<uintptr_t>(this); - EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(a, MOJO_HANDLE_SIGNAL_READABLE, - &IgnoreResult, context)); - EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, - MojoWatch(a, MOJO_HANDLE_SIGNAL_READABLE, &IgnoreResult, context)); - - // Cancel and add it again; should be OK. - EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(a, context)); - EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(a, MOJO_HANDLE_SIGNAL_READABLE, - &IgnoreResult, context)); - - CloseHandle(a); - CloseHandle(b); -} - -TEST_F(WatchTest, MultipleWatches) { - MojoHandle a, b; - CreateMessagePipe(&a, &b); - - // Add multiple watchers to |b| and see that they are both notified by a - // single write to |a|. - base::RunLoop loop; - int expected_notifications = 2; - auto on_readable = [&] (MojoResult result, MojoHandleSignalsState state) { - EXPECT_EQ(MOJO_RESULT_OK, result); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, - state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE); - EXPECT_GT(expected_notifications, 0); - if (--expected_notifications == 0) - loop.Quit(); - }; - WatchHelper watcher1; - WatchHelper watcher2; - watcher1.Watch(b, MOJO_HANDLE_SIGNAL_READABLE, on_readable); - watcher2.Watch(b, MOJO_HANDLE_SIGNAL_READABLE, on_readable); - - WriteMessage(a, "Ping!"); - loop.Run(); - - watcher1.Cancel(); - watcher2.Cancel(); - - CloseHandle(a); - CloseHandle(b); -} - -TEST_F(WatchTest, WatchWhileSatisfied) { - MojoHandle a, b; - CreateMessagePipe(&a, &b); - - // Write to |a| and then start watching |b|. The callback should be invoked - // synchronously. - WriteMessage(a, "hey"); - bool signaled = false; - WatchHelper b_watcher; - base::RunLoop loop; - b_watcher.Watch( - b, MOJO_HANDLE_SIGNAL_READABLE, - [&] (MojoResult result, MojoHandleSignalsState state) { - EXPECT_EQ(MOJO_RESULT_OK, result); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, - state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE); - signaled = true; - loop.Quit(); - }); - loop.Run(); - EXPECT_TRUE(signaled); - b_watcher.Cancel(); - - CloseHandle(a); - CloseHandle(b); -} - -TEST_F(WatchTest, WatchWhileUnsatisfiable) { - MojoHandle a, b; - CreateMessagePipe(&a, &b); - - // Close |a| and then try to watch |b|. MojoWatch() should fail. - CloseHandle(a); - uintptr_t context = reinterpret_cast<uintptr_t>(this); - EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - MojoWatch(b, MOJO_HANDLE_SIGNAL_READABLE, &IgnoreResult, context)); - - CloseHandle(b); -} - -TEST_F(WatchTest, RespondFromCallback) { - MojoHandle a, b; - CreateMessagePipe(&a, &b); - - // Watch |a| and |b|. Write to |a|, then write to |b| from within the callback - // which notifies it of the available message. - const std::string kTestMessage = "hello worlds."; - base::RunLoop loop; - WatchHelper b_watcher; - b_watcher.Watch( - b, MOJO_HANDLE_SIGNAL_READABLE, - [&] (MojoResult result, MojoHandleSignalsState state) { - EXPECT_EQ(MOJO_RESULT_OK, result); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, - state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE); - EXPECT_TRUE(b_watcher.is_watching()); - - // Echo a's message back to it. - WriteMessage(b, ReadMessage(b)); - }); - - WatchHelper a_watcher; - a_watcher.Watch( - a, MOJO_HANDLE_SIGNAL_READABLE, - [&] (MojoResult result, MojoHandleSignalsState state) { - EXPECT_EQ(MOJO_RESULT_OK, result); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, - state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE); - EXPECT_TRUE(a_watcher.is_watching()); - - // Expect to receive back the message that was originally sent to |b|. - EXPECT_EQ(kTestMessage, ReadMessage(a)); - - loop.Quit(); - }); - - WriteMessage(a, kTestMessage); - loop.Run(); - - a_watcher.Cancel(); - b_watcher.Cancel(); - - CloseHandle(a); - CloseHandle(b); -} - -TEST_F(WatchTest, WatchDataPipeConsumer) { - MojoHandle a, b; - CreateDataPipe(&a, &b, 64); - - base::RunLoop loop; - WatchHelper b_watcher; - b_watcher.Watch( - b, MOJO_HANDLE_SIGNAL_READABLE, - [&] (MojoResult result, MojoHandleSignalsState state) { - EXPECT_EQ(MOJO_RESULT_OK, result); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, - state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE); - EXPECT_TRUE(b_watcher.is_watching()); - loop.Quit(); - }); - - WriteData(a, "Hello!"); - loop.Run(); - - EXPECT_TRUE(b_watcher.is_watching()); - b_watcher.Cancel(); - - CloseHandle(a); - CloseHandle(b); -} - -TEST_F(WatchTest, WatchDataPipeProducer) { - MojoHandle a, b; - CreateDataPipe(&a, &b, 8); - - // Fill the pipe to capacity so writes will block. - WriteData(a, "xxxxxxxx"); - - base::RunLoop loop; - WatchHelper a_watcher; - a_watcher.Watch( - a, MOJO_HANDLE_SIGNAL_WRITABLE, - [&] (MojoResult result, MojoHandleSignalsState state) { - EXPECT_EQ(MOJO_RESULT_OK, result); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, - state.satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); - EXPECT_TRUE(a_watcher.is_watching()); - loop.Quit(); - }); - - EXPECT_EQ("xxxxxxxx", ReadData(b, 8)); - loop.Run(); - - EXPECT_TRUE(a_watcher.is_watching()); - a_watcher.Cancel(); - - CloseHandle(a); - CloseHandle(b); -} - -TEST_F(WatchTest, WakeUpSelfWithinWatchCallback) { - MojoHandle a, b; - CreateMessagePipe(&a, &b); - - int expected_notifications = 2; - base::RunLoop loop; - WatchHelper b_watcher; - b_watcher.Watch( - b, MOJO_HANDLE_SIGNAL_READABLE, - [&] (MojoResult result, MojoHandleSignalsState state) { - EXPECT_EQ(MOJO_RESULT_OK, result); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, - state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE); - EXPECT_TRUE(b_watcher.is_watching()); - if (--expected_notifications == 0) { - loop.Quit(); - } else { - // Trigger b's watch again from within this callback. This should be - // safe to do. - WriteMessage(a, "hey"); - } - }); - - WriteMessage(a, "hey hey hey"); - loop.Run(); - - b_watcher.Cancel(); - - CloseHandle(a); - CloseHandle(b); -} - -TEST_F(WatchTest, NestedCancellation) { - // Verifies that cancellations in nested system request contexts preempt - // other notifications for the same watcher. This tests against the condition - // hit by http://crbug.com/622298. - - MojoHandle a, b, c, d; - CreateMessagePipe(&a, &b); - CreateMessagePipe(&c, &d); - - base::RunLoop loop; - bool a_watcher_run = false; - WatchHelper a_watcher; - a_watcher.Watch( - a, MOJO_HANDLE_SIGNAL_READABLE, - [&](MojoResult result, MojoHandleSignalsState state) { - a_watcher_run = true; - }); - - WatchHelper c_watcher; - c_watcher.Watch( - c, MOJO_HANDLE_SIGNAL_READABLE, - [&](MojoResult result, MojoHandleSignalsState state) { - // This will trigger a notification on |a_watcher| above to be executed - // once this handler finishes running... - CloseHandle(b); - - // ...but this should prevent that notification from dispatching because - // |a_watcher| is now cancelled. - a_watcher.Cancel(); - - loop.Quit(); - }); - - { - // Force "system" notifications for the synchronous behavior required to - // test this case. - mojo::edk::RequestContext request_context( - mojo::edk::RequestContext::Source::SYSTEM); - - // Trigger the |c_watcher| callback above. - CloseHandle(d); - } - - loop.Run(); - - EXPECT_FALSE(a_watcher.is_watching()); - EXPECT_FALSE(a_watcher_run); - - c_watcher.Cancel(); - - CloseHandle(a); - CloseHandle(c); -} - -} // namespace -} // namespace edk -} // namespace mojo diff --git a/mojo/edk/system/watcher.cc b/mojo/edk/system/watcher.cc deleted file mode 100644 index 25c2276..0000000 --- a/mojo/edk/system/watcher.cc +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "mojo/edk/system/watcher.h" - -#include "mojo/edk/system/handle_signals_state.h" -#include "mojo/edk/system/request_context.h" - -namespace mojo { -namespace edk { - -Watcher::Watcher(MojoHandleSignals signals, const WatchCallback& callback) - : signals_(signals), callback_(callback) { -} - -void Watcher::MaybeInvokeCallback(MojoResult result, - const HandleSignalsState& state, - MojoWatchNotificationFlags flags) { - base::AutoLock lock(lock_); - if (is_cancelled_) - return; - - callback_.Run(result, state, flags); -} - -void Watcher::NotifyForStateChange(const HandleSignalsState& signals_state) { - RequestContext* request_context = RequestContext::current(); - if (signals_state.satisfies(signals_)) { - request_context->AddWatchNotifyFinalizer( - make_scoped_refptr(this), MOJO_RESULT_OK, signals_state); - } else if (!signals_state.can_satisfy(signals_)) { - request_context->AddWatchNotifyFinalizer( - make_scoped_refptr(this), MOJO_RESULT_FAILED_PRECONDITION, - signals_state); - } -} - -void Watcher::NotifyClosed() { - static const HandleSignalsState closed_state = {0, 0}; - RequestContext::current()->AddWatchNotifyFinalizer( - make_scoped_refptr(this), MOJO_RESULT_CANCELLED, closed_state); -} - -void Watcher::Cancel() { - base::AutoLock lock(lock_); - is_cancelled_ = true; -} - -Watcher::~Watcher() {} - -} // namespace edk -} // namespace mojo diff --git a/mojo/edk/system/watcher.h b/mojo/edk/system/watcher.h deleted file mode 100644 index b6dc2e4..0000000 --- a/mojo/edk/system/watcher.h +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MOJO_EDK_SYSTEM_WATCHER_H_ -#define MOJO_EDK_SYSTEM_WATCHER_H_ - -#include "base/callback.h" -#include "base/macros.h" -#include "base/memory/ref_counted.h" -#include "base/synchronization/lock.h" -#include "mojo/public/c/system/functions.h" -#include "mojo/public/c/system/types.h" - -namespace mojo { -namespace edk { - -struct HandleSignalsState; - -// This object corresponds to a watch added by a single call to |MojoWatch()|. -// -// An event may occur at any time which should trigger a Watcher to run its -// callback, but the callback needs to be deferred until all EDK locks are -// released. At the same time, a watch may be cancelled at any time by -// |MojoCancelWatch()| and it is not OK for the callback to be invoked after -// that happens. -// -// Therefore a Watcher needs to have some associated thread-safe state to track -// its cancellation, which is why it's ref-counted. -class Watcher : public base::RefCountedThreadSafe<Watcher> { - public: - using WatchCallback = base::Callback<void(MojoResult, - const HandleSignalsState&, - MojoWatchNotificationFlags)>; - - // Constructs a new Watcher which watches for |signals| to be satisfied on a - // handle and which invokes |callback| either when one such signal is - // satisfied, or all such signals become unsatisfiable. - Watcher(MojoHandleSignals signals, const WatchCallback& callback); - - // Runs the Watcher's callback with the given arguments if it hasn't been - // cancelled yet. - void MaybeInvokeCallback(MojoResult result, - const HandleSignalsState& state, - MojoWatchNotificationFlags flags); - - // Notifies the Watcher of a state change. This may result in the Watcher - // adding a finalizer to the current RequestContext to invoke its callback, - // cancellation notwithstanding. - void NotifyForStateChange(const HandleSignalsState& signals_state); - - // Notifies the Watcher of handle closure. This always results in the Watcher - // adding a finalizer to the current RequestContext to invoke its callback, - // cancellation notwithstanding. - void NotifyClosed(); - - // Explicitly cancels the watch, guaranteeing that its callback will never be - // be invoked again. - void Cancel(); - - private: - friend class base::RefCountedThreadSafe<Watcher>; - - ~Watcher(); - - // The set of signals which are watched by this Watcher. - const MojoHandleSignals signals_; - - // The callback to invoke with a result and signal state any time signals in - // |signals_| are satisfied or become permanently unsatisfiable. - const WatchCallback callback_; - - // Guards |is_cancelled_|. - base::Lock lock_; - - // Indicates whether the watch has been cancelled. A |Watcher| may exist for a - // brief period of time after being cancelled if it's been attached as a - // RequestContext finalizer. In such cases the callback must not be invoked, - // hence this flag. - bool is_cancelled_ = false; - - DISALLOW_COPY_AND_ASSIGN(Watcher); -}; - -} // namespace edk -} // namespace mojo - -#endif // MOJO_EDK_SYSTEM_WATCHER_H_ diff --git a/mojo/edk/system/watcher_dispatcher.cc b/mojo/edk/system/watcher_dispatcher.cc new file mode 100644 index 0000000..409dd2a --- /dev/null +++ b/mojo/edk/system/watcher_dispatcher.cc @@ -0,0 +1,232 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/system/watcher_dispatcher.h" + +#include <algorithm> +#include <limits> +#include <map> + +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "mojo/edk/system/watch.h" + +namespace mojo { +namespace edk { + +WatcherDispatcher::WatcherDispatcher(MojoWatcherCallback callback) + : callback_(callback) {} + +void WatcherDispatcher::NotifyHandleState(Dispatcher* dispatcher, + const HandleSignalsState& state) { + base::AutoLock lock(lock_); + auto it = watched_handles_.find(dispatcher); + if (it == watched_handles_.end()) + return; + + // Maybe fire a notification to the watch assoicated with this dispatcher, + // provided we're armed it cares about the new state. + if (it->second->NotifyState(state, armed_)) { + ready_watches_.insert(it->second.get()); + + // If we were armed and got here, we notified the watch. Disarm. + armed_ = false; + } else { + ready_watches_.erase(it->second.get()); + } +} + +void WatcherDispatcher::NotifyHandleClosed(Dispatcher* dispatcher) { + scoped_refptr<Watch> watch; + { + base::AutoLock lock(lock_); + auto it = watched_handles_.find(dispatcher); + if (it == watched_handles_.end()) + return; + + watch = std::move(it->second); + + // Wipe out all state associated with the closed dispatcher. + watches_.erase(watch->context()); + ready_watches_.erase(watch.get()); + watched_handles_.erase(it); + } + + // NOTE: It's important that this is called outside of |lock_| since it + // acquires internal Watch locks. + watch->Cancel(); +} + +void WatcherDispatcher::InvokeWatchCallback( + uintptr_t context, + MojoResult result, + const HandleSignalsState& state, + MojoWatcherNotificationFlags flags) { + { + // We avoid holding the lock during dispatch. It's OK for notification + // callbacks to close this watcher, and it's OK for notifications to race + // with closure, if for example the watcher is closed from another thread + // between this test and the invocation of |callback_| below. + // + // Because cancellation synchronously blocks all future notifications, and + // because notifications themselves are mutually exclusive for any given + // context, we still guarantee that a single MOJO_RESULT_CANCELLED result + // is the last notification received for any given context. + // + // This guarantee is sufficient to make safe, synchronized, per-context + // state management possible in user code. + base::AutoLock lock(lock_); + if (closed_ && result != MOJO_RESULT_CANCELLED) + return; + } + + callback_(context, result, static_cast<MojoHandleSignalsState>(state), flags); +} + +Dispatcher::Type WatcherDispatcher::GetType() const { + return Type::WATCHER; +} + +MojoResult WatcherDispatcher::Close() { + // We swap out all the watched handle information onto the stack so we can + // call into their dispatchers without our own lock held. + std::map<uintptr_t, scoped_refptr<Watch>> watches; + { + base::AutoLock lock(lock_); + DCHECK(!closed_); + closed_ = true; + std::swap(watches, watches_); + watched_handles_.clear(); + } + + // Remove all refs from our watched dispatchers and fire cancellations. + for (auto& entry : watches) { + entry.second->dispatcher()->RemoveWatcherRef(this, entry.first); + entry.second->Cancel(); + } + + return MOJO_RESULT_OK; +} + +MojoResult WatcherDispatcher::WatchDispatcher( + scoped_refptr<Dispatcher> dispatcher, + MojoHandleSignals signals, + uintptr_t context) { + // NOTE: Because it's critical to avoid acquiring any other dispatcher locks + // while |lock_| is held, we defer adding oursevles to the dispatcher until + // after we've updated all our own relevant state and released |lock_|. + { + base::AutoLock lock(lock_); + if (watches_.count(context) || watched_handles_.count(dispatcher.get())) + return MOJO_RESULT_ALREADY_EXISTS; + + scoped_refptr<Watch> watch = new Watch(this, dispatcher, context, signals); + watches_.insert({context, watch}); + auto result = + watched_handles_.insert(std::make_pair(dispatcher.get(), watch)); + DCHECK(result.second); + } + + MojoResult rv = dispatcher->AddWatcherRef(this, context); + if (rv != MOJO_RESULT_OK) { + // Oops. This was not a valid handle to watch. Undo the above work and + // fail gracefully. + base::AutoLock lock(lock_); + watches_.erase(context); + watched_handles_.erase(dispatcher.get()); + return rv; + } + + return MOJO_RESULT_OK; +} + +MojoResult WatcherDispatcher::CancelWatch(uintptr_t context) { + // We may remove the last stored ref to the Watch below, so we retain + // a reference on the stack. + scoped_refptr<Watch> watch; + { + base::AutoLock lock(lock_); + auto it = watches_.find(context); + if (it == watches_.end()) + return MOJO_RESULT_NOT_FOUND; + watch = it->second; + watches_.erase(it); + } + + // Mark the watch as cancelled so no further notifications get through. + watch->Cancel(); + + // We remove the watcher ref for this context before updating any more + // internal watcher state, ensuring that we don't receiving further + // notifications for this context. + watch->dispatcher()->RemoveWatcherRef(this, context); + + { + base::AutoLock lock(lock_); + auto handle_it = watched_handles_.find(watch->dispatcher().get()); + DCHECK(handle_it != watched_handles_.end()); + ready_watches_.erase(handle_it->second.get()); + watched_handles_.erase(handle_it); + } + + return MOJO_RESULT_OK; +} + +MojoResult WatcherDispatcher::Arm( + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) { + base::AutoLock lock(lock_); + if (num_ready_contexts && + (!ready_contexts || !ready_results || !ready_signals_states)) { + return MOJO_RESULT_INVALID_ARGUMENT; + } + + if (watched_handles_.empty()) + return MOJO_RESULT_NOT_FOUND; + + if (ready_watches_.empty()) { + // Fast path: No watches are ready to notify, so we're done. + armed_ = true; + return MOJO_RESULT_OK; + } + + if (num_ready_contexts) { + DCHECK_LE(ready_watches_.size(), std::numeric_limits<uint32_t>::max()); + *num_ready_contexts = std::min( + *num_ready_contexts, static_cast<uint32_t>(ready_watches_.size())); + + WatchSet::const_iterator next_ready_iter = ready_watches_.begin(); + if (last_watch_to_block_arming_) { + // Find the next watch to notify in simple round-robin order on the + // |ready_watches_| map, wrapping around to the beginning if necessary. + next_ready_iter = ready_watches_.find(last_watch_to_block_arming_); + if (next_ready_iter != ready_watches_.end()) + ++next_ready_iter; + if (next_ready_iter == ready_watches_.end()) + next_ready_iter = ready_watches_.begin(); + } + + for (size_t i = 0; i < *num_ready_contexts; ++i) { + const Watch* const watch = *next_ready_iter; + ready_contexts[i] = watch->context(); + ready_results[i] = watch->last_known_result(); + ready_signals_states[i] = watch->last_known_signals_state(); + + // Iterate and wrap around. + last_watch_to_block_arming_ = watch; + ++next_ready_iter; + if (next_ready_iter == ready_watches_.end()) + next_ready_iter = ready_watches_.begin(); + } + } + + return MOJO_RESULT_FAILED_PRECONDITION; +} + +WatcherDispatcher::~WatcherDispatcher() {} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/watcher_dispatcher.h b/mojo/edk/system/watcher_dispatcher.h new file mode 100644 index 0000000..605a315 --- /dev/null +++ b/mojo/edk/system/watcher_dispatcher.h @@ -0,0 +1,101 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_SYSTEM_WATCHER_DISPATCHER_H_ +#define MOJO_EDK_SYSTEM_WATCHER_DISPATCHER_H_ + +#include <map> +#include <set> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/public/c/system/watcher.h" + +namespace mojo { +namespace edk { + +class Watch; + +// The dispatcher type which backs watcher handles. +class WatcherDispatcher : public Dispatcher { + public: + // Constructs a new WatcherDispatcher which invokes |callback| when a + // registered watch observes some relevant state change. + explicit WatcherDispatcher(MojoWatcherCallback callback); + + // Methods used by watched dispatchers to notify watchers of events. + void NotifyHandleState(Dispatcher* dispatcher, + const HandleSignalsState& state); + void NotifyHandleClosed(Dispatcher* dispatcher); + + // Method used by RequestContext (indirectly, via Watch) to complete + // notification operations from a safe stack frame to avoid reentrancy. + void InvokeWatchCallback(uintptr_t context, + MojoResult result, + const HandleSignalsState& state, + MojoWatcherNotificationFlags flags); + + // Dispatcher: + Type GetType() const override; + MojoResult Close() override; + MojoResult WatchDispatcher(scoped_refptr<Dispatcher> dispatcher, + MojoHandleSignals signals, + uintptr_t context) override; + MojoResult CancelWatch(uintptr_t context) override; + MojoResult Arm(uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) override; + + private: + friend class Watch; + + using WatchSet = std::set<const Watch*>; + + ~WatcherDispatcher() override; + + const MojoWatcherCallback callback_; + + // Guards access to the fields below. + // + // NOTE: This may be acquired while holding another dispatcher's lock, as + // watched dispatchers call into WatcherDispatcher methods which lock this + // when issuing state change notifications. WatcherDispatcher must therefore + // take caution to NEVER acquire other dispatcher locks while this is held. + base::Lock lock_; + + bool armed_ = false; + bool closed_ = false; + + // A mapping from context to Watch. + std::map<uintptr_t, scoped_refptr<Watch>> watches_; + + // A mapping from watched dispatcher to Watch. + std::map<Dispatcher*, scoped_refptr<Watch>> watched_handles_; + + // The set of all Watch instances which are currently ready to signal. This is + // used for efficient arming behavior, as it allows for O(1) discovery of + // whether or not arming can succeed and quick determination of who's + // responsible if it can't. + WatchSet ready_watches_; + + // Tracks the last Watch whose state was returned by Arm(). This is used to + // ensure consistent round-robin behavior in the event that multiple Watches + // remain ready over the span of several Arm() attempts. + // + // NOTE: This pointer is only used to index |ready_watches_| and may point to + // an invalid object. It must therefore never be dereferenced. + const Watch* last_watch_to_block_arming_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(WatcherDispatcher); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_WATCHER_DISPATCHER_H_ diff --git a/mojo/edk/system/watcher_set.cc b/mojo/edk/system/watcher_set.cc index 878f29a..0355b58 100644 --- a/mojo/edk/system/watcher_set.cc +++ b/mojo/edk/system/watcher_set.cc @@ -4,54 +4,79 @@ #include "mojo/edk/system/watcher_set.h" -#include "mojo/edk/system/request_context.h" -#include "mojo/public/c/system/types.h" +#include <utility> namespace mojo { namespace edk { -WatcherSet::WatcherSet() {} +WatcherSet::WatcherSet(Dispatcher* owner) : owner_(owner) {} -WatcherSet::~WatcherSet() {} +WatcherSet::~WatcherSet() = default; -void WatcherSet::NotifyForStateChange(const HandleSignalsState& state) { +void WatcherSet::NotifyState(const HandleSignalsState& state) { + // Avoid notifying watchers if they have already seen this state. + if (last_known_state_.has_value() && state.equals(last_known_state_.value())) + return; + last_known_state_ = state; for (const auto& entry : watchers_) - entry.second->NotifyForStateChange(state); + entry.first->NotifyHandleState(owner_, state); } void WatcherSet::NotifyClosed() { for (const auto& entry : watchers_) - entry.second->NotifyClosed(); + entry.first->NotifyHandleClosed(owner_); } -MojoResult WatcherSet::Add(MojoHandleSignals signals, - const Watcher::WatchCallback& callback, +MojoResult WatcherSet::Add(const scoped_refptr<WatcherDispatcher>& watcher, uintptr_t context, const HandleSignalsState& current_state) { - auto it = watchers_.find(context); - if (it != watchers_.end()) - return MOJO_RESULT_ALREADY_EXISTS; - - if (!current_state.can_satisfy(signals)) - return MOJO_RESULT_FAILED_PRECONDITION; - - scoped_refptr<Watcher> watcher(new Watcher(signals, callback)); - watchers_.insert(std::make_pair(context, watcher)); + auto it = watchers_.find(watcher.get()); + if (it == watchers_.end()) { + auto result = + watchers_.insert(std::make_pair(watcher.get(), Entry{watcher})); + it = result.first; + } - watcher->NotifyForStateChange(current_state); + if (!it->second.contexts.insert(context).second) + return MOJO_RESULT_ALREADY_EXISTS; + if (last_known_state_.has_value() && + !current_state.equals(last_known_state_.value())) { + // This new state may be relevant to everyone, in which case we just + // notify everyone. + NotifyState(current_state); + } else { + // Otherwise only notify the newly added Watcher. + watcher->NotifyHandleState(owner_, current_state); + } return MOJO_RESULT_OK; } -MojoResult WatcherSet::Remove(uintptr_t context) { - auto it = watchers_.find(context); +MojoResult WatcherSet::Remove(WatcherDispatcher* watcher, uintptr_t context) { + auto it = watchers_.find(watcher); if (it == watchers_.end()) - return MOJO_RESULT_INVALID_ARGUMENT; + return MOJO_RESULT_NOT_FOUND; + + ContextSet& contexts = it->second.contexts; + auto context_it = contexts.find(context); + if (context_it == contexts.end()) + return MOJO_RESULT_NOT_FOUND; + + contexts.erase(context_it); + if (contexts.empty()) + watchers_.erase(it); - RequestContext::current()->AddWatchCancelFinalizer(it->second); - watchers_.erase(it); return MOJO_RESULT_OK; } +WatcherSet::Entry::Entry(const scoped_refptr<WatcherDispatcher>& dispatcher) + : dispatcher(dispatcher) {} + +WatcherSet::Entry::Entry(Entry&& other) = default; + +WatcherSet::Entry::~Entry() = default; + +WatcherSet::Entry& WatcherSet::Entry::operator=(Entry&& other) = default; + } // namespace edk } // namespace mojo diff --git a/mojo/edk/system/watcher_set.h b/mojo/edk/system/watcher_set.h index 8ae54a1..2b7ef2c 100644 --- a/mojo/edk/system/watcher_set.h +++ b/mojo/edk/system/watcher_set.h @@ -5,45 +5,62 @@ #ifndef MOJO_EDK_SYSTEM_WATCHER_SET_H_ #define MOJO_EDK_SYSTEM_WATCHER_SET_H_ -#include <unordered_map> +#include <map> -#include "base/callback.h" #include "base/macros.h" #include "base/memory/ref_counted.h" +#include "base/optional.h" #include "mojo/edk/system/handle_signals_state.h" -#include "mojo/edk/system/watcher.h" -#include "mojo/public/c/system/types.h" +#include "mojo/edk/system/watcher_dispatcher.h" namespace mojo { namespace edk { -// A WatcherSet maintains a set of Watchers attached to a single handle and -// keyed on an arbitrary user context. +// A WatcherSet maintains a set of references to WatcherDispatchers to be +// notified when a handle changes state. +// +// Dispatchers which may be watched by a watcher should own a WatcherSet and +// notify it of all relevant state changes. class WatcherSet { public: - WatcherSet(); + // |owner| is the Dispatcher who owns this WatcherSet. + explicit WatcherSet(Dispatcher* owner); ~WatcherSet(); - // Notifies all Watchers of a state change. - void NotifyForStateChange(const HandleSignalsState& state); + // Notifies all watchers of the handle's current signals state. + void NotifyState(const HandleSignalsState& state); - // Notifies all Watchers that their watched handle has been closed. + // Notifies all watchers that this handle has been closed. void NotifyClosed(); - // Adds a new watcher to watch for signals in |signals| to be satisfied or - // unsatisfiable. |current_state| is the current signals state of the - // handle being watched. - MojoResult Add(MojoHandleSignals signals, - const Watcher::WatchCallback& callback, + // Adds a new watcher+context. + MojoResult Add(const scoped_refptr<WatcherDispatcher>& watcher, uintptr_t context, const HandleSignalsState& current_state); - // Removes a watcher from the set. - MojoResult Remove(uintptr_t context); + // Removes a watcher+context. + MojoResult Remove(WatcherDispatcher* watcher, uintptr_t context); private: - // A map of watchers keyed on context value. - std::unordered_map<uintptr_t, scoped_refptr<Watcher>> watchers_; + using ContextSet = std::set<uintptr_t>; + + struct Entry { + Entry(const scoped_refptr<WatcherDispatcher>& dispatcher); + Entry(Entry&& other); + ~Entry(); + + Entry& operator=(Entry&& other); + + scoped_refptr<WatcherDispatcher> dispatcher; + ContextSet contexts; + + private: + DISALLOW_COPY_AND_ASSIGN(Entry); + }; + + Dispatcher* const owner_; + std::map<WatcherDispatcher*, Entry> watchers_; + base::Optional<HandleSignalsState> last_known_state_; DISALLOW_COPY_AND_ASSIGN(WatcherSet); }; diff --git a/mojo/edk/system/watcher_unittest.cc b/mojo/edk/system/watcher_unittest.cc new file mode 100644 index 0000000..dd396cd --- /dev/null +++ b/mojo/edk/system/watcher_unittest.cc @@ -0,0 +1,1637 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdint.h> + +#include <map> +#include <memory> +#include <set> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/platform_thread.h" +#include "base/threading/simple_thread.h" +#include "base/time/time.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/c/system/watcher.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +using WatcherTest = test::MojoTestBase; + +class WatchHelper { + public: + using ContextCallback = + base::Callback<void(MojoResult, MojoHandleSignalsState)>; + + WatchHelper() {} + ~WatchHelper() {} + + MojoResult CreateWatcher(MojoHandle* handle) { + return MojoCreateWatcher(&Notify, handle); + } + + uintptr_t CreateContext(const ContextCallback& callback) { + return CreateContextWithCancel(callback, base::Closure()); + } + + uintptr_t CreateContextWithCancel(const ContextCallback& callback, + const base::Closure& cancel_callback) { + auto context = base::MakeUnique<NotificationContext>(callback); + NotificationContext* raw_context = context.get(); + raw_context->SetCancelCallback(base::Bind( + [](std::unique_ptr<NotificationContext> context, + const base::Closure& cancel_callback) { + if (cancel_callback) + cancel_callback.Run(); + }, + base::Passed(&context), cancel_callback)); + return reinterpret_cast<uintptr_t>(raw_context); + } + + private: + class NotificationContext { + public: + explicit NotificationContext(const ContextCallback& callback) + : callback_(callback) {} + + ~NotificationContext() {} + + void SetCancelCallback(const base::Closure& cancel_callback) { + cancel_callback_ = cancel_callback; + } + + void Notify(MojoResult result, MojoHandleSignalsState state) { + if (result == MOJO_RESULT_CANCELLED) + cancel_callback_.Run(); + else + callback_.Run(result, state); + } + + private: + const ContextCallback callback_; + base::Closure cancel_callback_; + + DISALLOW_COPY_AND_ASSIGN(NotificationContext); + }; + + static void Notify(uintptr_t context, + MojoResult result, + MojoHandleSignalsState state, + MojoWatcherNotificationFlags flags) { + reinterpret_cast<NotificationContext*>(context)->Notify(result, state); + } + + DISALLOW_COPY_AND_ASSIGN(WatchHelper); +}; + +class ThreadedRunner : public base::SimpleThread { + public: + explicit ThreadedRunner(const base::Closure& callback) + : SimpleThread("ThreadedRunner"), callback_(callback) {} + ~ThreadedRunner() override {} + + void Run() override { callback_.Run(); } + + private: + const base::Closure callback_; + + DISALLOW_COPY_AND_ASSIGN(ThreadedRunner); +}; + +void ExpectNoNotification(uintptr_t context, + MojoResult result, + MojoHandleSignalsState state, + MojoWatcherNotificationFlags flags) { + NOTREACHED(); +} + +void ExpectOnlyCancel(uintptr_t context, + MojoResult result, + MojoHandleSignalsState state, + MojoWatcherNotificationFlags flags) { + EXPECT_EQ(result, MOJO_RESULT_CANCELLED); +} + +TEST_F(WatcherTest, InvalidArguments) { + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoCreateWatcher(&ExpectNoNotification, nullptr)); + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectNoNotification, &w)); + + // Try to watch unwatchable handles. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoWatch(w, w, MOJO_HANDLE_SIGNAL_READABLE, 0)); + MojoHandle buffer_handle = CreateBuffer(42); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoWatch(w, buffer_handle, MOJO_HANDLE_SIGNAL_READABLE, 0)); + + // Try to cancel a watch on an invalid watcher handle. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoCancelWatch(buffer_handle, 0)); + + // Try to arm an invalid handle. + EXPECT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + MojoArmWatcher(MOJO_HANDLE_INVALID, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoArmWatcher(buffer_handle, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(buffer_handle)); + + // Try to arm with a non-null count but at least one null output buffer. + uint32_t num_ready_contexts = 1; + uintptr_t ready_context; + MojoResult ready_result; + MojoHandleSignalsState ready_state; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoArmWatcher(w, &num_ready_contexts, nullptr, &ready_result, + &ready_state)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoArmWatcher(w, &num_ready_contexts, &ready_context, nullptr, + &ready_state)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoArmWatcher(w, &num_ready_contexts, &ready_context, + &ready_result, nullptr)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, WatchMessagePipeReadable) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + int num_expected_notifications = 1; + const uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, int* expected_count, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_GT(*expected_count, 0); + *expected_count -= 1; + + EXPECT_EQ(MOJO_RESULT_OK, result); + event->Signal(); + }, + &event, &num_expected_notifications)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + const char kMessage1[] = "hey hey hey hey"; + const char kMessage2[] = "i said hey"; + const char kMessage3[] = "what's goin' on?"; + + // Writing to |b| multiple times should notify exactly once. + WriteMessage(b, kMessage1); + WriteMessage(b, kMessage2); + event.Wait(); + + // This also shouldn't fire a notification; the watcher is still disarmed. + WriteMessage(b, kMessage3); + + // Arming should fail with relevant information. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(readable_a_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + + // Flush the three messages from above. + EXPECT_EQ(kMessage1, ReadMessage(a)); + EXPECT_EQ(kMessage2, ReadMessage(a)); + EXPECT_EQ(kMessage3, ReadMessage(a)); + + // Now we can rearm the watcher. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); +} + +TEST_F(WatcherTest, CloseWatchedMessagePipeHandle) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t readable_a_context = helper.CreateContextWithCancel( + WatchHelper::ContextCallback(), + base::Bind([](base::WaitableEvent* event) { event->Signal(); }, &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + + // Test that closing a watched handle fires an appropriate notification, even + // when the watcher is unarmed. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, CloseWatchedMessagePipeHandlePeer) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + event->Signal(); + }, + &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + + // Test that closing a watched handle's peer with an armed watcher fires an + // appropriate notification. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + event.Wait(); + + // And now arming should fail with correct information about |a|'s state. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(readable_a_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + EXPECT_FALSE(ready_states[0].satisfiable_signals & + MOJO_HANDLE_SIGNAL_READABLE); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); +} + +TEST_F(WatcherTest, WatchDataPipeConsumerReadable) { + constexpr size_t kTestPipeCapacity = 64; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + int num_expected_notifications = 1; + const uintptr_t readable_consumer_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, int* expected_count, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_GT(*expected_count, 0); + *expected_count -= 1; + + EXPECT_EQ(MOJO_RESULT_OK, result); + event->Signal(); + }, + &event, &num_expected_notifications)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, consumer, MOJO_HANDLE_SIGNAL_READABLE, + readable_consumer_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + const char kMessage1[] = "hey hey hey hey"; + const char kMessage2[] = "i said hey"; + const char kMessage3[] = "what's goin' on?"; + + // Writing to |producer| multiple times should notify exactly once. + WriteData(producer, kMessage1); + WriteData(producer, kMessage2); + event.Wait(); + + // This also shouldn't fire a notification; the watcher is still disarmed. + WriteData(producer, kMessage3); + + // Arming should fail with relevant information. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(readable_consumer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + + // Flush the three messages from above. + EXPECT_EQ(kMessage1, ReadData(consumer, sizeof(kMessage1) - 1)); + EXPECT_EQ(kMessage2, ReadData(consumer, sizeof(kMessage2) - 1)); + EXPECT_EQ(kMessage3, ReadData(consumer, sizeof(kMessage3) - 1)); + + // Now we can rearm the watcher. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); +} + +TEST_F(WatcherTest, WatchDataPipeConsumerNewDataReadable) { + constexpr size_t kTestPipeCapacity = 64; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + int num_new_data_notifications = 0; + const uintptr_t new_data_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, int* notification_count, MojoResult result, + MojoHandleSignalsState state) { + *notification_count += 1; + + EXPECT_EQ(MOJO_RESULT_OK, result); + event->Signal(); + }, + &event, &num_new_data_notifications)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, consumer, MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + new_data_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + const char kMessage1[] = "hey hey hey hey"; + const char kMessage2[] = "i said hey"; + const char kMessage3[] = "what's goin' on?"; + + // Writing to |producer| multiple times should notify exactly once. + WriteData(producer, kMessage1); + WriteData(producer, kMessage2); + event.Wait(); + + // This also shouldn't fire a notification; the watcher is still disarmed. + WriteData(producer, kMessage3); + + // Arming should fail with relevant information. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(new_data_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + + // Attempt to read more data than is available. Should fail but clear the + // NEW_DATA_READABLE signal. + char large_buffer[512]; + uint32_t large_read_size = 512; + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + MojoReadData(consumer, large_buffer, &large_read_size, + MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + + // Attempt to arm again. Should succeed. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Write more data. Should notify. + event.Reset(); + WriteData(producer, kMessage1); + event.Wait(); + + // Reading some data should clear NEW_DATA_READABLE again so we can rearm. + EXPECT_EQ(kMessage1, ReadData(consumer, sizeof(kMessage1) - 1)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + EXPECT_EQ(2, num_new_data_notifications); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); +} + +TEST_F(WatcherTest, WatchDataPipeProducerWritable) { + constexpr size_t kTestPipeCapacity = 8; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + // Half the capacity of the data pipe. + const char kTestData[] = "aaaa"; + static_assert((sizeof(kTestData) - 1) * 2 == kTestPipeCapacity, + "Invalid test data for this test."); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + int num_expected_notifications = 1; + const uintptr_t writable_producer_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, int* expected_count, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_GT(*expected_count, 0); + *expected_count -= 1; + + EXPECT_EQ(MOJO_RESULT_OK, result); + event->Signal(); + }, + &event, &num_expected_notifications)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, producer, MOJO_HANDLE_SIGNAL_WRITABLE, + writable_producer_context)); + + // The producer is already writable, so arming should fail with relevant + // information. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(writable_producer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + // Write some data, but don't fill the pipe yet. Arming should fail again. + WriteData(producer, kTestData); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(writable_producer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + // Write more data, filling the pipe to capacity. Arming should succeed now. + WriteData(producer, kTestData); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Now read from the pipe, making the producer writable again. Should notify. + EXPECT_EQ(kTestData, ReadData(consumer, sizeof(kTestData) - 1)); + event.Wait(); + + // Arming should fail again. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(writable_producer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + // Fill the pipe once more and arm the watcher. Should succeed. + WriteData(producer, kTestData); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); +}; + +TEST_F(WatcherTest, CloseWatchedDataPipeConsumerHandle) { + constexpr size_t kTestPipeCapacity = 8; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t readable_consumer_context = helper.CreateContextWithCancel( + WatchHelper::ContextCallback(), + base::Bind([](base::WaitableEvent* event) { event->Signal(); }, &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, consumer, MOJO_HANDLE_SIGNAL_READABLE, + readable_consumer_context)); + + // Closing the consumer should fire a cancellation notification. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, CloseWatcherDataPipeConsumerHandlePeer) { + constexpr size_t kTestPipeCapacity = 8; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t readable_consumer_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + event->Signal(); + }, + &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, consumer, MOJO_HANDLE_SIGNAL_READABLE, + readable_consumer_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Closing the producer should fire a notification for an unsatisfiable watch. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + event.Wait(); + + // Now attempt to rearm and expect appropriate error feedback. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(readable_consumer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]); + EXPECT_FALSE(ready_states[0].satisfiable_signals & + MOJO_HANDLE_SIGNAL_READABLE); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); +} + +TEST_F(WatcherTest, CloseWatchedDataPipeProducerHandle) { + constexpr size_t kTestPipeCapacity = 8; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t writable_producer_context = helper.CreateContextWithCancel( + WatchHelper::ContextCallback(), + base::Bind([](base::WaitableEvent* event) { event->Signal(); }, &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, producer, MOJO_HANDLE_SIGNAL_WRITABLE, + writable_producer_context)); + + // Closing the consumer should fire a cancellation notification. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, CloseWatchedDataPipeProducerHandlePeer) { + constexpr size_t kTestPipeCapacity = 8; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + const char kTestMessageFullCapacity[] = "xxxxxxxx"; + static_assert(sizeof(kTestMessageFullCapacity) - 1 == kTestPipeCapacity, + "Invalid test message size for this test."); + + // Make the pipe unwritable initially. + WriteData(producer, kTestMessageFullCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t writable_producer_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + event->Signal(); + }, + &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, producer, MOJO_HANDLE_SIGNAL_WRITABLE, + writable_producer_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Closing the consumer should fire a notification for an unsatisfiable watch, + // as the full data pipe can never be read from again and is therefore + // permanently full and unwritable. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); + event.Wait(); + + // Now attempt to rearm and expect appropriate error feedback. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(writable_producer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]); + EXPECT_FALSE(ready_states[0].satisfiable_signals & + MOJO_HANDLE_SIGNAL_WRITABLE); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); +} + +TEST_F(WatcherTest, ArmWithNoWatches) { + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectNoNotification, &w)); + EXPECT_EQ(MOJO_RESULT_NOT_FOUND, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, WatchDuplicateContext) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectOnlyCancel, &w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, 0)); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + MojoWatch(w, b, MOJO_HANDLE_SIGNAL_READABLE, 0)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); +} + +TEST_F(WatcherTest, CancelUnknownWatch) { + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectNoNotification, &w)); + EXPECT_EQ(MOJO_RESULT_NOT_FOUND, MojoCancelWatch(w, 1234)); +} + +TEST_F(WatcherTest, ArmWithWatchAlreadySatisfied) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectOnlyCancel, &w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, a, MOJO_HANDLE_SIGNAL_WRITABLE, 0)); + + // |a| is always writable, so we can never arm this watcher. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(0u, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); +} + +TEST_F(WatcherTest, ArmWithWatchAlreadyUnsatisfiable) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectOnlyCancel, &w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, 0)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + + // |b| is closed and never wrote any messages, so |a| won't be readable again. + // MojoArmWatcher() should fail, incidcating as much. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(0u, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + EXPECT_FALSE(ready_states[0].satisfiable_signals & + MOJO_HANDLE_SIGNAL_READABLE); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); +} + +TEST_F(WatcherTest, MultipleWatches) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + base::WaitableEvent a_event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + base::WaitableEvent b_event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + int num_a_notifications = 0; + int num_b_notifications = 0; + auto notify_callback = + base::Bind([](base::WaitableEvent* event, int* notification_count, + MojoResult result, MojoHandleSignalsState state) { + *notification_count += 1; + EXPECT_EQ(MOJO_RESULT_OK, result); + event->Signal(); + }); + uintptr_t readable_a_context = helper.CreateContext( + base::Bind(notify_callback, &a_event, &num_a_notifications)); + uintptr_t readable_b_context = helper.CreateContext( + base::Bind(notify_callback, &b_event, &num_b_notifications)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + // Add two independent watch contexts to watch for |a| or |b| readability. + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, b, MOJO_HANDLE_SIGNAL_READABLE, readable_b_context)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + const char kMessage1[] = "things are happening"; + const char kMessage2[] = "ok. ok. ok. ok."; + const char kMessage3[] = "plz wake up"; + + // Writing to |b| should signal |a|'s watch. + WriteMessage(b, kMessage1); + a_event.Wait(); + a_event.Reset(); + + // Subsequent messages on |b| should not trigger another notification. + WriteMessage(b, kMessage2); + WriteMessage(b, kMessage3); + + // Messages on |a| also shouldn't trigger |b|'s notification, since the + // watcher should be disarmed by now. + WriteMessage(a, kMessage1); + WriteMessage(a, kMessage2); + WriteMessage(a, kMessage3); + + // Arming should fail. Since we only ask for at most one context's information + // that's all we should get back. Which one we get is unspecified. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = 1; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_TRUE(ready_contexts[0] == readable_a_context || + ready_contexts[0] == readable_b_context); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + // Now try arming again, verifying that both contexts are returned. + num_ready_contexts = kMaxReadyContexts; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(2u, num_ready_contexts); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[1]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + EXPECT_TRUE(ready_states[1].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + EXPECT_TRUE((ready_contexts[0] == readable_a_context && + ready_contexts[1] == readable_b_context) || + (ready_contexts[0] == readable_b_context && + ready_contexts[1] == readable_a_context)); + + // Flush out the test messages so we should be able to successfully rearm. + EXPECT_EQ(kMessage1, ReadMessage(a)); + EXPECT_EQ(kMessage2, ReadMessage(a)); + EXPECT_EQ(kMessage3, ReadMessage(a)); + EXPECT_EQ(kMessage1, ReadMessage(b)); + EXPECT_EQ(kMessage2, ReadMessage(b)); + EXPECT_EQ(kMessage3, ReadMessage(b)); + + // Add a watch which is always satisfied, so we can't arm. Arming should fail + // with only this new watch's information. + uintptr_t writable_c_context = helper.CreateContext(base::Bind( + [](MojoResult result, MojoHandleSignalsState state) { NOTREACHED(); })); + MojoHandle c, d; + CreateMessagePipe(&c, &d); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, c, MOJO_HANDLE_SIGNAL_WRITABLE, writable_c_context)); + num_ready_contexts = kMaxReadyContexts; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(writable_c_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + // Cancel the new watch and arming should succeed once again. + EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, writable_c_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); +} + +TEST_F(WatcherTest, NotifyOtherFromNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hello a"; + static const char kTestMessageToB[] = "hello b"; + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + WatchHelper helper; + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](MojoHandle w, MojoHandle a, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ("hello a", ReadMessage(a)); + + // Re-arm the watcher and signal |b|. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + WriteMessage(a, kTestMessageToB); + }, + w, a)); + + uintptr_t readable_b_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoHandle w, MojoHandle b, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToB, ReadMessage(b)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + event->Signal(); + }, + &event, w, b)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, b, MOJO_HANDLE_SIGNAL_READABLE, readable_b_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Send a message to |a|. The relevant watch context should be notified, and + // should in turn send a message to |b|, waking up the other context. The + // second context signals |event|. + WriteMessage(b, kTestMessageToA); + event.Wait(); +} + +TEST_F(WatcherTest, NotifySelfFromNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hello a"; + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + WatchHelper helper; + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + int expected_notifications = 10; + uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](int* expected_count, MojoHandle w, MojoHandle a, MojoHandle b, + base::WaitableEvent* event, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ("hello a", ReadMessage(a)); + + EXPECT_GT(*expected_count, 0); + *expected_count -= 1; + if (*expected_count == 0) { + event->Signal(); + return; + } else { + // Re-arm the watcher and signal |a| again. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + WriteMessage(b, kTestMessageToA); + } + }, + &expected_notifications, w, a, b, &event)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Send a message to |a|. When the watch above is notified, it will rearm and + // send another message to |a|. This will happen until + // |expected_notifications| reaches 0. + WriteMessage(b, kTestMessageToA); + event.Wait(); +} + +TEST_F(WatcherTest, ImplicitCancelOtherFromNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle c, d; + CreateMessagePipe(&c, &d); + + static const char kTestMessageToA[] = "hi a"; + static const char kTestMessageToC[] = "hi c"; + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + WatchHelper helper; + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + uintptr_t readable_a_context = helper.CreateContextWithCancel( + base::Bind([](MojoResult result, MojoHandleSignalsState state) { + NOTREACHED(); + }), + base::Bind([](base::WaitableEvent* event) { event->Signal(); }, &event)); + + uintptr_t readable_c_context = helper.CreateContext(base::Bind( + [](MojoHandle w, MojoHandle a, MojoHandle b, MojoHandle c, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToC, ReadMessage(c)); + + // Now rearm the watcher. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Must result in exactly ONE notification on the above context, for + // CANCELLED only. Because we cannot dispatch notifications until the + // stack unwinds, and because we must never dispatch non-cancellation + // notifications for a handle once it's been closed, we must be certain + // that cancellation due to closure preemptively invalidates any + // pending non-cancellation notifications queued on the current + // RequestContext, such as the one resulting from the WriteMessage here. + WriteMessage(b, kTestMessageToA); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + + // Rearming should be fine since |a|'s watch should already be + // implicitly cancelled (even though the notification will not have + // been invoked yet.) + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Nothing interesting should happen as a result of this. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + }, + w, a, b, c)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, c, MOJO_HANDLE_SIGNAL_READABLE, readable_c_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(d, kTestMessageToC); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); +} + +TEST_F(WatcherTest, ExplicitCancelOtherFromNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle c, d; + CreateMessagePipe(&c, &d); + + static const char kTestMessageToA[] = "hi a"; + static const char kTestMessageToC[] = "hi c"; + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + WatchHelper helper; + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](MojoResult result, MojoHandleSignalsState state) { NOTREACHED(); })); + + uintptr_t readable_c_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, uintptr_t readable_a_context, MojoHandle w, + MojoHandle a, MojoHandle b, MojoHandle c, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToC, ReadMessage(c)); + + // Now rearm the watcher. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Should result in no notifications on the above context, because the + // watch will have been cancelled by the time the notification callback + // can execute. + WriteMessage(b, kTestMessageToA); + WriteMessage(b, kTestMessageToA); + EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, readable_a_context)); + + // Rearming should be fine now. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Nothing interesting should happen as a result of these. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + + event->Signal(); + }, + &event, readable_a_context, w, a, b, c)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, c, MOJO_HANDLE_SIGNAL_READABLE, readable_c_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(d, kTestMessageToC); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); +} + +TEST_F(WatcherTest, NestedCancellation) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle c, d; + CreateMessagePipe(&c, &d); + + static const char kTestMessageToA[] = "hey a"; + static const char kTestMessageToC[] = "hey c"; + static const char kTestMessageToD[] = "hey d"; + + // This is a tricky test. It establishes a watch on |b| using one watcher and + // watches on |c| and |d| using another watcher. + // + // A message is written to |d| to wake up |c|'s watch, and the notification + // handler for that event does the following: + // 1. Writes to |a| to eventually wake up |b|'s watcher. + // 2. Rearms |c|'s watcher. + // 3. Writes to |d| to eventually wake up |c|'s watcher again. + // + // Meanwhile, |b|'s watch notification handler cancels |c|'s watch altogether + // before writing to |c| to wake up |d|. + // + // The net result should be that |c|'s context only gets notified once (from + // the first write to |d| above) and everyone else gets notified as expected. + + MojoHandle b_watcher; + MojoHandle cd_watcher; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&b_watcher)); + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&cd_watcher)); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + uintptr_t readable_d_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoHandle d, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToD, ReadMessage(d)); + event->Signal(); + }, + &event, d)); + + static int num_expected_c_notifications = 1; + uintptr_t readable_c_context = helper.CreateContext(base::Bind( + [](MojoHandle cd_watcher, MojoHandle a, MojoHandle c, MojoHandle d, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_GT(num_expected_c_notifications--, 0); + + // Trigger an eventual |readable_b_context| notification. + WriteMessage(a, kTestMessageToA); + + EXPECT_EQ(kTestMessageToC, ReadMessage(c)); + EXPECT_EQ(MOJO_RESULT_OK, MojoArmWatcher(cd_watcher, nullptr, nullptr, + nullptr, nullptr)); + + // Trigger another eventual |readable_c_context| notification. + WriteMessage(d, kTestMessageToC); + }, + cd_watcher, a, c, d)); + + uintptr_t readable_b_context = helper.CreateContext(base::Bind( + [](MojoHandle cd_watcher, uintptr_t readable_c_context, MojoHandle c, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, + MojoCancelWatch(cd_watcher, readable_c_context)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoArmWatcher(cd_watcher, nullptr, nullptr, + nullptr, nullptr)); + + WriteMessage(c, kTestMessageToD); + }, + cd_watcher, readable_c_context, c)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(b_watcher, b, MOJO_HANDLE_SIGNAL_READABLE, + readable_b_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(cd_watcher, c, MOJO_HANDLE_SIGNAL_READABLE, + readable_c_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(cd_watcher, d, MOJO_HANDLE_SIGNAL_READABLE, + readable_d_context)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(b_watcher, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(cd_watcher, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(d, kTestMessageToC); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(cd_watcher)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b_watcher)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); +} + +TEST_F(WatcherTest, CancelSelfInNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hey a"; + + MojoHandle w; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + static uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoHandle w, MojoHandle a, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + + // There should be no problem cancelling this watch from its own + // notification invocation. + EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, readable_a_context)); + EXPECT_EQ(kTestMessageToA, ReadMessage(a)); + + // Arming should fail because there are no longer any registered + // watches on the watcher. + EXPECT_EQ(MOJO_RESULT_NOT_FOUND, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // And closing |a| should be fine (and should not invoke this + // notification with MOJO_RESULT_CANCELLED) for the same reason. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + + event->Signal(); + }, + &event, w, a)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(b, kTestMessageToA); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, CloseWatcherInNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA1[] = "hey a"; + static const char kTestMessageToA2[] = "hey a again"; + + MojoHandle w; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoHandle w, MojoHandle a, MojoHandle b, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToA1, ReadMessage(a)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // There should be no problem closing this watcher from its own + // notification callback. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + + // And these should not trigger more notifications, because |w| has been + // closed already. + WriteMessage(b, kTestMessageToA2); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + + event->Signal(); + }, + &event, w, a, b)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(b, kTestMessageToA1); + event.Wait(); +} + +TEST_F(WatcherTest, CloseWatcherAfterImplicitCancel) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hey a"; + + MojoHandle w; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoHandle w, MojoHandle a, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToA, ReadMessage(a)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // This will cue up a notification for |MOJO_RESULT_CANCELLED|... + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + + // ...but it should never fire because we close the watcher here. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + + event->Signal(); + }, + &event, w, a)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(b, kTestMessageToA); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); +} + +TEST_F(WatcherTest, OtherThreadCancelDuringNotification) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hey a"; + + MojoHandle w; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + base::WaitableEvent wait_for_notification( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + base::WaitableEvent wait_for_cancellation( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + static bool callback_done = false; + uintptr_t readable_a_context = helper.CreateContextWithCancel( + base::Bind( + [](base::WaitableEvent* wait_for_notification, MojoHandle w, + MojoHandle a, MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToA, ReadMessage(a)); + + wait_for_notification->Signal(); + + // Give the other thread sufficient time to race with the completion + // of this callback. There should be no race, since the cancellation + // notification must be mutually exclusive to this notification. + base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1)); + + callback_done = true; + }, + &wait_for_notification, w, a), + base::Bind( + [](base::WaitableEvent* wait_for_cancellation) { + EXPECT_TRUE(callback_done); + wait_for_cancellation->Signal(); + }, + &wait_for_cancellation)); + + ThreadedRunner runner(base::Bind( + [](base::WaitableEvent* wait_for_notification, + base::WaitableEvent* wait_for_cancellation, MojoHandle w, + uintptr_t readable_a_context) { + wait_for_notification->Wait(); + + // Cancel the watch while the notification is still running. + EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, readable_a_context)); + + wait_for_cancellation->Wait(); + + EXPECT_TRUE(callback_done); + }, + &wait_for_notification, &wait_for_cancellation, w, readable_a_context)); + runner.Start(); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(b, kTestMessageToA); + runner.Join(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, WatchesCancelEachOtherFromNotifications) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hey a"; + static const char kTestMessageToB[] = "hey b"; + + base::WaitableEvent wait_for_a_to_notify( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + base::WaitableEvent wait_for_b_to_notify( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + base::WaitableEvent wait_for_a_to_cancel( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + base::WaitableEvent wait_for_b_to_cancel( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + MojoHandle a_watcher; + MojoHandle b_watcher; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&a_watcher)); + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&b_watcher)); + + // We set up two watchers, one on |a| and one on |b|. They cancel each other + // from within their respective watch notifications. This should be safe, + // i.e., it should not deadlock, in spite of the fact that we also guarantee + // mutually exclusive notification execution (including cancellations) on any + // given watch. + bool a_cancelled = false; + bool b_cancelled = false; + static uintptr_t readable_b_context; + uintptr_t readable_a_context = helper.CreateContextWithCancel( + base::Bind( + [](base::WaitableEvent* wait_for_a_to_notify, + base::WaitableEvent* wait_for_b_to_notify, MojoHandle b_watcher, + MojoHandle a, MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToA, ReadMessage(a)); + wait_for_a_to_notify->Signal(); + wait_for_b_to_notify->Wait(); + EXPECT_EQ(MOJO_RESULT_OK, + MojoCancelWatch(b_watcher, readable_b_context)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b_watcher)); + }, + &wait_for_a_to_notify, &wait_for_b_to_notify, b_watcher, a), + base::Bind( + [](base::WaitableEvent* wait_for_a_to_cancel, + base::WaitableEvent* wait_for_b_to_cancel, bool* a_cancelled) { + *a_cancelled = true; + wait_for_a_to_cancel->Signal(); + wait_for_b_to_cancel->Wait(); + }, + &wait_for_a_to_cancel, &wait_for_b_to_cancel, &a_cancelled)); + + readable_b_context = helper.CreateContextWithCancel( + base::Bind( + [](base::WaitableEvent* wait_for_a_to_notify, + base::WaitableEvent* wait_for_b_to_notify, + uintptr_t readable_a_context, MojoHandle a_watcher, MojoHandle b, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToB, ReadMessage(b)); + wait_for_b_to_notify->Signal(); + wait_for_a_to_notify->Wait(); + EXPECT_EQ(MOJO_RESULT_OK, + MojoCancelWatch(a_watcher, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a_watcher)); + }, + &wait_for_a_to_notify, &wait_for_b_to_notify, readable_a_context, + a_watcher, b), + base::Bind( + [](base::WaitableEvent* wait_for_a_to_cancel, + base::WaitableEvent* wait_for_b_to_cancel, bool* b_cancelled) { + *b_cancelled = true; + wait_for_b_to_cancel->Signal(); + wait_for_a_to_cancel->Wait(); + }, + &wait_for_a_to_cancel, &wait_for_b_to_cancel, &b_cancelled)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(a_watcher, a, MOJO_HANDLE_SIGNAL_READABLE, + readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(a_watcher, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(b_watcher, b, MOJO_HANDLE_SIGNAL_READABLE, + readable_b_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(b_watcher, nullptr, nullptr, nullptr, nullptr)); + + ThreadedRunner runner( + base::Bind([](MojoHandle b) { WriteMessage(b, kTestMessageToA); }, b)); + runner.Start(); + + WriteMessage(a, kTestMessageToB); + + wait_for_a_to_cancel.Wait(); + wait_for_b_to_cancel.Wait(); + runner.Join(); + + EXPECT_TRUE(a_cancelled); + EXPECT_TRUE(b_cancelled); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); +} + +TEST_F(WatcherTest, AlwaysCancel) { + // Basic sanity check to ensure that all possible ways to cancel a watch + // result in a final MOJO_RESULT_CANCELLED notification. + + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle w; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + const base::Closure signal_event = + base::Bind(&base::WaitableEvent::Signal, base::Unretained(&event)); + + // Cancel via |MojoCancelWatch()|. + uintptr_t context = helper.CreateContextWithCancel( + WatchHelper::ContextCallback(), signal_event); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, context)); + EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, context)); + event.Wait(); + event.Reset(); + + // Cancel by closing the watched handle. + context = helper.CreateContextWithCancel(WatchHelper::ContextCallback(), + signal_event); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, context)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + event.Wait(); + event.Reset(); + + // Cancel by closing the watcher handle. + context = helper.CreateContextWithCancel(WatchHelper::ContextCallback(), + signal_event); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, b, MOJO_HANDLE_SIGNAL_READABLE, context)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); +} + +TEST_F(WatcherTest, ArmFailureCirculation) { + // Sanity check to ensure that all ready handles will eventually be returned + // over a finite number of calls to MojoArmWatcher(). + + constexpr size_t kNumTestPipes = 100; + constexpr size_t kNumTestHandles = kNumTestPipes * 2; + MojoHandle handles[kNumTestHandles]; + + // Create a bunch of pipes and make sure they're all readable. + for (size_t i = 0; i < kNumTestPipes; ++i) { + CreateMessagePipe(&handles[i], &handles[i + kNumTestPipes]); + WriteMessage(handles[i], "hey"); + WriteMessage(handles[i + kNumTestPipes], "hay"); + WaitForSignals(handles[i], MOJO_HANDLE_SIGNAL_READABLE); + WaitForSignals(handles[i + kNumTestPipes], MOJO_HANDLE_SIGNAL_READABLE); + } + + // Create a watcher and watch all of them. + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectOnlyCancel, &w)); + for (size_t i = 0; i < kNumTestHandles; ++i) { + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, handles[i], MOJO_HANDLE_SIGNAL_READABLE, i)); + } + + // Keep trying to arm |w| until every watch gets an entry in |ready_contexts|. + // If MojoArmWatcher() is well-behaved, this should terminate eventually. + std::set<uintptr_t> ready_contexts; + while (ready_contexts.size() < kNumTestHandles) { + uint32_t num_ready_contexts = 1; + uintptr_t ready_context; + MojoResult ready_result; + MojoHandleSignalsState ready_state; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, &ready_context, + &ready_result, &ready_state)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(MOJO_RESULT_OK, ready_result); + ready_contexts.insert(ready_context); + } + + for (size_t i = 0; i < kNumTestHandles; ++i) + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handles[i])); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/test/mojo_test_base.cc b/mojo/edk/test/mojo_test_base.cc index f1032d7..71a5e3b 100644 --- a/mojo/edk/test/mojo_test_base.cc +++ b/mojo/edk/test/mojo_test_base.cc @@ -5,13 +5,17 @@ #include "mojo/edk/test/mojo_test_base.h" #include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" +#include "base/synchronization/waitable_event.h" #include "mojo/edk/embedder/embedder.h" #include "mojo/edk/system/handle_signals_state.h" #include "mojo/public/c/system/buffer.h" #include "mojo/public/c/system/data_pipe.h" #include "mojo/public/c/system/functions.h" +#include "mojo/public/c/system/watcher.h" +#include "mojo/public/cpp/system/wait.h" #include "testing/gtest/include/gtest/gtest.h" #if defined(OS_MACOSX) && !defined(OS_IOS) @@ -22,7 +26,6 @@ namespace mojo { namespace edk { namespace test { - #if defined(OS_MACOSX) && !defined(OS_IOS) namespace { base::MachPortBroker* g_mach_broker = nullptr; @@ -130,9 +133,7 @@ std::string MojoTestBase::ReadMessageWithHandles( MojoHandle mp, MojoHandle* handles, uint32_t expected_num_handles) { - CHECK_EQ(MojoWait(mp, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE, - nullptr), - MOJO_RESULT_OK); + CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK); uint32_t message_size = 0; uint32_t num_handles = 0; @@ -154,9 +155,7 @@ std::string MojoTestBase::ReadMessageWithHandles( // static std::string MojoTestBase::ReadMessageWithOptionalHandle(MojoHandle mp, MojoHandle* handle) { - CHECK_EQ(MojoWait(mp, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE, - nullptr), - MOJO_RESULT_OK); + CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK); uint32_t message_size = 0; uint32_t num_handles = 0; @@ -191,9 +190,7 @@ std::string MojoTestBase::ReadMessage(MojoHandle mp) { void MojoTestBase::ReadMessage(MojoHandle mp, char* data, size_t num_bytes) { - CHECK_EQ(MojoWait(mp, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE, - nullptr), - MOJO_RESULT_OK); + CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK); uint32_t message_size = 0; uint32_t num_handles = 0; @@ -288,8 +285,7 @@ void MojoTestBase::CreateDataPipe(MojoHandle *p0, // static void MojoTestBase::WriteData(MojoHandle producer, const std::string& data) { - CHECK_EQ(MojoWait(producer, MOJO_HANDLE_SIGNAL_WRITABLE, - MOJO_DEADLINE_INDEFINITE, nullptr), + CHECK_EQ(WaitForSignals(producer, MOJO_HANDLE_SIGNAL_WRITABLE), MOJO_RESULT_OK); uint32_t num_bytes = static_cast<uint32_t>(data.size()); CHECK_EQ(MojoWriteData(producer, data.data(), &num_bytes, @@ -300,8 +296,7 @@ void MojoTestBase::WriteData(MojoHandle producer, const std::string& data) { // static std::string MojoTestBase::ReadData(MojoHandle consumer, size_t size) { - CHECK_EQ(MojoWait(consumer, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, nullptr), + CHECK_EQ(WaitForSignals(consumer, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK); std::vector<char> buffer(size); uint32_t num_bytes = static_cast<uint32_t>(size); @@ -313,6 +308,20 @@ std::string MojoTestBase::ReadData(MojoHandle consumer, size_t size) { return std::string(buffer.data(), buffer.size()); } +// static +MojoHandleSignalsState MojoTestBase::GetSignalsState(MojoHandle handle) { + MojoHandleSignalsState signals_state; + CHECK_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(handle, &signals_state)); + return signals_state; +} + +// static +MojoResult MojoTestBase::WaitForSignals(MojoHandle handle, + MojoHandleSignals signals, + MojoHandleSignalsState* state) { + return Wait(Handle(handle), signals, state); +} + } // namespace test } // namespace edk } // namespace mojo diff --git a/mojo/edk/test/mojo_test_base.h b/mojo/edk/test/mojo_test_base.h index fa5b64c..35e2c2b 100644 --- a/mojo/edk/test/mojo_test_base.h +++ b/mojo/edk/test/mojo_test_base.h @@ -30,8 +30,6 @@ class MojoTestBase : public testing::Test { ~MojoTestBase() override; using LaunchType = MultiprocessTestHelper::LaunchType; - - protected: using HandlerCallback = base::Callback<void(ScopedMessagePipeHandle)>; class ClientController { @@ -152,6 +150,14 @@ class MojoTestBase : public testing::Test { // Reads data from a data pipe. static std::string ReadData(MojoHandle consumer, size_t size); + // Queries the signals state of |handle|. + static MojoHandleSignalsState GetSignalsState(MojoHandle handle); + + // Helper to block the calling thread waiting for signals to be raised. + static MojoResult WaitForSignals(MojoHandle handle, + MojoHandleSignals signals, + MojoHandleSignalsState* state = nullptr); + void set_launch_type(LaunchType launch_type) { launch_type_ = launch_type; } private: diff --git a/mojo/edk/test/multiprocess_test_helper.cc b/mojo/edk/test/multiprocess_test_helper.cc index de6e2d9..cf37782 100644 --- a/mojo/edk/test/multiprocess_test_helper.cc +++ b/mojo/edk/test/multiprocess_test_helper.cc @@ -46,11 +46,13 @@ const char kMojoPrimordialPipeToken[] = "mojo-primordial-pipe-token"; const char kMojoNamedPipeName[] = "mojo-named-pipe-name"; template <typename Func> -int RunClientFunction(Func handler) { +int RunClientFunction(Func handler, bool pass_pipe_ownership_to_main) { CHECK(MultiprocessTestHelper::primordial_pipe.is_valid()); ScopedMessagePipeHandle pipe = std::move(MultiprocessTestHelper::primordial_pipe); - return handler(pipe.get().value()); + MessagePipeHandle pipe_handle = + pass_pipe_ownership_to_main ? pipe.release() : pipe.get(); + return handler(pipe_handle.value()); } } // namespace @@ -58,7 +60,7 @@ int RunClientFunction(Func handler) { MultiprocessTestHelper::MultiprocessTestHelper() {} MultiprocessTestHelper::~MultiprocessTestHelper() { - CHECK(!test_child_.IsValid()); + CHECK(!test_child_.process.IsValid()); } ScopedMessagePipeHandle MultiprocessTestHelper::StartChild( @@ -74,7 +76,7 @@ ScopedMessagePipeHandle MultiprocessTestHelper::StartChildWithExtraSwitch( const std::string& switch_value, LaunchType launch_type) { CHECK(!test_child_name.empty()); - CHECK(!test_child_.IsValid()); + CHECK(!test_child_.process.IsValid()); std::string test_child_main = test_child_name + "TestChildMain"; @@ -168,22 +170,22 @@ ScopedMessagePipeHandle MultiprocessTestHelper::StartChildWithExtraSwitch( if (launch_type == LaunchType::CHILD || launch_type == LaunchType::NAMED_CHILD) { DCHECK(server_handle.is_valid()); - process.Connect(test_child_.Handle(), + process.Connect(test_child_.process.Handle(), ConnectionParams(std::move(server_handle)), process_error_callback_); } - CHECK(test_child_.IsValid()); + CHECK(test_child_.process.IsValid()); return pipe; } int MultiprocessTestHelper::WaitForChildShutdown() { - CHECK(test_child_.IsValid()); + CHECK(test_child_.process.IsValid()); int rv = -1; - WaitForMultiprocessTestChildExit(test_child_, TestTimeouts::action_timeout(), - &rv); - test_child_.Close(); + WaitForMultiprocessTestChildExit(test_child_.process, + TestTimeouts::action_timeout(), &rv); + test_child_.process.Close(); return rv; } @@ -232,20 +234,25 @@ void MultiprocessTestHelper::ChildSetup() { // static int MultiprocessTestHelper::RunClientMain( - const base::Callback<int(MojoHandle)>& main) { - return RunClientFunction([main](MojoHandle handle){ - return main.Run(handle); - }); + const base::Callback<int(MojoHandle)>& main, + bool pass_pipe_ownership_to_main) { + return RunClientFunction( + [main](MojoHandle handle) { return main.Run(handle); }, + pass_pipe_ownership_to_main); } // static int MultiprocessTestHelper::RunClientTestMain( const base::Callback<void(MojoHandle)>& main) { - return RunClientFunction([main](MojoHandle handle) { - main.Run(handle); - return (::testing::Test::HasFatalFailure() || - ::testing::Test::HasNonfatalFailure()) ? 1 : 0; - }); + return RunClientFunction( + [main](MojoHandle handle) { + main.Run(handle); + return (::testing::Test::HasFatalFailure() || + ::testing::Test::HasNonfatalFailure()) + ? 1 + : 0; + }, + true /* close_pipe_on_exit */); } // static diff --git a/mojo/edk/test/multiprocess_test_helper.h b/mojo/edk/test/multiprocess_test_helper.h index dd9bd6a..dc1c9bc 100644 --- a/mojo/edk/test/multiprocess_test_helper.h +++ b/mojo/edk/test/multiprocess_test_helper.h @@ -80,12 +80,13 @@ class MultiprocessTestHelper { // |EXPECT_TRUE(WaitForChildTestShutdown());|. bool WaitForChildTestShutdown(); - const base::Process& test_child() const { return test_child_; } + const base::Process& test_child() const { return test_child_.process; } // Used by macros in mojo/edk/test/mojo_test_base.h to support multiprocess // test client initialization. static void ChildSetup(); - static int RunClientMain(const base::Callback<int(MojoHandle)>& main); + static int RunClientMain(const base::Callback<int(MojoHandle)>& main, + bool pass_pipe_ownership_to_main = false); static int RunClientTestMain(const base::Callback<void(MojoHandle)>& main); // For use (and only valid) in the child process: @@ -93,7 +94,7 @@ class MultiprocessTestHelper { private: // Valid after |StartChild()| and before |WaitForChildShutdown()|. - base::Process test_child_; + base::SpawnChildResult test_child_; ProcessErrorCallback process_error_callback_; diff --git a/mojo/public/README.md b/mojo/public/README.md deleted file mode 100644 index dd91742..0000000 --- a/mojo/public/README.md +++ /dev/null @@ -1,43 +0,0 @@ -Mojo Public API -=============== - -The Mojo Public API is a binary stable API to the Mojo system. - -It consists of support for a number of programming languages (with a directory -for each support language), some "build" tools and build-time requirements, and -interface definitions for Mojo services (specified using an IDL). - -Note that there are various subdirectories named tests/. These contain tests of -the code in the enclosing directory, and are not meant for use by Mojo -applications. - -C/CPP/JS --------- - -The c/, cpp/, js/ subdirectories define the API for C, C++, and JavaScript, -respectively. - -The basic principle for these directories is that they consist of the source -files that one needs at build/deployment/run time (as appropriate for the -language), organized in a natural way for the particular language. - -Interfaces ----------- - -The interfaces/ subdirectory contains Mojo IDL (a.k.a. .mojom) descriptions of -standard Mojo services. - -Platform --------- - -The platform/ subdirectory contains any build-time requirements (e.g., static -libraries) that may be needed to produce a Service library for certain -platforms, such as a native shared library or as a NaCl binary. - -Tools ------ - -The tools/ subdirectory contains tools that are useful/necessary at -build/deployment time. These tools may be needed (as a practical necessity) to -use the API in any given language, e.g., to generate bindings from Mojo IDL -files. diff --git a/mojo/public/c/README.md b/mojo/public/c/README.md deleted file mode 100644 index 223c205..0000000 --- a/mojo/public/c/README.md +++ /dev/null @@ -1,22 +0,0 @@ -Mojo Public C API -================= - -This directory contains C language bindings for the Mojo Public API. - -System ------- - -The system/ subdirectory provides definitions of the basic low-level API used by -all Services (whether directly or indirectly). These consist primarily -of the IPC primitives used to communicate with Mojo services. - -Though the message protocol is stable, the implementation of the transport is -not, and access to the IPC mechanisms must be via the primitives defined in this -directory. - -Test Support ------------- - -This directory contains a C API for running tests. This API is only available -under special, specific test conditions. It is not meant for general use by Mojo -applications. diff --git a/mojo/public/c/system/BUILD.gn b/mojo/public/c/system/BUILD.gn index c3b3d5f..08185c7 100644 --- a/mojo/public/c/system/BUILD.gn +++ b/mojo/public/c/system/BUILD.gn @@ -17,7 +17,7 @@ component("system") { "thunks.cc", "thunks.h", "types.h", - "wait_set.h", + "watcher.h", ] defines = [ "MOJO_SYSTEM_IMPLEMENTATION" ] diff --git a/mojo/public/c/system/README.md b/mojo/public/c/system/README.md new file mode 100644 index 0000000..2abe80f --- /dev/null +++ b/mojo/public/c/system/README.md @@ -0,0 +1,869 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo C System API +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview +The Mojo C System API is a lightweight API (with an eventually-stable ABI) upon +which all higher layers of the Mojo system are built. + +This API exposes the fundamental capabilities to: create, read from, and write +to **message pipes**; create, read from, and write to **data pipes**; create +**shared buffers** and generate sharable handles to them; wrap platform-specific +handle objects (such as **file descriptors**, **Windows handles**, and +**Mach ports**) for seamless transit over message pipes; and efficiently watch +handles for various types of state transitions. + +This document provides a brief guide to API usage with example code snippets. +For a detailed API references please consult the headers in +[//mojo/public/c/system](https://cs.chromium.org/chromium/src/mojo/public/c/system/). + +### A Note About Multithreading + +The Mojo C System API is entirely thread-agnostic. This means that all functions +may be called from any thread in a process, and there are no restrictions on how +many threads can use the same object at the same time. + +Of course this does not mean you can completely ignore potential concurrency +issues -- such as a handle being closed on one thread while another thread is +trying to perform an operation on the same handle -- but there is nothing +fundamentally incorrect about using any given API or handle from multiple +threads. + +### A Note About Synchronization + +Every Mojo API call is non-blocking and synchronously yields some kind of status +result code, but the call's side effects -- such as affecting the state of +one or more handles in the system -- may or may not occur asynchronously. + +Mojo objects can be observed for interesting state changes in a way that is +thread-agnostic and in some ways similar to POSIX signal handlers: *i.e.* +user-provided notification handlers may be invoked at any time on arbitrary +threads in the process. It is entirely up to the API user to take appropriate +measures to synchronize operations against other application state. + +The higher level [system](/mojo#High-Level-System-APIs) and +[bindings](/mojo#High-Level-Bindings-APIs) APIs provide helpers to simplify Mojo +usage in this regard, at the expense of some flexibility. + +## Result Codes + +Most API functions return a value of type `MojoResult`. This is an integral +result code used to convey some meaningful level of detail about the result of a +requested operation. + +See [//mojo/public/c/system/types.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/types.h) +for different possible values. See documentation for individual API calls for +more specific contextual meaning of various result codes. + +## Handles + +Every Mojo IPC primitive is identified by a generic, opaque integer handle of +type `MojoHandle`. Handles can be acquired by creating new objects using various +API calls, or by reading messages which contain attached handles. + +A `MojoHandle` can represent a message pipe endpoint, a data pipe consumer, +a data pipe producer, a shared buffer reference, a wrapped native platform +handle such as a POSIX file descriptor or a Windows system handle, or a watcher +object (see [Signals & Watchers](#Signals-Watchers) below.) + +All types of handles except for watchers (which are an inherently local concept) +can be attached to messages and sent over message pipes. + +Any `MojoHandle` may be closed by calling `MojoClose`: + +``` c +MojoHandle x = DoSomethingToGetAValidHandle(); +MojoResult result = MojoClose(x); +``` + +If the handle passed to `MojoClose` was a valid handle, it will be closed and +`MojoClose` returns `MOJO_RESULT_OK`. Otherwise it returns +`MOJO_RESULT_INVALID_ARGUMENT`. + +Similar to native system handles on various popular platforms, `MojoHandle` +values may be reused over time. Thus it is important to avoid logical errors +which lead to misplaced handle ownership, double-closes, *etc.* + +## Message Pipes + +A message pipe is a bidirectional messaging channel which can carry arbitrary +unstructured binary messages with zero or more `MojoHandle` attachments to be +transferred from one end of a pipe to the other. Message pipes work seamlessly +across process boundaries or within a single process. + +The [Embedder Development Kit (EDK)](/mojo/edk/embedder) provides the means to +bootstrap one or more primordial cross-process message pipes, and it's up to +Mojo embedders to expose this capability in some useful way. Once such a pipe is +established, additional handles -- including other message pipe handles -- may +be sent to a remote process using that pipe (or in turn, over other pipes sent +over that pipe, or pipes sent over *that* pipe, and so on...) + +The public C System API exposes the ability to read and write messages on pipes +and to create new message pipes. + +See [//mojo/public/c/system/message_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/message_pipe.h) +for detailed message pipe API documentation. + +### Creating Message Pipes + +`MojoCreateMessagePipe` can be used to create a new message pipe: + +``` c +MojoHandle a, b; +MojoResult result = MojoCreateMessagePipe(NULL, &a, &b); +``` + +After this snippet, `result` should be `MOJO_RESULT_OK` (it's really hard for +this to fail!), and `a` and `b` will contain valid Mojo handles, one for each +end of the new message pipe. + +Any messages written to `a` are eventually readable from `b`, and any messages +written to `b` are eventually readable from `a`. If `a` is closed at any point, +`b` will eventually become aware of this fact; likewise if `b` is closed, `a` +will become aware of that. + +The state of these conditions can be queried and watched asynchronously as +described in the [Signals & Watchers](#Signals-Watchers) section below. + +### Allocating Messages + +In order to avoid redundant internal buffer copies, Mojo would like to allocate +your message storage buffers for you. This is easy: + +``` c +MojoMessageHandle message; +MojoResult result = MojoAllocMessage(6, NULL, 0, MOJO_ALLOC_MESSAGE_FLAG_NONE, + &message); +``` + +Note that we have a special `MojoMessageHandle` type for message objects. + +The code above allocates a buffer for a message payload of 6 bytes with no +handles attached. + +If we change our mind and decide not to send this message, we can delete it: + +``` c +MojoResult result = MojoFreeMessage(message); +``` + +If we instead decide to send our newly allocated message, we first need to fill +in the payload data with something interesting. How about a pleasant greeting: + +``` c +void* buffer = NULL; +MojoResult result = MojoGetMessageBuffer(message, &buffer); +memcpy(buffer, "hello", 6); +``` + +Now we can write the message to a pipe. Note that attempting to write a message +transfers ownership of the message object (and any attached handles) into the +target pipe and there is therefore no need to subsequently call +`MojoFreeMessage` on that message. + +### Writing Messages + +``` c +result = MojoWriteMessageNew(a, message, MOJO_WRITE_MESSAGE_FLAG_NONE); +``` + +`MojoWriteMessage` is a *non-blocking* call: it always returns +immediately. If its return code is `MOJO_RESULT_OK` the message will eventually +find its way to the other end of the pipe -- assuming that end isn't closed +first, of course. If the return code is anything else, the message is deleted +and not transferred. + +In this case since we know `b` is still open, we also know the message will +eventually arrive at `b`. `b` can be queried or watched to become aware of when +the message arrives, but we'll ignore that complexity for now. See +[Signals & Watchers](#Signals-Watchers) below for more information. + +*** aside +**NOTE**: Although this is an implementation detail and not strictly guaranteed by the +System API, it is true in the current implementation that the message will +arrive at `b` before the above `MojoWriteMessage` call even returns, because `b` +is in the same process as `a` and has never been transferred over another pipe. +*** + +### Reading Messages + +We can read a new message object from a pipe: + +``` c +MojoMessageHandle message; +uint32_t num_bytes; +MojoResult result = MojoReadMessageNew(b, &message, &num_bytes, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE); +``` + +and map its buffer to retrieve the contents: + +``` c +void* buffer = NULL; +MojoResult result = MojoGetMessageBuffer(message, &buffer); +printf("Pipe says: %s", (const char*)buffer); +``` + +`result` should be `MOJO_RESULT_OK` and this snippet should write `"hello"` to +`stdout`. + +If we try were to try reading again now that there are no messages on `b`: + +``` c +MojoMessageHandle message; +MojoResult result = MojoReadMessageNew(b, &message, NULL, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE); +``` + +We'll get a `result` of `MOJO_RESULT_SHOULD_WAIT`, indicating that the pipe is +not yet readable. + +### Messages With Handles + +Probably the most useful feature of Mojo IPC is that message pipes can carry +arbitrary Mojo handles, including other message pipes. This is also +straightforward. + +Here's an example which creates two pipes, using the first pipe to transfer +one end of the second pipe. If you have a good imagination you can pretend the +first pipe spans a process boundary, which makes the example more practically +interesting: + +``` c +MojoHandle a, b; +MojoHandle c, d; +MojoMessage message; + +// Allocate a message with an empty payload and handle |c| attached. Note that +// this takes ownership of |c|, effectively invalidating its handle value. +MojoResult result = MojoAllocMessage(0, &c, 1, MOJO_ALLOC_MESSAGE_FLAG_NONE, + message); + +result = MojoWriteMessageNew(a, message, MOJO_WRITE_MESSAGE_FLAG_NONE); + +// Some time later... +uint32_t num_bytes; +MojoHandle e; +uint32_t num_handles = 1; +MojoResult result = MojoReadMessageNew(b, &message, &num_bytes, &e, + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); +``` + +At this point the handle in `e` is now referencing the same message pipe +endpoint which was originally referenced by `c`. + +Note that `num_handles` above is initialized to 1 before we pass its address to +`MojoReadMessageNew`. This is to indicate how much `MojoHandle` storage is +available at the output buffer we gave it (`&e` above). + +If we didn't know how many handles to expect in an incoming message -- which is +often the case -- we can use `MojoReadMessageNew` to query for this information +first: + +``` c +MojoMessageHandle message; +uint32_t num_bytes = 0; +uint32_t num_handles = 0; +MojoResult result = MojoReadMessageNew(b, &message, &num_bytes, NULL, + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); +``` + +If in this case there were a received message on `b` with some nonzero number +of handles, `result` would be `MOJO_RESULT_RESOURCE_EXHAUSTED`, and both +`num_bytes` and `num_handles` would be updated to reflect the payload size and +number of attached handles on the next available message. + +It's also worth noting that if there did happen to be a message available with +no payload and no handles (*i.e.* an empty message), this would actually return +`MOJO_RESULT_OK`. + +## Data Pipes + +Data pipes provide an efficient unidirectional channel for moving large amounts +of unframed data between two endpoints. Every data pipe has a fixed +**element size** and **capacity**. Reads and writes must be done in sizes that +are a multiple of the element size, and writes to the pipe can only be queued +up to the pipe's capacity before reads must be done to make more space +available. + +Every data pipe has a single **producer** handle used to write data into the +pipe and a single **consumer** handle used to read data out of the pipe. + +Finally, data pipes support both immediate I/O -- reading into and writing out +from user-supplied buffers -- as well as two-phase I/O, allowing callers to +temporarily lock some portion of the data pipe in order to read or write its +contents directly. + +See [//mojo/public/c/system/data_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/data_pipe.h) +for detailed data pipe API documentation. + +### Creating Data Pipes + +Use `MojoCreateDataPipe` to create a new data pipe. The +`MojoCreateDataPipeOptions` structure is used to configure the new pipe, but +this can be omitted to assume the default options of a single-byte element size +and an implementation-defined default capacity (64 kB at the time of this +writing.) + +``` c +MojoHandle producer, consumer; +MojoResult result = MojoCreateDataPipe(NULL, &producer, &consumer); +``` + +### Immediate I/O + +Data can be written into or read out of a data pipe using buffers provided by +the caller. This is generally more convenient than two-phase I/O but is +also less efficient due to extra copying. + +``` c +uint32_t num_bytes = 12; +MojoResult result = MojoWriteData(producer, "datadatadata", &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE); +``` + +The above snippet will attempt to write 12 bytes into the data pipe, which +should succeed and return `MOJO_RESULT_OK`. If the available capacity on the +pipe was less than the amount requested (the input value of `*num_bytes`) this +will copy what it can into the pipe and return the number of bytes written in +`*num_bytes`. If no data could be copied this will instead return +`MOJO_RESULT_SHOULD_WAIT`. + +Reading from the consumer is a similar operation. + +``` c +char buffer[64]; +uint32_t num_bytes = 64; +MojoResult result = MojoReadData(consumer, buffer, &num_bytes, + MOJO_READ_DATA_FLAG_NONE); +``` + +This will attempt to read up to 64 bytes, returning the actual number of bytes +read in `*num_bytes`. + +`MojoReadData` supports a number of interesting flags to change the behavior: +you can peek at the data (copy bytes out without removing them from the pipe), +query the number of bytes available without doing any actual reading of the +contents, or discard data from the pipe without bothering to copy it anywhere. + +This also supports a `MOJO_READ_DATA_FLAG_ALL_OR_NONE` which ensures that the +call succeeds **only** if the exact number of bytes requested could be read. +Otherwise such a request will fail with `MOJO_READ_DATA_OUT_OF_RANGE`. + +### Two-Phase I/O + +Data pipes also support two-phase I/O operations, allowing a caller to +temporarily lock a portion of the data pipe's storage for direct memory access. + +``` c +void* buffer; +uint32_t num_bytes = 1024; +MojoResult result = MojoBeginWriteData(producer, &buffer, &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE); +``` + +This requests write access to a region of up to 1024 bytes of the data pipe's +next available capacity. Upon success, `buffer` will point to the writable +storage and `num_bytes` will indicate the size of the buffer there. + +The caller should then write some data into the memory region and release it +ASAP, indicating the number of bytes actually written: + +``` c +memcpy(buffer, "hello", 6); +MojoResult result = MojoEndWriteData(producer, 6); +``` + +Two-phase reads look similar: + +``` c +void* buffer; +uint32_t num_bytes = 1024; +MojoResult result = MojoBeginReadData(consumer, &buffer, &num_bytes, + MOJO_READ_DATA_FLAG_NONE); +// result should be MOJO_RESULT_OK, since there is some data available. + +printf("Pipe says: %s", (const char*)buffer); // Should say "hello". + +result = MojoEndReadData(consumer, 1); // Say we only consumed one byte. + +num_bytes = 1024; +result = MojoBeginReadData(consumer, &buffer, &num_bytes, + MOJO_READ_DATA_FLAG_NONE); +printf("Pipe says: %s", (const char*)buffer); // Should say "ello". +result = MojoEndReadData(consumer, 5); +``` + +## Shared Buffers + +Shared buffers are chunks of memory which can be mapped simultaneously by +multiple processes. Mojo provides a simple API to make these available to +applications. + +See [//mojo/public/c/system/buffer.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/buffer.h) +for detailed shared buffer API documentation. + +### Creating Buffer Handles + +Usage is straightforward. You can create a new buffer: + +``` c +// Allocate a shared buffer of 4 kB. +MojoHandle buffer; +MojoResult result = MojoCreateSharedBuffer(NULL, 4096, &buffer); +``` + +You can also duplicate an existing shared buffer handle: + +``` c +MojoHandle another_name_for_buffer; +MojoResult result = MojoDuplicateBufferHandle(buffer, NULL, + &another_name_for_buffer); +``` + +This is useful if you want to retain a handle to the buffer while also sharing +handles with one or more other clients. The allocated buffer remains valid as +long as at least one shared buffer handle exists to reference it. + +### Mapping Buffers + +You can map (and later unmap) a specified range of the buffer to get direct +memory access to its contents: + +``` c +void* data; +MojoResult result = MojoMapBuffer(buffer, 0, 64, &data, + MOJO_MAP_BUFFER_FLAG_NONE); + +*(int*)data = 42; +result = MojoUnmapBuffer(data); +``` + +A buffer may have any number of active mappings at a time, in any number of +processes. + +### Read-Only Handles + +An option can also be specified on `MojoDuplicateBufferHandle` to ensure +that the newly duplicated handle can only be mapped to read-only memory: + +``` c +MojoHandle read_only_buffer; +MojoDuplicateBufferHandleOptions options; +options.struct_size = sizeof(options); +options.flags = MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY; +MojoResult result = MojoDuplicateBufferHandle(buffer, &options, + &read_only_buffer); + +// Attempt to map and write to the buffer using the read-only handle: +void* data; +result = MojoMapBuffer(read_only_buffer, 0, 64, &data, + MOJO_MAP_BUFFER_FLAG_NONE); +*(int*)data = 42; // CRASH +``` + +*** note +**NOTE:** One important limitation of the current implementation is that +read-only handles can only be produced from a handle that was originally created +by `MojoCreateSharedBuffer` (*i.e.*, you cannot create a read-only duplicate +from a non-read-only duplicate), and the handle cannot have been transferred +over a message pipe first. +*** + +## Native Platform Handles (File Descriptors, Windows Handles, *etc.*) + +Native platform handles to system objects can be wrapped as Mojo handles for +seamless transit over message pipes. Mojo currently supports wrapping POSIX +file descriptors, Windows handles, and Mach ports. + +See [//mojo/public/c/system/platform_handle.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/platform_handle.h) +for detailed platform handle API documentation. + +### Wrapping Basic Handle Types + +Wrapping a POSIX file descriptor is simple: + +``` c +MojoPlatformHandle platform_handle; +platform_handle.struct_size = sizeof(platform_handle); +platform_handle.type = MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR; +platform_handle.value = (uint64_t)fd; +MojoHandle handle; +MojoResult result = MojoWrapPlatformHandle(&platform_handle, &handle); +``` + +Note that at this point `handle` effectively owns the file descriptor +and if you were to call `MojoClose(handle)`, the file descriptor would be closed +too; but we're not going to close it here! We're going to pretend we've sent it +over a message pipe, and now we want to unwrap it on the other side: + +``` c +MojoPlatformHandle platform_handle; +platform_handle.struct_size = sizeof(platform_handle); +MojoResult result = MojoUnwrapPlatformHandle(handle, &platform_handle); +int fd = (int)platform_handle.value; +``` + +The situation looks nearly identical for wrapping and unwrapping Windows handles +and Mach ports. + +### Wrapping Shared Buffer Handles + +Unlike other handle types, shared buffers have special meaning in Mojo, and it +may be desirable to wrap a native platform handle -- along with some extra +metadata -- such that be treated like a real Mojo shared buffer handle. +Conversely it can also be useful to unpack a Mojo shared buffer handle into +a native platform handle which references the buffer object. Both of these +things can be done using the `MojoWrapPlatformSharedBuffer` and +`MojoUnwrapPlatformSharedBuffer` APIs. + +On Windows, the wrapped platform handle must always be a Windows handle to +a file mapping object. + +On OS X, the wrapped platform handle must be a memory-object send right. + +On all other POSIX systems, the wrapped platform handle must be a file +descriptor for a shared memory object. + +## Signals & Watchers + +Message pipe and data pipe (producer and consumer) handles can change state in +ways that may be interesting to a Mojo API user. For example, you may wish to +know when a message pipe handle has messages available to be read or when its +peer has been closed. Such states are reflected by a fixed set of boolean +signals on each pipe handle. + +### Signals + +Every message pipe and data pipe handle maintains a notion of +**signaling state** which may be queried at any time. For example: + +``` c +MojoHandle a, b; +MojoCreateMessagePipe(NULL, &a, &b); + +MojoHandleSignalsState state; +MojoResult result = MojoQueryHandleSignalsState(a, &state); +``` + +The `MojoHandleSignalsState` structure exposes two fields: `satisfied_signals` +and `satisfiable_signals`. Both of these are bitmasks of the type +`MojoHandleSignals` (see [//mojo/public/c/system/types.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/types.h) +for more details.) + +The `satisfied_signals` bitmask indicates signals which were satisfied on the +handle at the time of the call, while the `satisfiable_signals` bitmask +indicates signals which were still possible to satisfy at the time of the call. +It is thus by definition always true that: + +``` c +(satisfied_signals | satisfiable_signals) == satisfiable_signals +``` + +In other words a signal obviously cannot be satisfied if it is no longer +satisfiable. Furthermore once a signal is unsatisfiable, *i.e.* is no longer +set in `sastisfiable_signals`, it can **never** become satisfiable again. + +To illustrate this more clearly, consider the message pipe created above. Both +ends of the pipe are still open and neither has been written to yet. Thus both +handles start out with the same signaling state: + +| Field | State | +|-----------------------|-------| +| `satisfied_signals` | `MOJO_HANDLE_SIGNAL_WRITABLE` +| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_WRITABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED` + +Writing a message to handle `b` will eventually alter the signaling state of `a` +such that `MOJO_HANDLE_SIGNAL_READABLE` also becomes satisfied. If we were to +then close `b`, the signaling state of `a` would look like: + +| Field | State | +|-----------------------|-------| +| `satisfied_signals` | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED` +| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED` + +Note that even though `a`'s peer is known to be closed (hence making `a` +permanently unwritable) it remains readable because there's still an unread +received message waiting to be read from `a`. + +Finally if we read the last message from `a` its signaling state becomes: + +| Field | State | +|-----------------------|-------| +| `satisfied_signals` | `MOJO_HANDLE_SIGNAL_PEER_CLOSED` +| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_PEER_CLOSED` + +and we know definitively that `a` can never be read from again. + +### Watching Signals + +The ability to query a handle's signaling state can be useful, but it's not +sufficient to support robust and efficient pipe usage. Mojo watchers empower +users with the ability to **watch** a handle's signaling state for interesting +changes and automatically invoke a notification handler in response. + +When a watcher is created it must be bound to a function pointer matching +the following signature, defined in +[//mojo/public/c/system/watcher.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/watcher.h): + +``` c +typedef void (*MojoWatcherNotificationCallback)( + uintptr_t context, + MojoResult result, + MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags); +``` + +The `context` argument corresponds to a specific handle being watched by the +watcher (read more below), and the remaining arguments provide details regarding +the specific reason for the notification. It's important to be aware that a +watcher's registered handler may be called **at any time** and +**on any thread**. + +It's also helpful to understand a bit about the mechanism by which the handler +can be invoked. Essentially, any Mojo C System API call may elicit a handle +state change of some kind. If such a change is relevant to conditions watched by +a watcher, and that watcher is in a state which allows it raise a corresponding +notification, its notification handler will be invoked synchronously some time +before the outermost System API call on the current thread's stack returns. + +Handle state changes can also occur as a result of incoming IPC from an external +process. If a pipe in the current process is connected to an endpoint in another +process and the internal Mojo system receives an incoming message bound for the +local endpoint, the arrival of that message will trigger a state change on the +receiving handle and may thus invoke one or more watchers' notification handlers +as a result. + +The `MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM` flag on the notification +handler's `flags` argument is used to indicate whether the handler was invoked +due to such an internal system IPC event (if the flag is set), or if it was +invoked synchronously due to some local API call (if the flag is unset.) +This distinction can be useful to make in certain cases to *e.g.* avoid +accidental reentrancy in user code. + +### Creating a Watcher + +Creating a watcher is simple: + +``` c + +void OnNotification(uintptr_t context, + MojoResult result, + MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags) { + // ... +} + +MojoHandle w; +MojoResult result = MojoCreateWatcher(&OnNotification, &w); +``` + +Like all other `MojoHandle` types, watchers may be destroyed by closing them +with `MojoClose`. Unlike other `MojoHandle` types, watcher handles are **not** +transferrable across message pipes. + +In order for a watcher to be useful, it has to watch at least one handle. + +### Adding a Handle to a Watcher + +Any given watcher can watch any given (message or data pipe) handle for some set +of signaling conditions. A handle may be watched simultaneously by multiple +watchers, and a single watcher can watch multiple different handles +simultaneously. + +``` c +MojoHandle a, b; +MojoCreateMessagePipe(NULL, &a, &b); + +// Watch handle |a| for readability. +const uintptr_t context = 1234; +MojoResult result = MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, context); +``` + +We've successfully instructed watcher `w` to begin watching pipe handle `a` for +readability. However, our recently created watcher is still in a **disarmed** +state, meaning that it will never fire a notification pertaining to this watched +signaling condition. It must be **armed** before that can happen. + +### Arming a Watcher + +In order for a watcher to invoke its notification handler in response to a +relevant signaling state change on a watched handle, it must first be armed. A +watcher may only be armed if none of its watched handles would elicit a +notification immediately once armed. + +In this case `a` is clearly not yet readable, so arming should succeed: + +``` c +MojoResult result = MojoArmWatcher(w, NULL, NULL, NULL, NULL); +``` + +Now we can write to `b` to make `a` readable: + +``` c +MojoWriteMessage(b, NULL, 0, NULL, 0, MOJO_WRITE_MESSAGE_NONE); +``` + +Eventually -- and in practice possibly before `MojoWriteMessage` even +returns -- this will cause `OnNotification` to be invoked on the calling thread +with the `context` value (*i.e.* 1234) that was given when the handle was added +to the watcher. + +The `result` parameter will be `MOJO_RESULT_OK` to indicate that the watched +signaling condition has been *satisfied*. If the watched condition had instead +become permanently *unsatisfiable* (*e.g.*, if `b` were instead closed), `result` +would instead indicate `MOJO_RESULT_FAILED_PRECONDITION`. + +**NOTE:** Immediately before a watcher decides to invoke its notification +handler, it automatically disarms itself to prevent another state change from +eliciting another notification. Therefore a watcher must be repeatedly rearmed +in order to continue dispatching signaling notifications. + +As noted above, arming a watcher may fail if any of the watched conditions for +a handle are already partially satisfied or fully unsatisfiable. In that case +the caller may provide buffers for `MojoArmWatcher` to store information about +a subset of the relevant watches which caused it to fail: + +``` c +// Provide some storage for information about watches that are already ready. +uint32_t num_ready_contexts = 4; +uintptr_t ready_contexts[4]; +MojoResult ready_results[4]; +struct MojoHandleSignalsStates ready_states[4]; +MojoResult result = MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states); +``` + +Because `a` is still readable this operation will fail with +`MOJO_RESULT_FAILED_PRECONDITION`. The input value of `num_ready_contexts` +informs `MojoArmWatcher` that it may store information regarding up to 4 watches +which currently prevent arming. In this case of course there is only one active +watch, so upon return we will see: + +* `num_ready_contexts` is `1`. +* `ready_contexts[0]` is `1234`. +* `ready_results[0]` is `MOJO_RESULT_OK` +* `ready_states[0]` is the last known signaling state of handle `a`. + +In other words the stored information mirrors what would have been the +notification handler's arguments if the watcher were allowed to arm and thus +notify immediately. + +### Cancelling a Watch + +There are three ways a watch can be cancelled: + +* The watched handle is closed +* The watcher handle is closed (in which case all of its watches are cancelled.) +* `MojoCancelWatch` is explicitly called for a given `context`. + +In the above example this means any of the following operations will cancel the +watch on `a`: + +``` c +// Close the watched handle... +MojoClose(a); + +// OR close the watcher handle... +MojoClose(w); + +// OR explicitly cancel. +MojoResult result = MojoCancelWatch(w, 1234); +``` + +In every case the watcher's notification handler is invoked for the cancelled +watch(es) regardless of whether or not the watcher is or was armed at the time. +The notification handler receives a `result` of `MOJO_RESULT_CANCELLED` for +these notifications, and this is guaranteed to be the final notification for any +given watch context. + +### Practical Watch Context Usage + +It is common and probably wise to treat a watch's `context` value as an opaque +pointer to some thread-safe state associated in some way with the handle being +watched. Here's a small example which uses a single watcher to watch both ends +of a message pipe and accumulate a count of messages received at each end. + +``` c +// NOTE: For the sake of simplicity this example code is not in fact +// thread-safe. As long as there's only one thread running in the process and +// no external process connections, this is fine. + +struct WatchedHandleState { + MojoHandle watcher; + MojoHandle handle; + int message_count; +}; + +void OnNotification(uintptr_t context, + MojoResult result, + MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags) { + struct WatchedHandleState* state = (struct WatchedHandleState*)(context); + MojoResult rv; + + if (result == MOJO_RESULT_CANCELLED) { + // Cancellation is always the last notification and is guaranteed to + // eventually happen for every context, assuming no handles are leaked. We + // treat this as an opportunity to free the WatchedHandleState. + free(state); + return; + } + + if (result == MOJO_RESULT_FAILED_PRECONDITION) { + // No longer readable, i.e. the other handle must have been closed. Better + // cancel. Note that we could also just call MojoClose(state->watcher) here + // since we know |context| is its only registered watch. + MojoCancelWatch(state->watcher, context); + return; + } + + // This is the only handle watched by the watcher, so as long as we can't arm + // the watcher we know something's up with this handle. Try to read messages + // until we can successfully arm again or something goes terribly wrong. + while (MojoArmWatcher(state->watcher, NULL, NULL, NULL, NULL) == + MOJO_RESULT_FAILED_PRECONDITION) { + rv = MojoReadMessageNew(state->handle, NULL, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD); + if (rv == MOJO_RESULT_OK) { + state->message_count++; + } else if (rv == MOJO_RESULT_FAILED_PRECONDITION) { + MojoCancelWatch(state->watcher, context); + return; + } + } +} + +MojoHandle a, b; +MojoCreateMessagePipe(NULL, &a, &b); + +MojoHandle a_watcher, b_watcher; +MojoCreateWatcher(&OnNotification, &a_watcher); +MojoCreateWatcher(&OnNotification, &b_watcher) + +struct WatchedHandleState* a_state = malloc(sizeof(struct WatchedHandleState)); +a_state->watcher = a_watcher; +a_state->handle = a; +a_state->message_count = 0; + +struct WatchedHandleState* b_state = malloc(sizeof(struct WatchedHandleState)); +b_state->watcher = b_watcher; +b_state->handle = b; +b_state->message_count = 0; + +MojoWatch(a_watcher, a, MOJO_HANDLE_SIGNAL_READABLE, (uintptr_t)a_state); +MojoWatch(b_watcher, b, MOJO_HANDLE_SIGNAL_READABLE, (uintptr_t)b_state); + +MojoArmWatcher(a_watcher, NULL, NULL, NULL, NULL); +MojoArmWatcher(b_watcher, NULL, NULL, NULL, NULL); +``` + +Now any writes to `a` will increment `message_count` in `b_state`, and any +writes to `b` will increment `message_count` in `a_state`. + +If either `a` or `b` is closed, both watches will be cancelled - one because +watch cancellation is implicit in handle closure, and the other because its +watcher will eventually detect that the handle is no longer readable. diff --git a/mojo/public/c/system/buffer.h b/mojo/public/c/system/buffer.h index 0f02737..285e0d7 100644 --- a/mojo/public/c/system/buffer.h +++ b/mojo/public/c/system/buffer.h @@ -2,10 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// This file contains types/constants and functions specific to buffers (and in -// particular shared buffers). -// TODO(vtl): Reorganize this file (etc.) to separate general buffer functions -// from (shared) buffer creation. +// This file contains types/constants and functions specific to shared buffers. // // Note: This header should be compilable as C. @@ -20,15 +17,13 @@ // |MojoCreateSharedBufferOptions|: Used to specify creation parameters for a // shared buffer to |MojoCreateSharedBuffer()|. +// // |uint32_t struct_size|: Set to the size of the // |MojoCreateSharedBufferOptions| struct. (Used to allow for future // extensions.) +// // |MojoCreateSharedBufferOptionsFlags flags|: Reserved for future use. // |MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE|: No flags; default mode. -// -// TODO(vtl): Maybe add a flag to indicate whether the memory should be -// executable or not? -// TODO(vtl): Also a flag for discardable (ashmem-style) buffers. typedef uint32_t MojoCreateSharedBufferOptionsFlags; @@ -50,17 +45,21 @@ MOJO_STATIC_ASSERT(sizeof(MojoCreateSharedBufferOptions) == 8, // |MojoDuplicateBufferHandleOptions|: Used to specify parameters in duplicating // access to a shared buffer to |MojoDuplicateBufferHandle()|. +// // |uint32_t struct_size|: Set to the size of the // |MojoDuplicateBufferHandleOptions| struct. (Used to allow for future // extensions.) -// |MojoDuplicateBufferHandleOptionsFlags flags|: Reserved for future use. +// +// |MojoDuplicateBufferHandleOptionsFlags flags|: Flags to control the +// behavior of |MojoDuplicateBufferHandle()|. May be some combination of +// the following: +// // |MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE|: No flags; default -// mode. +// mode. // |MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY|: The duplicate -// shared buffer can only be mapped read-only. A read-only duplicate can -// only be created before the buffer is passed over a message pipe. -// -// TODO(vtl): Add flags to remove writability (and executability)? Also, COW? +// shared buffer can only be mapped read-only. A read-only duplicate +// may only be created before any handles to the buffer are passed +// over a message pipe. typedef uint32_t MojoDuplicateBufferHandleOptionsFlags; @@ -102,18 +101,15 @@ extern "C" { // label for pointer parameters. // Creates a buffer of size |num_bytes| bytes that can be shared between -// applications (by duplicating the handle -- see |MojoDuplicateBufferHandle()| -// -- and passing it over a message pipe). To access the buffer, one must call -// |MojoMapBuffer()|. +// processes. The returned handle may be duplicated any number of times by +// |MojoDuplicateBufferHandle()|. +// +// To access the buffer's storage, one must call |MojoMapBuffer()|. // // |options| may be set to null for a shared buffer with the default options. // // On success, |*shared_buffer_handle| will be set to the handle for the shared -// buffer. (On failure, it is not modified.) -// -// Note: While more than |num_bytes| bytes may apparently be -// available/visible/readable/writable, trying to use those extra bytes is -// undefined behavior. +// buffer. On failure it is not modified. // // Returns: // |MOJO_RESULT_OK| on success. @@ -128,17 +124,14 @@ MOJO_SYSTEM_EXPORT MojoResult MojoCreateSharedBuffer( uint64_t num_bytes, // In. MojoHandle* shared_buffer_handle); // Out. -// Duplicates the handle |buffer_handle| to a buffer. This creates another -// handle (returned in |*new_buffer_handle| on success), which can then be sent -// to another application over a message pipe, while retaining access to the -// |buffer_handle| (and any mappings that it may have). +// Duplicates the handle |buffer_handle| as a new shared buffer handle. On +// success this returns the new handle in |*new_buffer_handle|. A shared buffer +// remains allocated as long as there is at least one shared buffer handle +// referencing it in at least one process in the system. // // |options| may be set to null to duplicate the buffer handle with the default // options. // -// On success, |*shared_buffer_handle| will be set to the handle for the new -// buffer handle. (On failure, it is not modified.) -// // Returns: // |MOJO_RESULT_OK| on success. // |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., @@ -152,17 +145,16 @@ MOJO_SYSTEM_EXPORT MojoResult MojoDuplicateBufferHandle( // Maps the part (at offset |offset| of length |num_bytes|) of the buffer given // by |buffer_handle| into memory, with options specified by |flags|. |offset + // num_bytes| must be less than or equal to the size of the buffer. On success, -// |*buffer| points to memory with the requested part of the buffer. (On -// failure, it is not modified.) +// |*buffer| points to memory with the requested part of the buffer. On +// failure |*buffer| it is not modified. // -// A single buffer handle may have multiple active mappings (possibly depending -// on the buffer type). The permissions (e.g., writable or executable) of the -// returned memory may depend on the properties of the buffer and properties -// attached to the buffer handle as well as |flags|. +// A single buffer handle may have multiple active mappings The permissions +// (e.g., writable or executable) of the returned memory depend on th +// properties of the buffer and properties attached to the buffer handle, as +// well as |flags|. // -// Note: Though data outside the specified range may apparently be -// available/visible/readable/writable, trying to use those extra bytes is -// undefined behavior. +// A mapped buffer must eventually be unmapped by calling |MojoUnmapBuffer()| +// with the value of |*buffer| returned by this function. // // Returns: // |MOJO_RESULT_OK| on success. @@ -179,8 +171,9 @@ MOJO_SYSTEM_EXPORT MojoResult MojoMapBuffer(MojoHandle buffer_handle, // Unmaps a buffer pointer that was mapped by |MojoMapBuffer()|. |buffer| must // have been the result of |MojoMapBuffer()| (not some other pointer inside -// the mapped memory), and the entire mapping will be removed (partial unmapping -// is not supported). A mapping may only be unmapped once. +// the mapped memory), and the entire mapping will be removed. +// +// A mapping may only be unmapped once. // // Returns: // |MOJO_RESULT_OK| on success. diff --git a/mojo/public/c/system/core.h b/mojo/public/c/system/core.h index 77d452b..03c0652 100644 --- a/mojo/public/c/system/core.h +++ b/mojo/public/c/system/core.h @@ -17,6 +17,6 @@ #include "mojo/public/c/system/platform_handle.h" #include "mojo/public/c/system/system_export.h" #include "mojo/public/c/system/types.h" -#include "mojo/public/c/system/wait_set.h" +#include "mojo/public/c/system/watcher.h" #endif // MOJO_PUBLIC_C_SYSTEM_CORE_H_ diff --git a/mojo/public/c/system/data_pipe.h b/mojo/public/c/system/data_pipe.h index 4a7dcef..f51e36c 100644 --- a/mojo/public/c/system/data_pipe.h +++ b/mojo/public/c/system/data_pipe.h @@ -17,14 +17,19 @@ // |MojoCreateDataPipeOptions|: Used to specify creation parameters for a data // pipe to |MojoCreateDataPipe()|. +// // |uint32_t struct_size|: Set to the size of the |MojoCreateDataPipeOptions| // struct. (Used to allow for future extensions.) +// // |MojoCreateDataPipeOptionsFlags flags|: Used to specify different modes of -// operation. -// |MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE|: No flags; default mode. +// operation. May be some combination of the following values: +// +// |MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE|: No flags; default mode. +// // |uint32_t element_num_bytes|: The size of an element, in bytes. All // transactions and buffers will consist of an integral number of // elements. Must be nonzero. +// // |uint32_t capacity_num_bytes|: The capacity of the data pipe, in number of // bytes; must be a multiple of |element_num_bytes|. The data pipe will // always be able to queue AT LEAST this much data. Set to zero to opt for @@ -52,10 +57,11 @@ MOJO_STATIC_ASSERT(sizeof(MojoCreateDataPipeOptions) == 16, "MojoCreateDataPipeOptions has wrong size"); // |MojoWriteDataFlags|: Used to specify different modes to |MojoWriteData()| -// and |MojoBeginWriteData()|. +// and |MojoBeginWriteData()|. May be some combination of the following values: +// // |MOJO_WRITE_DATA_FLAG_NONE| - No flags; default mode. // |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| - Write either all the elements -// requested or none of them. +// requested or none of them. typedef uint32_t MojoWriteDataFlags; @@ -68,10 +74,12 @@ const MojoWriteDataFlags MOJO_WRITE_DATA_FLAG_ALL_OR_NONE = 1 << 0; #endif // |MojoReadDataFlags|: Used to specify different modes to |MojoReadData()| and -// |MojoBeginReadData()|. +// |MojoBeginReadData()|. May be some combination of the following values: +// // |MOJO_READ_DATA_FLAG_NONE| - No flags; default mode. // |MOJO_READ_DATA_FLAG_ALL_OR_NONE| - Read (or discard) either the requested -// number of elements or none. +// number of elements or none. NOTE: This flag is not currently supported +// by |MojoBeginReadData()|. // |MOJO_READ_DATA_FLAG_DISCARD| - Discard (up to) the requested number of // elements. // |MOJO_READ_DATA_FLAG_QUERY| - Query the number of elements available to @@ -106,9 +114,10 @@ extern "C" { // label for pointer parameters. // Creates a data pipe, which is a unidirectional communication channel for -// unframed data, with the given options. Data is unframed, but must come as -// (multiples of) discrete elements, of the size given in |options|. See -// |MojoCreateDataPipeOptions| for a description of the different options +// unframed data. Data must be read and written in multiples of discrete +// discrete elements of size given in |options|. +// +// See |MojoCreateDataPipeOptions| for a description of the different options // available for data pipes. // // |options| may be set to null for a data pipe with the default options (which @@ -117,7 +126,7 @@ extern "C" { // // On success, |*data_pipe_producer_handle| will be set to the handle for the // producer and |*data_pipe_consumer_handle| will be set to the handle for the -// consumer. (On failure, they are not modified.) +// consumer. On failure they are not modified. // // Returns: // |MOJO_RESULT_OK| on success. @@ -132,29 +141,22 @@ MOJO_SYSTEM_EXPORT MojoResult MojoCreateDataPipe( MojoHandle* data_pipe_producer_handle, // Out. MojoHandle* data_pipe_consumer_handle); // Out. -// Writes the given data to the data pipe producer given by -// |data_pipe_producer_handle|. |elements| points to data of size |*num_bytes|; -// |*num_bytes| should be a multiple of the data pipe's element size. If -// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| is set in |flags|, either all the data -// will be written or none is. +// Writes the data pipe producer given by |data_pipe_producer_handle|. // -// On success, |*num_bytes| is set to the amount of data that was actually -// written. +// |elements| points to data of size |*num_bytes|; |*num_bytes| must be a +// multiple of the data pipe's element size. If +// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| is set in |flags|, either all the data +// is written (if enough write capacity is available) or none is. // -// Note: If the data pipe has the "may discard" option flag (specified on -// creation), this will discard as much data as required to write the given -// data, starting with the earliest written data that has not been consumed. -// However, even with "may discard", if |*num_bytes| is greater than the data -// pipe's capacity (and |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| is not set), this -// will write the maximum amount possible (namely, the data pipe's capacity) and -// set |*num_bytes| to that amount. It will *not* discard data from |elements|. +// On success |*num_bytes| is set to the amount of data that was actually +// written. On failure it is unmodified. // // Returns: // |MOJO_RESULT_OK| on success. // |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., // |data_pipe_producer_dispatcher| is not a handle to a data pipe // producer or |*num_bytes| is not a multiple of the data pipe's element -// size). +// size.) // |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe consumer handle has been // closed. // |MOJO_RESULT_OUT_OF_RANGE| if |flags| has @@ -166,8 +168,6 @@ MOJO_SYSTEM_EXPORT MojoResult MojoCreateDataPipe( // |MOJO_RESULT_SHOULD_WAIT| if no data can currently be written (and the // consumer is still open) and |flags| does *not* have // |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set. -// -// TODO(vtl): Should there be a way of querying how much data can be written? MOJO_SYSTEM_EXPORT MojoResult MojoWriteData(MojoHandle data_pipe_producer_handle, const void* elements, @@ -175,40 +175,26 @@ MOJO_SYSTEM_EXPORT MojoResult MojoWriteDataFlags flags); // Begins a two-phase write to the data pipe producer given by -// |data_pipe_producer_handle|. On success, |*buffer| will be a pointer to which -// the caller can write |*buffer_num_bytes| bytes of data. If flags has -// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set, then the output value -// |*buffer_num_bytes| will be at least as large as its input value, which must -// also be a multiple of the element size (if |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| -// is not set, the input value of |*buffer_num_bytes| is ignored). +// |data_pipe_producer_handle|. On success |*buffer| will be a pointer to which +// the caller can write up to |*buffer_num_bytes| bytes of data. // // During a two-phase write, |data_pipe_producer_handle| is *not* writable. -// E.g., if another thread tries to write to it, it will get |MOJO_RESULT_BUSY|; -// that thread can then wait for |data_pipe_producer_handle| to become writable -// again. +// If another caller tries to write to it by calling |MojoWriteData()| or +// |MojoBeginWriteData()|, their request will fail with |MOJO_RESULT_BUSY|. // -// When |MojoBeginWriteData()| returns MOJO_RESULT_OK, and the caller has -// finished writing data to |*buffer|, it should call |MojoEndWriteData()| to -// specify the amount written and to complete the two-phase write. -// |MojoEndWriteData()| need not be called for other return values. -// -// Note: If the data pipe has the "may discard" option flag (specified on -// creation) and |flags| has |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set, this may -// discard some data. +// If |MojoBeginWriteData()| returns MOJO_RESULT_OK and once the caller has +// finished writing data to |*buffer|, |MojoEndWriteData()| must be called to +// indicate the amount of data actually written and to complete the two-phase +// write operation. |MojoEndWriteData()| need not be called when +// |MojoBeginWriteData()| fails. // // Returns: // |MOJO_RESULT_OK| on success. // |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., // |data_pipe_producer_handle| is not a handle to a data pipe producer or -// flags has |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set and -// |*buffer_num_bytes| is not a multiple of the element size). +// flags has |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set. // |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe consumer handle has been // closed. -// |MOJO_RESULT_OUT_OF_RANGE| if |flags| has -// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set and the required amount of data -// (specified by |*buffer_num_bytes|) cannot be written contiguously at -// this time. (Note that there may be space available for the required -// amount of data, but the "next" write position may not be large enough.) // |MOJO_RESULT_BUSY| if there is already a two-phase write ongoing with // |data_pipe_producer_handle| (i.e., |MojoBeginWriteData()| has been // called, but not yet the matching |MojoEndWriteData()|). @@ -220,17 +206,16 @@ MOJO_SYSTEM_EXPORT MojoResult uint32_t* buffer_num_bytes, // In/out. MojoWriteDataFlags flags); -// Ends a two-phase write to the data pipe producer given by -// |data_pipe_producer_handle| that was begun by a call to -// |MojoBeginWriteData()| on the same handle. |num_bytes_written| should -// indicate the amount of data actually written; it must be less than or equal -// to the value of |*buffer_num_bytes| output by |MojoBeginWriteData()| and must -// be a multiple of the element size. The buffer given by |*buffer| from -// |MojoBeginWriteData()| must have been filled with exactly |num_bytes_written| -// bytes of data. +// Ends a two-phase write that was previously initiated by +// |MojoBeginWriteData()| for the same |data_pipe_producer_handle|. +// +// |num_bytes_written| must indicate the number of bytes actually written into +// the two-phase write buffer. It must be less than or equal to the value of +// |*buffer_num_bytes| output by |MojoBeginWriteData()|, and it must be a +// multiple of the data pipe's element size. // // On failure, the two-phase write (if any) is ended (so the handle may become -// writable again, if there's space available) but no data written to |*buffer| +// writable again if there's space available) but no data written to |*buffer| // is "put into" the data pipe. // // Returns: @@ -297,36 +282,27 @@ MOJO_SYSTEM_EXPORT MojoResult MojoReadData(MojoHandle data_pipe_consumer_handle, // Begins a two-phase read from the data pipe consumer given by // |data_pipe_consumer_handle|. On success, |*buffer| will be a pointer from -// which the caller can read |*buffer_num_bytes| bytes of data. If flags has -// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set, then the output value -// |*buffer_num_bytes| will be at least as large as its input value, which must -// also be a multiple of the element size (if |MOJO_READ_DATA_FLAG_ALL_OR_NONE| -// is not set, the input value of |*buffer_num_bytes| is ignored). |flags| must -// not have |MOJO_READ_DATA_FLAG_DISCARD|, |MOJO_READ_DATA_FLAG_QUERY|, or -// |MOJO_READ_DATA_FLAG_PEEK| set. +// which the caller can read up to |*buffer_num_bytes| bytes of data. // // During a two-phase read, |data_pipe_consumer_handle| is *not* readable. -// E.g., if another thread tries to read from it, it will get -// |MOJO_RESULT_BUSY|; that thread can then wait for |data_pipe_consumer_handle| -// to become readable again. +// If another caller tries to read from it by calling |MojoReadData()| or +// |MojoBeginReadData()|, their request will fail with |MOJO_RESULT_BUSY|. // -// Once the caller has finished reading data from |*buffer|, it should call -// |MojoEndReadData()| to specify the amount read and to complete the two-phase -// read. +// Once the caller has finished reading data from |*buffer|, |MojoEndReadData()| +// must be called to indicate the number of bytes read and to complete the +// two-phase read operation. +// +// |flags| must not have |MOJO_READ_DATA_FLAG_DISCARD|, +// |MOJO_READ_DATA_FLAG_QUERY|, |MOJO_READ_DATA_FLAG_PEEK|, or +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set. // // Returns: // |MOJO_RESULT_OK| on success. // |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., // |data_pipe_consumer_handle| is not a handle to a data pipe consumer, -// |flags| has |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set and -// |*buffer_num_bytes| is not a multiple of the element size, or |flags| -// has invalid flags set). +// or |flags| has invalid flags set.) // |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe producer handle has been // closed. -// |MOJO_RESULT_OUT_OF_RANGE| if |flags| has |MOJO_READ_DATA_FLAG_ALL_OR_NONE| -// set and the required amount of data (specified by |*buffer_num_bytes|) -// cannot be read from a contiguous buffer at this time. (Note that there -// may be the required amount of data, but it may not be contiguous.) // |MOJO_RESULT_BUSY| if there is already a two-phase read ongoing with // |data_pipe_consumer_handle| (i.e., |MojoBeginReadData()| has been // called, but not yet the matching |MojoEndReadData()|). diff --git a/mojo/public/c/system/functions.h b/mojo/public/c/system/functions.h index f0a23d9..d0656c6 100644 --- a/mojo/public/c/system/functions.h +++ b/mojo/public/c/system/functions.h @@ -19,14 +19,6 @@ extern "C" { #endif -// A callback used to notify watchers registered via |MojoWatch()|. Called when -// some watched signals are satisfied or become unsatisfiable. See the -// documentation for |MojoWatch()| for more details. -typedef void (*MojoWatchCallback)(uintptr_t context, - MojoResult result, - struct MojoHandleSignalsState signals_state, - MojoWatchNotificationFlags flags); - // Note: Pointer parameters that are labelled "optional" may be null (at least // under some circumstances). Non-const pointer parameters are also labeled // "in", "out", or "in/out", to indicate how they are used. (Note that how/if @@ -50,161 +42,24 @@ MOJO_SYSTEM_EXPORT MojoTimeTicks MojoGetTimeTicksNow(void); // // Concurrent operations on |handle| may succeed (or fail as usual) if they // happen before the close, be cancelled with result |MOJO_RESULT_CANCELLED| if -// they properly overlap (this is likely the case with |MojoWait()|, etc.), or -// fail with |MOJO_RESULT_INVALID_ARGUMENT| if they happen after. +// they properly overlap (this is likely the case with watchers), or fail with +// |MOJO_RESULT_INVALID_ARGUMENT| if they happen after. MOJO_SYSTEM_EXPORT MojoResult MojoClose(MojoHandle handle); -// Waits on the given handle until one of the following happens: -// - A signal indicated by |signals| is satisfied. -// - It becomes known that no signal indicated by |signals| will ever be -// satisfied. (See the description of the |MOJO_RESULT_CANCELLED| and -// |MOJO_RESULT_FAILED_PRECONDITION| return values below.) -// - Until |deadline| has passed. -// -// If |deadline| is |MOJO_DEADLINE_INDEFINITE|, this will wait "forever" (until -// one of the other wait termination conditions is satisfied). If |deadline| is -// 0, this will return |MOJO_RESULT_DEADLINE_EXCEEDED| only if one of the other -// termination conditions (e.g., a signal is satisfied, or all signals are -// unsatisfiable) is not already satisfied. -// -// |signals_state| (optional): See documentation for |MojoHandleSignalsState|. -// -// Returns: -// |MOJO_RESULT_OK| if some signal in |signals| was satisfied (or is already -// satisfied). -// |MOJO_RESULT_CANCELLED| if |handle| was closed (necessarily from another -// thread) during the wait. -// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle (e.g., if -// it has already been closed). The |signals_state| value is unchanged. -// |MOJO_RESULT_DEADLINE_EXCEEDED| if the deadline has passed without any of -// the signals being satisfied. -// |MOJO_RESULT_FAILED_PRECONDITION| if it becomes known that none of the -// signals in |signals| can ever be satisfied (e.g., when waiting on one -// end of a message pipe and the other end is closed). -// -// If there are multiple waiters (on different threads, obviously) waiting on -// the same handle and signal, and that signal becomes satisfied, all waiters -// will be awoken. -MOJO_SYSTEM_EXPORT MojoResult -MojoWait(MojoHandle handle, - MojoHandleSignals signals, - MojoDeadline deadline, - struct MojoHandleSignalsState* signals_state); // Optional out. - -// Waits on |handles[0]|, ..., |handles[num_handles-1]| until: -// - (At least) one handle satisfies a signal indicated in its respective -// |signals[0]|, ..., |signals[num_handles-1]|. -// - It becomes known that no signal in some |signals[i]| will ever be -// satisfied. -// - |deadline| has passed. -// -// This means that |MojoWaitMany()| behaves as if |MojoWait()| were called on -// each handle/signals pair simultaneously, completing when the first -// |MojoWait()| would complete. -// -// See |MojoWait()| for more details about |deadline|. -// -// |result_index| (optional) is used to return the index of the handle that -// caused the call to return. For example, the index |i| (from 0 to -// |num_handles-1|) if |handle[i]| satisfies a signal from |signals[i]|. You -// must manually initialize this to a suitable sentinel value (e.g. -1) -// before you make this call because this value is not updated if there is -// no specific handle that causes the function to return. Pass null if you -// don't need this value to be returned. -// -// |signals_states| (optional) points to an array of size |num_handles| of -// MojoHandleSignalsState. See |MojoHandleSignalsState| for more details -// about the meaning of each array entry. This array is not an atomic -// snapshot. The array will be updated if the function does not return -// |MOJO_RESULT_INVALID_ARGUMENT| or |MOJO_RESULT_RESOURCE_EXHAUSTED|. -// -// Returns: -// |MOJO_RESULT_CANCELLED| if some |handle[i]| was closed (necessarily from -// another thread) during the wait. -// |MOJO_RESULT_RESOURCE_EXHAUSTED| if there are too many handles. The -// |signals_state| array is unchanged. -// |MOJO_RESULT_INVALID_ARGUMENT| if some |handle[i]| is not a valid handle -// (e.g., if it is zero or if it has already been closed). The -// |signals_state| array is unchanged. -// |MOJO_RESULT_DEADLINE_EXCEEDED| if the deadline has passed without any of -// handles satisfying any of its signals. -// |MOJO_RESULT_FAILED_PRECONDITION| if it is or becomes impossible that SOME -// |handle[i]| will ever satisfy any of the signals in |signals[i]|. -MOJO_SYSTEM_EXPORT MojoResult -MojoWaitMany(const MojoHandle* handles, - const MojoHandleSignals* signals, - uint32_t num_handles, - MojoDeadline deadline, - uint32_t* result_index, // Optional out - struct MojoHandleSignalsState* signals_states); // Optional out - -// Watches the given handle for one of the following events to happen: -// - A signal indicated by |signals| is satisfied. -// - It becomes known that no signal indicated by |signals| will ever be -// satisfied. (See the description of the |MOJO_RESULT_CANCELLED| and -// |MOJO_RESULT_FAILED_PRECONDITION| return values below.) -// - The handle is closed. -// -// |handle|: The handle to watch. Must be an open message pipe or data pipe -// handle. -// |signals|: The signals to watch for. -// |callback|: A function to be called any time one of the above events happens. -// The function must be safe to call from any thread at any time. -// |context|: User-provided context passed to |callback| when called. |context| -// is used to uniquely identify a registered watch and can be used to cancel -// the watch later using |MojoCancelWatch()|. -// -// Returns: -// |MOJO_RESULT_OK| if the watch has been successfully registered. Note that -// if the signals are already satisfied this may synchronously invoke -// |callback| before returning. -// |MOJO_RESULT_CANCELLED| if the watch was cancelled. In this case it is not -// necessary to explicitly call |MojoCancelWatch()|, and in fact it may be -// an error to do so as the handle may have been closed. -// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not an open message pipe -// handle. -// |MOJO_RESULT_FAILED_PRECONDITION| if it is already known that |signals| can -// never be satisfied. -// |MOJO_RESULT_ALREADY_EXISTS| if there is already a watch registered for -// the same combination of |handle| and |context|. -// -// Callback result codes: -// The callback may be called at any time on any thread with one of the -// following result codes to indicate various events: -// -// |MOJO_RESULT_OK| indicates that some signal in |signals| has been -// satisfied. -// |MOJO_RESULT_FAILED_PRECONDITION| indicates that no signals in |signals| -// can ever be satisfied again. -// |MOJO_RESULT_CANCELLED| indicates that the handle has been closed. In this -// case the watch is implicitly cancelled and there is no need to call -// |MojoCancelWatch()|. -MOJO_SYSTEM_EXPORT MojoResult -MojoWatch(MojoHandle handle, - MojoHandleSignals signals, - MojoWatchCallback callback, - uintptr_t context); - -// Cancels a handle watch corresponding to some prior call to |MojoWatch()|. -// -// NOTE: If the watch callback corresponding to |context| is currently running -// this will block until the callback completes execution. It is therefore -// illegal to call |MojoCancelWatch()| on a given |handle| and |context| from -// within the associated callback itself, as this will always deadlock. -// -// After |MojoCancelWatch()| function returns, the watch's associated callback -// will NEVER be called again by Mojo. +// Queries the last known signals state of a handle. // -// |context|: The same user-provided context given to some prior call to -// |MojoWatch()|. Only the watch corresponding to this context will be -// cancelled. +// Note that no guarantees can be made about the accuracy of the returned +// signals state by the time this returns, as other threads in the system may +// change the handle's state at any time. Use with appropriate discretion. // // Returns: -// |MOJO_RESULT_OK| if the watch corresponding to |context| was cancelled. -// |MOJO_RESULT_INVALID_ARGUMENT| if no watch was registered with |context| -// for the given |handle|, or if |handle| is invalid. +// |MOJO_RESULT_OK| on success. |*signals_state| is populated with the +// last known signals state of |handle|. +// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle or +// |signals_state| is null. MOJO_SYSTEM_EXPORT MojoResult -MojoCancelWatch(MojoHandle handle, uintptr_t context); +MojoQueryHandleSignalsState(MojoHandle handle, + struct MojoHandleSignalsState* signals_state); // Retrieves system properties. See the documentation for |MojoPropertyType| for // supported property types and their corresponding output value type. diff --git a/mojo/public/c/system/platform_handle.h b/mojo/public/c/system/platform_handle.h index 0b02357..7449c2e 100644 --- a/mojo/public/c/system/platform_handle.h +++ b/mojo/public/c/system/platform_handle.h @@ -45,12 +45,16 @@ const MojoPlatformHandleType MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE = 3; #define MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE ((MojoPlatformHandleType)3) #endif -// |MojoPlatformHandle|: A handle to an OS object. +// |MojoPlatformHandle|: A handle to a native platform object. +// // |uint32_t struct_size|: The size of this structure. Used for versioning // to allow for future extensions. +// // |MojoPlatformHandleType type|: The type of handle stored in |value|. +// // |uint64_t value|: The value of this handle. Ignored if |type| is -// MOJO_PLATFORM_HANDLE_TYPE_INVALID. +// MOJO_PLATFORM_HANDLE_TYPE_INVALID. Otherwise the meaning of this +// value depends on the value of |type|. // struct MOJO_ALIGNAS(8) MojoPlatformHandle { @@ -84,8 +88,9 @@ MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY = 1 << 0; ((MojoPlatformSharedBufferHandleFlags)1 << 0) #endif -// Wraps a generic platform handle as a Mojo handle which can be transferred -// over a message pipe. Takes ownership of the underlying platform object. +// Wraps a native platform handle as a Mojo handle which can be transferred +// over a message pipe. Takes ownership of the underlying native platform +// object. // // |platform_handle|: The platform handle to wrap. // @@ -103,12 +108,12 @@ MOJO_SYSTEM_EXPORT MojoResult MojoWrapPlatformHandle(const struct MojoPlatformHandle* platform_handle, MojoHandle* mojo_handle); // Out -// Unwraps a generic platform handle from a Mojo handle. If this call succeeds, -// ownership of the underlying platform object is bound to the returned platform -// handle and becomes the caller's responsibility. The Mojo handle is always -// closed regardless of success or failure. +// Unwraps a native platform handle from a Mojo handle. If this call succeeds, +// ownership of the underlying platform object is assumed by the caller. The +// The Mojo handle is always closed regardless of success or failure. // -// |mojo_handle|: The Mojo handle from which to unwrap the platform handle. +// |mojo_handle|: The Mojo handle from which to unwrap the native platform +// handle. // // Returns: // |MOJO_RESULT_OK| if the handle was successfully unwrapped. In this case @@ -119,11 +124,13 @@ MOJO_SYSTEM_EXPORT MojoResult MojoUnwrapPlatformHandle(MojoHandle mojo_handle, struct MojoPlatformHandle* platform_handle); // Out -// Wraps a platform shared buffer handle as a Mojo shared buffer handle which -// can be transferred over a message pipe. Takes ownership of the platform -// shared buffer handle. +// Wraps a native platform shared buffer handle as a Mojo shared buffer handle +// which can be used exactly like a shared buffer handle created by +// |MojoCreateSharedBuffer()| or |MojoDuplicateBufferHandle()|. // -// |platform_handle|: The platform handle to wrap. Must be a handle to a +// Takes ownership of the native platform shared buffer handle. +// +// |platform_handle|: The platform handle to wrap. Must be a native handle to a // shared buffer object. // |num_bytes|: The size of the shared buffer in bytes. // |flags|: Flags which influence the treatment of the shared buffer object. See @@ -148,11 +155,11 @@ MojoWrapPlatformSharedBufferHandle( MojoPlatformSharedBufferHandleFlags flags, MojoHandle* mojo_handle); // Out -// Unwraps a platform shared buffer handle from a Mojo shared buffer handle. -// If this call succeeds, ownership of the underlying shared buffer object is -// bound to the returned platform handle and becomes the caller's -// responsibility. The Mojo handle is always closed regardless of success or -// failure. +// Unwraps a native platform shared buffer handle from a Mojo shared buffer +// handle. If this call succeeds, ownership of the underlying shared buffer +// object is assumed by the caller. +// +// The Mojo handle is always closed regardless of success or failure. // // |mojo_handle|: The Mojo shared buffer handle to unwrap. // diff --git a/mojo/public/c/system/tests/BUILD.gn b/mojo/public/c/system/tests/BUILD.gn index 0dd7052..bace63c 100644 --- a/mojo/public/c/system/tests/BUILD.gn +++ b/mojo/public/c/system/tests/BUILD.gn @@ -18,6 +18,7 @@ source_set("tests") { deps = [ "//mojo/public/c/system", + "//mojo/public/cpp/system", "//testing/gtest", ] } diff --git a/mojo/public/c/system/tests/core_perftest.cc b/mojo/public/c/system/tests/core_perftest.cc index 5d4e56b..cab465b 100644 --- a/mojo/public/c/system/tests/core_perftest.cc +++ b/mojo/public/c/system/tests/core_perftest.cc @@ -12,6 +12,7 @@ #include "base/macros.h" #include "base/threading/simple_thread.h" +#include "mojo/public/cpp/system/wait.h" #include "mojo/public/cpp/test_support/test_support.h" #include "mojo/public/cpp/test_support/test_utils.h" #include "testing/gtest/include/gtest/gtest.h" @@ -85,8 +86,7 @@ class MessagePipeReaderThread : public base::SimpleThread { } if (result == MOJO_RESULT_SHOULD_WAIT) { - result = MojoWait(handle_, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, nullptr); + result = mojo::Wait(mojo::Handle(handle_), MOJO_HANDLE_SIGNAL_READABLE); if (result == MOJO_RESULT_OK) { // Go to the top of the loop to read again. continue; diff --git a/mojo/public/c/system/tests/core_unittest.cc b/mojo/public/c/system/tests/core_unittest.cc index 8a54380..a9da255 100644 --- a/mojo/public/c/system/tests/core_unittest.cc +++ b/mojo/public/c/system/tests/core_unittest.cc @@ -9,6 +9,7 @@ #include <stdint.h> #include <string.h> +#include "mojo/public/cpp/system/wait.h" #include "testing/gtest/include/gtest/gtest.h" namespace mojo { @@ -31,7 +32,6 @@ TEST(CoreTest, GetTimeTicksNow) { // Tests that everything that takes a handle properly recognizes it. TEST(CoreTest, InvalidHandle) { MojoHandle h0, h1; - MojoHandleSignals sig; char buffer[10] = {0}; uint32_t buffer_size; void* write_pointer; @@ -40,18 +40,8 @@ TEST(CoreTest, InvalidHandle) { // Close: EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(MOJO_HANDLE_INVALID)); - // Wait: - EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - MojoWait(MOJO_HANDLE_INVALID, ~MOJO_HANDLE_SIGNAL_NONE, 1000000, - nullptr)); - - h0 = MOJO_HANDLE_INVALID; - sig = ~MOJO_HANDLE_SIGNAL_NONE; - EXPECT_EQ( - MOJO_RESULT_INVALID_ARGUMENT, - MojoWaitMany(&h0, &sig, 1, MOJO_DEADLINE_INDEFINITE, nullptr, nullptr)); - // Message pipe: + h0 = MOJO_HANDLE_INVALID; EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoWriteMessage(h0, buffer, 3, nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE)); @@ -98,23 +88,12 @@ TEST(CoreTest, BasicMessagePipe) { EXPECT_NE(h0, MOJO_HANDLE_INVALID); EXPECT_NE(h1, MOJO_HANDLE_INVALID); - // Shouldn't be readable, we haven't written anything. + // Shouldn't be readable, we haven't written anything. Should be writable. MojoHandleSignalsState state; - EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, - MojoWait(h0, MOJO_HANDLE_SIGNAL_READABLE, 0, &state)); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); - EXPECT_EQ(kSignalAll, state.satisfiable_signals); - - // Should be writable. - EXPECT_EQ(MOJO_RESULT_OK, - MojoWait(h0, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &state)); + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(h0, &state)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); EXPECT_EQ(kSignalAll, state.satisfiable_signals); - // Last parameter is optional. - EXPECT_EQ(MOJO_RESULT_OK, - MojoWait(h0, MOJO_HANDLE_SIGNAL_WRITABLE, 0, nullptr)); - // Try to read. buffer_size = static_cast<uint32_t>(sizeof(buffer)); EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, @@ -128,11 +107,12 @@ TEST(CoreTest, BasicMessagePipe) { 0, MOJO_WRITE_MESSAGE_FLAG_NONE)); // |h0| should be readable. - uint32_t result_index = 1; + size_t result_index = 1; MojoHandleSignalsState states[1]; sig = MOJO_HANDLE_SIGNAL_READABLE; - EXPECT_EQ(MOJO_RESULT_OK, MojoWaitMany(&h0, &sig, 1, MOJO_DEADLINE_INDEFINITE, - &result_index, states)); + Handle handle0(h0); + EXPECT_EQ(MOJO_RESULT_OK, + mojo::WaitMany(&handle0, &sig, 1, &result_index, states)); EXPECT_EQ(0u, result_index); EXPECT_EQ(kSignalReadadableWritable, states[0].satisfied_signals); @@ -147,24 +127,22 @@ TEST(CoreTest, BasicMessagePipe) { EXPECT_STREQ(kHello, buffer); // |h0| should no longer be readable. - EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, - MojoWait(h0, MOJO_HANDLE_SIGNAL_READABLE, 10, &state)); - + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(h0, &state)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); EXPECT_EQ(kSignalAll, state.satisfiable_signals); // Close |h0|. EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0)); - EXPECT_EQ(MOJO_RESULT_OK, - MojoWait(h1, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, &state)); + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h1), + MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state)); // |h1| should no longer be readable or writable. EXPECT_EQ( MOJO_RESULT_FAILED_PRECONDITION, - MojoWait(h1, MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, - 1000, &state)); + mojo::Wait(mojo::Handle(h1), + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + &state)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); @@ -188,18 +166,14 @@ TEST(CoreTest, BasicDataPipe) { // The consumer |hc| shouldn't be readable. MojoHandleSignalsState state; - EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, - MojoWait(hc, MOJO_HANDLE_SIGNAL_READABLE, 0, &state)); - + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(hc, &state)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_NONE, state.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, state.satisfiable_signals); // The producer |hp| should be writable. - EXPECT_EQ(MOJO_RESULT_OK, - MojoWait(hp, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &state)); - + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(hp, &state)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); @@ -223,11 +197,12 @@ TEST(CoreTest, BasicDataPipe) { MOJO_WRITE_MESSAGE_FLAG_NONE)); // |hc| should be(come) readable. - uint32_t result_index = 1; + size_t result_index = 1; MojoHandleSignalsState states[1]; sig = MOJO_HANDLE_SIGNAL_READABLE; - EXPECT_EQ(MOJO_RESULT_OK, MojoWaitMany(&hc, &sig, 1, MOJO_DEADLINE_INDEFINITE, - &result_index, states)); + Handle consumer_handle(hc); + EXPECT_EQ(MOJO_RESULT_OK, + mojo::WaitMany(&consumer_handle, &sig, 1, &result_index, states)); EXPECT_EQ(0u, result_index); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, @@ -256,9 +231,8 @@ TEST(CoreTest, BasicDataPipe) { EXPECT_EQ(MOJO_RESULT_OK, MojoClose(hp)); // |hc| should still be readable. - EXPECT_EQ(MOJO_RESULT_OK, - MojoWait(hc, MOJO_HANDLE_SIGNAL_PEER_CLOSED, - MOJO_DEADLINE_INDEFINITE, &state)); + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(hc), + MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); @@ -276,7 +250,7 @@ TEST(CoreTest, BasicDataPipe) { // |hc| should no longer be readable. EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - MojoWait(hc, MOJO_HANDLE_SIGNAL_READABLE, 1000, &state)); + mojo::Wait(mojo::Handle(hc), MOJO_HANDLE_SIGNAL_READABLE, &state)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); diff --git a/mojo/public/c/system/tests/core_unittest_pure_c.c b/mojo/public/c/system/tests/core_unittest_pure_c.c index fa3caa5..3164649 100644 --- a/mojo/public/c/system/tests/core_unittest_pure_c.c +++ b/mojo/public/c/system/tests/core_unittest_pure_c.c @@ -42,7 +42,6 @@ const char* MinimalCTest(void) { // at the top. (MSVS 2013 is more reasonable.) MojoTimeTicks ticks; MojoHandle handle0, handle1; - MojoHandleSignals signals; const char kHello[] = "hello"; char buffer[200] = {0}; uint32_t num_bytes; @@ -54,40 +53,15 @@ const char* MinimalCTest(void) { EXPECT_NE(MOJO_RESULT_OK, MojoClose(handle0)); EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - MojoWait(handle0, ~MOJO_HANDLE_SIGNAL_NONE, - MOJO_DEADLINE_INDEFINITE, NULL)); + MojoQueryHandleSignalsState(handle0, NULL)); handle1 = MOJO_HANDLE_INVALID; EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(NULL, &handle0, &handle1)); - signals = MOJO_HANDLE_SIGNAL_READABLE; - uint32_t result_index = 123; - struct MojoHandleSignalsState states[1]; - EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, - MojoWaitMany(&handle0, &signals, 1, 1, &result_index, states)); - - // "Deadline exceeded" doesn't apply to a single handle, so this should leave - // |result_index| untouched. - EXPECT_EQ(123u, result_index); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, states[0].satisfied_signals); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | - MOJO_HANDLE_SIGNAL_PEER_CLOSED, - states[0].satisfiable_signals); - EXPECT_EQ(MOJO_RESULT_OK, MojoWriteMessage(handle0, kHello, (uint32_t)sizeof(kHello), NULL, 0u, MOJO_WRITE_DATA_FLAG_NONE)); - struct MojoHandleSignalsState state; - EXPECT_EQ(MOJO_RESULT_OK, MojoWait(handle1, MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); - - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, - state.satisfied_signals); - EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | - MOJO_HANDLE_SIGNAL_PEER_CLOSED, - state.satisfiable_signals); - num_bytes = (uint32_t)sizeof(buffer); EXPECT_EQ(MOJO_RESULT_OK, MojoReadMessage(handle1, buffer, &num_bytes, NULL, NULL, MOJO_READ_MESSAGE_FLAG_NONE)); diff --git a/mojo/public/c/system/thunks.cc b/mojo/public/c/system/thunks.cc index d6bfd95..67c568f 100644 --- a/mojo/public/c/system/thunks.cc +++ b/mojo/public/c/system/thunks.cc @@ -22,23 +22,11 @@ MojoResult MojoClose(MojoHandle handle) { return g_thunks.Close(handle); } -MojoResult MojoWait(MojoHandle handle, - MojoHandleSignals signals, - MojoDeadline deadline, - struct MojoHandleSignalsState* signals_state) { - assert(g_thunks.Wait); - return g_thunks.Wait(handle, signals, deadline, signals_state); -} - -MojoResult MojoWaitMany(const MojoHandle* handles, - const MojoHandleSignals* signals, - uint32_t num_handles, - MojoDeadline deadline, - uint32_t* result_index, - struct MojoHandleSignalsState* signals_states) { - assert(g_thunks.WaitMany); - return g_thunks.WaitMany(handles, signals, num_handles, deadline, - result_index, signals_states); +MojoResult MojoQueryHandleSignalsState( + MojoHandle handle, + struct MojoHandleSignalsState* signals_state) { + assert(g_thunks.QueryHandleSignalsState); + return g_thunks.QueryHandleSignalsState(handle, signals_state); } MojoResult MojoCreateMessagePipe(const MojoCreateMessagePipeOptions* options, @@ -158,44 +146,33 @@ MojoResult MojoUnmapBuffer(void* buffer) { return g_thunks.UnmapBuffer(buffer); } -MojoResult MojoCreateWaitSet(MojoHandle* wait_set) { - assert(g_thunks.CreateWaitSet); - return g_thunks.CreateWaitSet(wait_set); -} - -MojoResult MojoAddHandle(MojoHandle wait_set, - MojoHandle handle, - MojoHandleSignals signals) { - assert(g_thunks.AddHandle); - return g_thunks.AddHandle(wait_set, handle, signals); -} - -MojoResult MojoRemoveHandle(MojoHandle wait_set, MojoHandle handle) { - assert(g_thunks.RemoveHandle); - return g_thunks.RemoveHandle(wait_set, handle); -} - -MojoResult MojoGetReadyHandles(MojoHandle wait_set, - uint32_t* count, - MojoHandle* handles, - MojoResult* results, - struct MojoHandleSignalsState* signals_states) { - assert(g_thunks.GetReadyHandles); - return g_thunks.GetReadyHandles(wait_set, count, handles, results, - signals_states); +MojoResult MojoCreateWatcher(MojoWatcherCallback callback, + MojoHandle* watcher_handle) { + assert(g_thunks.CreateWatcher); + return g_thunks.CreateWatcher(callback, watcher_handle); } -MojoResult MojoWatch(MojoHandle handle, +MojoResult MojoWatch(MojoHandle watcher_handle, + MojoHandle handle, MojoHandleSignals signals, - MojoWatchCallback callback, uintptr_t context) { assert(g_thunks.Watch); - return g_thunks.Watch(handle, signals, callback, context); + return g_thunks.Watch(watcher_handle, handle, signals, context); } -MojoResult MojoCancelWatch(MojoHandle handle, uintptr_t context) { +MojoResult MojoCancelWatch(MojoHandle watcher_handle, uintptr_t context) { assert(g_thunks.CancelWatch); - return g_thunks.CancelWatch(handle, context); + return g_thunks.CancelWatch(watcher_handle, context); +} + +MojoResult MojoArmWatcher(MojoHandle watcher_handle, + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) { + assert(g_thunks.ArmWatcher); + return g_thunks.ArmWatcher(watcher_handle, num_ready_contexts, ready_contexts, + ready_results, ready_signals_states); } MojoResult MojoFuseMessagePipes(MojoHandle handle0, MojoHandle handle1) { diff --git a/mojo/public/c/system/thunks.h b/mojo/public/c/system/thunks.h index 161faf1..e61bb46 100644 --- a/mojo/public/c/system/thunks.h +++ b/mojo/public/c/system/thunks.h @@ -13,43 +13,18 @@ #include "mojo/public/c/system/core.h" #include "mojo/public/c/system/system_export.h" -// The embedder needs to bind the basic Mojo Core functions of a DSO to those of -// the embedder when loading a DSO that is dependent on mojo_system. -// The typical usage would look like: -// base::ScopedNativeLibrary app_library( -// base::LoadNativeLibrary(app_path_, &error)); -// typedef MojoResult (*MojoSetSystemThunksFn)(MojoSystemThunks*); -// MojoSetSystemThunksFn mojo_set_system_thunks_fn = -// reinterpret_cast<MojoSetSystemThunksFn>(app_library.GetFunctionPointer( -// "MojoSetSystemThunks")); -// MojoSystemThunks system_thunks = MojoMakeSystemThunks(); -// size_t expected_size = mojo_set_system_thunks_fn(&system_thunks); -// if (expected_size > sizeof(MojoSystemThunks)) { -// LOG(ERROR) -// << "Invalid DSO. Expected MojoSystemThunks size: " -// << expected_size; -// break; -// } - -// Structure used to bind the basic Mojo Core functions of a DSO to those of -// the embedder. -// This is the ABI between the embedder and the DSO. It can only have new -// functions added to the end. No other changes are supported. +// Structure used to bind the basic Mojo Core functions to an embedder +// implementation. This is intended to eventually be used as a stable ABI +// between a Mojo embedder and some loaded application code, but for now it is +// still effectively safe to rearrange entries as needed. #pragma pack(push, 8) struct MojoSystemThunks { size_t size; // Should be set to sizeof(MojoSystemThunks). MojoTimeTicks (*GetTimeTicksNow)(); MojoResult (*Close)(MojoHandle handle); - MojoResult (*Wait)(MojoHandle handle, - MojoHandleSignals signals, - MojoDeadline deadline, - struct MojoHandleSignalsState* signals_state); - MojoResult (*WaitMany)(const MojoHandle* handles, - const MojoHandleSignals* signals, - uint32_t num_handles, - MojoDeadline deadline, - uint32_t* result_index, - struct MojoHandleSignalsState* signals_states); + MojoResult (*QueryHandleSignalsState)( + MojoHandle handle, + struct MojoHandleSignalsState* signals_state); MojoResult (*CreateMessagePipe)( const struct MojoCreateMessagePipeOptions* options, MojoHandle* message_pipe_handle0, @@ -103,23 +78,18 @@ struct MojoSystemThunks { void** buffer, MojoMapBufferFlags flags); MojoResult (*UnmapBuffer)(void* buffer); - - MojoResult (*CreateWaitSet)(MojoHandle* wait_set); - MojoResult (*AddHandle)(MojoHandle wait_set, - MojoHandle handle, - MojoHandleSignals signals); - MojoResult (*RemoveHandle)(MojoHandle wait_set, - MojoHandle handle); - MojoResult (*GetReadyHandles)(MojoHandle wait_set, - uint32_t* count, - MojoHandle* handles, - MojoResult* results, - struct MojoHandleSignalsState* signals_states); - MojoResult (*Watch)(MojoHandle handle, + MojoResult (*CreateWatcher)(MojoWatcherCallback callback, + MojoHandle* watcher_handle); + MojoResult (*Watch)(MojoHandle watcher_handle, + MojoHandle handle, MojoHandleSignals signals, - MojoWatchCallback callback, uintptr_t context); - MojoResult (*CancelWatch)(MojoHandle handle, uintptr_t context); + MojoResult (*CancelWatch)(MojoHandle watcher_handle, uintptr_t context); + MojoResult (*ArmWatcher)(MojoHandle watcher_handle, + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states); MojoResult (*FuseMessagePipes)(MojoHandle handle0, MojoHandle handle1); MojoResult (*WriteMessageNew)(MojoHandle message_pipe_handle, MojoMessageHandle message, diff --git a/mojo/public/c/system/types.h b/mojo/public/c/system/types.h index 7e02eeb..15813b6 100644 --- a/mojo/public/c/system/types.h +++ b/mojo/public/c/system/types.h @@ -14,9 +14,6 @@ #include "mojo/public/c/system/macros.h" -// TODO(vtl): Notes: Use of undefined flags will lead to undefined behavior -// (typically they'll be ignored), not necessarily an error. - // |MojoTimeTicks|: A time delta, in microseconds, the meaning of which is // source-dependent. @@ -80,12 +77,10 @@ const MojoHandle MOJO_HANDLE_INVALID = 0; // the resource being invalidated. // |MOJO_RESULT_SHOULD_WAIT| - The request cannot currently be completed // (e.g., if the data requested is not yet available). The caller should -// wait for it to be feasible using |MojoWait()| or |MojoWaitMany()|. +// wait for it to be feasible using a watcher. // // The codes from |MOJO_RESULT_OK| to |MOJO_RESULT_DATA_LOSS| come from // Google3's canonical error codes. -// -// TODO(vtl): Add a |MOJO_RESULT_UNSATISFIABLE|? typedef uint32_t MojoResult; @@ -141,11 +136,11 @@ const MojoDeadline MOJO_DEADLINE_INDEFINITE = static_cast<MojoDeadline>(-1); #define MOJO_DEADLINE_INDEFINITE ((MojoDeadline) - 1) #endif -// |MojoHandleSignals|: Used to specify signals that can be waited on for a +// |MojoHandleSignals|: Used to specify signals that can be watched for on a // handle (and which can be triggered), e.g., the ability to read or write to // the handle. -// |MOJO_HANDLE_SIGNAL_NONE| - No flags. |MojoWait()|, etc. will return -// |MOJO_RESULT_FAILED_PRECONDITION| if you attempt to wait on this. +// |MOJO_HANDLE_SIGNAL_NONE| - No flags. A registered watch will always fail +// to arm with |MOJO_RESULT_FAILED_PRECONDITION| when watching for this. // |MOJO_HANDLE_SIGNAL_READABLE| - Can read (e.g., a message) from the handle. // |MOJO_HANDLE_SIGNAL_WRITABLE| - Can write (e.g., a message) to the handle. // |MOJO_HANDLE_SIGNAL_PEER_CLOSED| - The peer handle is closed. @@ -171,8 +166,9 @@ const MojoHandleSignals MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE = 1 << 3; #define MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE ((MojoHandleSignals)1 << 3); #endif -// |MojoHandleSignalsState|: Returned by wait functions to indicate the -// signaling state of handles. Members are as follows: +// |MojoHandleSignalsState|: Returned by watch notification callbacks and +// |MojoQueryHandleSignalsState| functions to indicate the signaling state of +// handles. Members are as follows: // - |satisfied signals|: Bitmask of signals that were satisfied at some time // before the call returned. // - |satisfiable signals|: These are the signals that are possible to @@ -189,24 +185,25 @@ struct MOJO_ALIGNAS(4) MojoHandleSignalsState { MOJO_STATIC_ASSERT(sizeof(MojoHandleSignalsState) == 8, "MojoHandleSignalsState has wrong size"); -// |MojoWatchNotificationFlags|: Passed to a callback invoked as a result of -// signals being raised on a handle watched by |MojoWatch()|. May take the -// following values: -// |MOJO_WATCH_NOTIFICATION_FLAG_FROM_SYSTEM| - The callback is being invoked -// as a result of a system-level event rather than a direct API call from -// user code. This may be used as an indication that user code is safe to -// call without fear of reentry. +// |MojoWatcherNotificationFlags|: Passed to a callback invoked by a watcher +// when some observed signals are raised or a watched handle is closed. May take +// on any combination of the following values: +// +// |MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM| - The callback is being +// invoked as a result of a system-level event rather than a direct API +// call from user code. This may be used as an indication that user code +// is safe to call without fear of reentry. -typedef uint32_t MojoWatchNotificationFlags; +typedef uint32_t MojoWatcherNotificationFlags; #ifdef __cplusplus -const MojoWatchNotificationFlags MOJO_WATCH_NOTIFICATION_FLAG_NONE = 0; -const MojoWatchNotificationFlags MOJO_WATCH_NOTIFICATION_FLAG_FROM_SYSTEM = +const MojoWatcherNotificationFlags MOJO_WATCHER_NOTIFICATION_FLAG_NONE = 0; +const MojoWatcherNotificationFlags MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM = 1 << 0; #else -#define MOJO_WATCH_NOTIFICATION_FLAG_NONE ((MojoWatchNotificationFlags)0) -#define MOJO_WATCH_NOTIFICATION_FLAG_FROM_SYSTEM \ - ((MojoWatchNotificationFlags)1 << 0); +#define MOJO_WATCHER_NOTIFICATION_FLAG_NONE ((MojoWatcherNotificationFlags)0) +#define MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM \ + ((MojoWatcherNotificationFlags)1 << 0); #endif // |MojoPropertyType|: Property types that can be passed to |MojoGetProperty()| diff --git a/mojo/public/c/system/wait_set.h b/mojo/public/c/system/wait_set.h deleted file mode 100644 index 3a127f5..0000000 --- a/mojo/public/c/system/wait_set.h +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This file contains types/constants and functions specific to wait sets. -// -// Note: This header should be compilable as C. - -#ifndef MOJO_PUBLIC_C_SYSTEM_WAIT_SET_H_ -#define MOJO_PUBLIC_C_SYSTEM_WAIT_SET_H_ - -#include <stdint.h> - -#include "mojo/public/c/system/system_export.h" -#include "mojo/public/c/system/types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -// Note: See the comment in functions.h about the meaning of the "optional" -// label for pointer parameters. - -// Creates a wait set. A wait set is a way to efficiently wait on multiple -// handles. -// -// On success, |*wait_set_handle| will contain a handle to a wait set. -// -// Returns: -// |MOJO_RESULT_OK| on success. -// |MOJO_RESULT_INVALID_ARGUMENT| if |wait_set_handle| is null. -// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a process/system/quota/etc. limit has -// been reached. -MOJO_SYSTEM_EXPORT MojoResult MojoCreateWaitSet( - MojoHandle* wait_set_handle); // Out. - -// Adds a wait on |handle| to |wait_set_handle|. -// -// A handle can only be added to any given wait set once, but may be added to -// any number of different wait sets. To modify the signals being waited for, -// the handle must first be removed, and then added with the new signals. -// -// If a handle is closed while still in the wait set, it is implicitly removed -// from the set after being returned from |MojoGetReadyHandles()| with the -// result |MOJO_RESULT_CANCELLED|. -// -// It is safe to add a handle to a wait set while performing a wait on another -// thread. If the added handle already has its signals satisfied, the waiting -// thread will be woken. -// -// Returns: -// |MOJO_RESULT_OK| if |handle| was successfully added to |wait_set_handle|. -// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle, or -// |wait_set_handle| is not a valid wait set. -// |MOJO_RESULT_ALREADY_EXISTS| if |handle| already exists in -// |wait_set_handle|. -MOJO_SYSTEM_EXPORT MojoResult MojoAddHandle( - MojoHandle wait_set_handle, - MojoHandle handle, - MojoHandleSignals signals); - -// Removes |handle| from |wait_set_handle|. -// -// It is safe to remove a handle from a wait set while performing a wait on -// another thread. If handle has its signals satisfied while it is being -// removed, the waiting thread may be woken up, but no handle may be available -// when |MojoGetReadyHandles()| is called. -// -// Returns: -// |MOJO_RESULT_OK| if |handle| was successfully removed from -// |wait_set_handle|. -// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle, or -// |wait_set_handle| is not a valid wait set. -// |MOJO_RESULT_NOT_FOUND| if |handle| does not exist in |wait_set_handle|. -MOJO_SYSTEM_EXPORT MojoResult MojoRemoveHandle( - MojoHandle wait_set_handle, - MojoHandle handle); - -// Retrieves a set of ready handles from |wait_set_handle|. A handle is ready if -// at least of of the following is true: -// - The handle's signals are satisfied. -// - It becomes known that no signal for the handle will ever be satisfied. -// - The handle is closed. -// -// A wait set may have ready handles when it satisfies the -// |MOJO_HANDLE_SIGNAL_READABLE| signal. Since handles may be added and removed -// from a wait set concurrently, it is possible for a wait set to satisfy -// |MOJO_HANDLE_SIGNAL_READABLE|, but not have any ready handles when -// |MojoGetReadyHandles()| is called. These spurious wake-ups must be gracefully -// handled. -// -// |*count| on input, must contain the maximum number of ready handles to be -// returned. On output, it will contain the number of ready handles returned. -// -// |handles| must point to an array of size |*count| of |MojoHandle|. It will be -// populated with handles that are considered ready. The number of handles -// returned will be in |*count|. -// -// |results| must point to an array of size |*count| of |MojoResult|. It will be -// populated with the wait result of the corresponding handle in |*handles|. -// Care should be taken that if a handle is closed on another thread, the handle -// would be invalid, but the result may not be |MOJO_RESULT_CANCELLED|. See -// documentation for |MojoWait()| for possible results. -// -// |signals_state| (optional) if non-null, must point to an array of size -// |*count| of |MojoHandleSignalsState|. It will be populated with the signals -// state of the corresponding handle in |*handles|. See documentation for -// |MojoHandleSignalsState| for more details about the meaning of each array -// entry. The array will always be updated for every returned handle. -// -// Mojo signals and satisfiability are logically 'level-triggered'. Therefore, -// if a signal continues to be satisfied and is not removed from the wait set, -// subsequent calls to |MojoGetReadyHandles()| will return the same handle. -// -// If multiple handles have their signals satisfied, the order in which handles -// are returned is undefined. The same handle, if not removed, may be returned -// in consecutive calls. Callers must not rely on any fairness and handles -// could be starved if not acted on. -// -// Returns: -// |MOJO_RESULT_OK| if ready handles are available. -// |MOJO_RESULT_INVALID_ARGUMENT| if |wait_set_handle| is not a valid wait -// set, if |*count| is 0, or if either |count|, |handles|, or |results| is -// null. -// |MOJO_RESULT_SHOULD_WAIT| if there are no ready handles. -MOJO_SYSTEM_EXPORT MojoResult MojoGetReadyHandles( - MojoHandle wait_set_handle, - uint32_t* count, // In/out. - MojoHandle* handles, // Out. - MojoResult* results, // Out. - struct MojoHandleSignalsState *signals_states); // Optional out. - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // MOJO_PUBLIC_C_SYSTEM_WAIT_SET_H_ diff --git a/mojo/public/c/system/watcher.h b/mojo/public/c/system/watcher.h new file mode 100644 index 0000000..e32856b --- /dev/null +++ b/mojo/public/c/system/watcher.h @@ -0,0 +1,184 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_C_SYSTEM_WATCHER_H_ +#define MOJO_PUBLIC_C_SYSTEM_WATCHER_H_ + +#include <stdint.h> + +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// A callback used to notify watchers about events on their watched handles. +// +// See documentation for |MojoWatcherNotificationFlags| for details regarding +// the possible values of |flags|. +// +// See documentation for |MojoWatch()| for details regarding the other arguments +// this callback receives when called. +typedef void (*MojoWatcherCallback)(uintptr_t context, + MojoResult result, + struct MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags); + +// Creates a new watcher. +// +// Watchers are used to trigger arbitrary code execution when one or more +// handles change state to meet certain conditions. +// +// A newly registered watcher is initially disarmed and may be armed using +// |MojoArmWatcher()|. A watcher is also always disarmed immediately before any +// invocation of one or more notification callbacks in response to a single +// handle's state changing in some relevant way. +// +// Parameters: +// |callback|: The |MojoWatcherCallback| to invoke any time the watcher is +// notified of an event. See |MojoWatch()| for details regarding arguments +// passed to the callback. Note that this may be called from any arbitrary +// thread. +// |watcher_handle|: The address at which to store the MojoHandle +// corresponding to the new watcher if successfully created. +// +// Returns: +// |MOJO_RESULT_OK| if the watcher has been successfully created. +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a handle could not be allocated for +// this watcher. +MOJO_SYSTEM_EXPORT MojoResult MojoCreateWatcher(MojoWatcherCallback callback, + MojoHandle* watcher_handle); + +// Adds a watch to a watcher. This allows the watcher to fire notifications +// regarding state changes on the handle corresponding to the arguments given. +// +// Note that notifications for a given watch context are guaranteed to be +// mutually exclusive in execution: the callback will never be entered for a +// given context while another invocation of the callback is still executing for +// the same context. As a result it is generally a good idea to ensure that +// callbacks do as little work as necessary in order to process the +// notification. +// +// Parameters: +// |watcher_handle|: The watcher to which |handle| is to be added. +// |handle|: The handle to add to the watcher. +// |signals|: The signals to watch for on |handle|. +// |context|: An arbitrary context value given to any invocation of the +// watcher's callback when invoked as a result of some state change +// relevant to this combination of |handle| and |signals|. Must be +// unique within any given watcher. +// +// Callback parameters (see |MojoWatcherNotificationCallback| above): +// When the watcher invokes its callback as a result of some notification +// relevant to this watch operation, |context| receives the value given here +// and |signals_state| receives the last known signals state of this handle. +// +// |result| is one of the following: +// |MOJO_RESULT_OK| if at least one of the watched signals is satisfied. The +// watcher must be armed for this notification to fire. +// |MOJO_RESULT_FAILED_PRECONDITION| if all of the watched signals are +// permanently unsatisfiable. The watcher must be armed for this +// notification to fire. +// |MOJO_RESULT_CANCELLED| if the watch has been cancelled. The may occur if +// the watcher has been closed, the watched handle has been closed, or +// the watch for |context| has been explicitly cancelled. This is always +// the last result received for any given context, and it is guaranteed +// to be received exactly once per watch, regardless of how the watch +// was cancelled. +// +// Returns: +// |MOJO_RESULT_OK| if the handle is now being watched by the watcher. +// |MOJO_RESULT_INVALID_ARGUMENT| if |watcher_handle| is not a watcher handle, +// |handle| is not a valid message pipe or data pipe handle. +// |MOJO_RESULT_ALREADY_EXISTS| if the watcher already has a watch registered +// for the given value of |context| or for the given |handle|. +MOJO_SYSTEM_EXPORT MojoResult MojoWatch(MojoHandle watcher_handle, + MojoHandle handle, + MojoHandleSignals signals, + uintptr_t context); + +// Removes a watch from a watcher. +// +// This ensures that the watch is cancelled as soon as possible. Cancellation +// may be deferred (or may even block) an aritrarily long time if the watch is +// already dispatching one or more notifications. +// +// When cancellation is complete, the watcher's callback is invoked one final +// time for |context|, with the result |MOJO_RESULT_CANCELLED|. +// +// The same behavior can be elicted by either closing the watched handle +// associated with this context, or by closing |watcher_handle| itself. In the +// lastter case, all registered contexts on the watcher are implicitly cancelled +// in a similar fashion. +// +// Parameters: +// |watcher_handle|: The handle of the watcher from which to remove a watch. +// |context|: The context of the watch to be removed. +// +// Returns: +// |MOJO_RESULT_OK| if the watch has been cancelled. +// |MOJO_RESULT_INVALID_ARGUMENT| if |watcher_handle| is not a watcher handle. +// |MOJO_RESULT_NOT_FOUND| if there is no watch registered on this watcher for +// the given value of |context|. +MOJO_SYSTEM_EXPORT MojoResult MojoCancelWatch(MojoHandle watcher_handle, + uintptr_t context); + +// Arms a watcher, enabling a single future event on one of the watched handles +// to trigger a single notification for each relevant watch context associated +// with that handle. +// +// Parameters: +// |watcher_handle|: The handle of the watcher. +// |num_ready_contexts|: An address pointing to the number of elements +// available for storage in the remaining output buffers. Optional and +// only used on failure. See |MOJO_RESULT_FAILED_PRECONDITION| below for +// more details. +// |ready_contexts|: An output buffer for contexts corresponding to the +// watches which would have notified if the watcher were armed. Optional +// and only uesd on failure. See |MOJO_RESULT_FAILED_PRECONDITION| below +// for more details. +// |ready_results|: An output buffer for MojoResult values corresponding to +// each context in |ready_contexts|. Optional and only used on failure. +// See |MOJO_RESULT_FAILED_PRECONDITION| below for more details. +// |ready_signals_states|: An output buffer for |MojoHandleSignalsState| +// structures corresponding to each context in |ready_contexts|. Optional +// and only used on failure. See |MOJO_RESULT_FAILED_PRECONDITION| below +// for more details. +// +// Returns: +// |MOJO_RESULT_OK| if the watcher has been successfully armed. All arguments +// other than |watcher_handle| are ignored in this case. +// |MOJO_RESULT_NOT_FOUND| if the watcher does not have any registered watch +// contexts. All arguments other than |watcher_handle| are ignored in this +// case. +// |MOJO_RESULT_INVALID_ARGUMENT| if |watcher_handle| is not a valid watcher +// handle, or if |num_ready_contexts| is non-null but any of the output +// buffer paramters is null. +// |MOJO_RESULT_FAILED_PRECONDITION| if one or more watches would have +// notified immediately upon arming the watcher. If |num_handles| is +// non-null, this assumes there is enough space for |*num_handles| entries +// in each of the subsequent output buffer arguments. +// +// At most that many entries are placed in the output buffers, +// corresponding to the watches which would have signalled if the watcher +// had been armed successfully. The actual number of entries placed in the +// output buffers is written to |*num_ready_contexts| before returning. +// +// If more than (input) |*num_ready_contexts| watch contexts were ready to +// notify, the subset presented in output buffers is arbitrary, but the +// implementation makes a best effort to circulate the outputs across +// consecutive calls so that callers may reliably avoid handle starvation. +MOJO_SYSTEM_EXPORT MojoResult +MojoArmWatcher(MojoHandle watcher_handle, + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + struct MojoHandleSignalsState* ready_signals_states); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_WATCHER_H_ diff --git a/mojo/public/cpp/README.md b/mojo/public/cpp/README.md deleted file mode 100644 index d0f1238..0000000 --- a/mojo/public/cpp/README.md +++ /dev/null @@ -1,38 +0,0 @@ -Mojo Public C++ API -=================== - -This directory contains C++ language bindings for the Mojo Public API. - -A number of subdirectories provide wrappers for the lower-level C APIs (in -subdirectories of the same name, under mojo/public/c/). Typically, these -wrappers provide increased convenience and/or type-safety. - -Other subdirectories provide support (static) libraries of various sorts. In -this case, the organization is to have the public interface for the library -defined in header files in the subdirectory itself and the implementation of the -library at a lower level, under a lib (sub)subdirectory. A developer should be -able to substitute their own implementation of any such support library, and -expect other support libraries, which may depend on that library, to work -properly. - -Bindings --------- - -The bindings/ subdirectory contains a support (static) library needed by the -code generated by the bindings generator tool (in mojo/public/tools/bindings/), -which translates Mojo IDL (.mojom) files into idiomatic C++ (among other -languages). - -System ------- - -The system/ subdirectory contains C++ wrappers (and some additional helpers) of -the API defined in mojo/public/c/system/, which defines the basic, "core" API, -especially used to communicate with Mojo services. - -Test Support ------------- - -The test_support/ subdirectory contains C++ wrappers of the test-only API -defined in mojo/public/c/test_support/. It is not meant for general use by Mojo -applications. diff --git a/mojo/public/cpp/bindings/BUILD.gn b/mojo/public/cpp/bindings/BUILD.gn index 5c41384..bd87965 100644 --- a/mojo/public/cpp/bindings/BUILD.gn +++ b/mojo/public/cpp/bindings/BUILD.gn @@ -54,6 +54,7 @@ component("bindings") { "lib/associated_binding.cc", "lib/associated_group.cc", "lib/associated_group_controller.cc", + "lib/associated_interface_ptr.cc", "lib/associated_interface_ptr_state.h", "lib/binding_state.cc", "lib/binding_state.h", @@ -102,6 +103,7 @@ component("bindings") { "lib/string_serialization.h", "lib/string_traits_string16.cc", "lib/sync_call_restrictions.cc", + "lib/sync_event_watcher.cc", "lib/sync_handle_registry.cc", "lib/sync_handle_watcher.cc", "lib/template_util.h", @@ -137,6 +139,7 @@ component("bindings") { "strong_binding_set.h", "struct_ptr.h", "sync_call_restrictions.h", + "sync_event_watcher.h", "sync_handle_registry.h", "sync_handle_watcher.h", "thread_safe_interface_ptr.h", diff --git a/mojo/public/cpp/bindings/README.md b/mojo/public/cpp/bindings/README.md new file mode 100644 index 0000000..b37267a --- /dev/null +++ b/mojo/public/cpp/bindings/README.md @@ -0,0 +1,1231 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo C++ Bindings API +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview +The Mojo C++ Bindings API leverages the +[C++ System API](/mojo/public/cpp/system) to provide a more natural set of +primitives for communicating over Mojo message pipes. Combined with generated +code from the [Mojom IDL and bindings generator](/mojo/public/tools/bindings), +users can easily connect interface clients and implementations across arbitrary +intra- and inter-process bounaries. + +This document provides a detailed guide to bindings API usage with example code +snippets. For a detailed API references please consult the headers in +[//mojo/public/cpp/bindings](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/). + +## Getting Started + +When a Mojom IDL file is processed by the bindings generator, C++ code is +emitted in a series of `.h` and `.cc` files with names based on the input +`.mojom` file. Suppose we create the following Mojom file at +`//services/db/public/interfaces/db.mojom`: + +``` +module db.mojom; + +interface Table { + AddRow(int32 key, string data); +}; + +interface Database { + CreateTable(Table& table); +}; +``` + +And a GN target to generate the bindings in +`//services/db/public/interfaces/BUILD.gn`: + +``` +import("//mojo/public/tools/bindings/mojom.gni") + +mojom("interfaces") { + sources = [ + "db.mojom", + ] +} +``` + +If we then build this target: + +``` +ninja -C out/r services/db/public/interfaces +``` + +This will produce several generated source files, some of which are relevant to +C++ bindings. Two of these files are: + +``` +out/gen/services/business/public/interfaces/factory.mojom.cc +out/gen/services/business/public/interfaces/factory.mojom.h +``` + +You can include the above generated header in your sources in order to use the +definitions therein: + +``` cpp +#include "services/business/public/interfaces/factory.mojom.h" + +class TableImpl : public db::mojom::Table { + // ... +}; +``` + +This document covers the different kinds of definitions generated by Mojom IDL +for C++ consumers and how they can effectively be used to communicate across +message pipes. + +*** note +**NOTE:** Using C++ bindings from within Blink code is typically subject to +special constraints which require the use of a different generated header. +For details, see [Blink Type Mapping](#Blink-Type-Mapping). +*** + +## Interfaces + +Mojom IDL interfaces are translated to corresponding C++ (pure virtual) class +interface definitions in the generated header, consisting of a single generated +method signature for each request message on the interface. Internally there is +also generated code for serialization and deserialization of messages, but this +detail is hidden from bindings consumers. + +### Basic Usage + +Let's consider a new `//sample/logger.mojom` to define a simple logging +interface which clients can use to log simple string messages: + +``` cpp +module sample.mojom; + +interface Logger { + Log(string message); +}; +``` + +Running this through the bindings generator will produce a `logging.mojom.h` +with the following definitions (modulo unimportant details): + +``` cpp +namespace sample { +namespace mojom { + +class Logger { + virtual ~Logger() {} + + virtual void Log(const std::string& message) = 0; +}; + +using LoggerPtr = mojo::InterfacePtr<Logger>; +using LoggerRequest = mojo::InterfaceRequest<Logger>; + +} // namespace mojom +} // namespace sample +``` + +Makes sense. Let's take a closer look at those type aliases at the end. + +### InterfacePtr and InterfaceRequest + +You will notice the type aliases for `LoggerPtr` and +`LoggerRequest` are using two of the most fundamental template types in the C++ +bindings library: **`InterfacePtr<T>`** and **`InterfaceRequest<T>`**. + +In the world of Mojo bindings libraries these are effectively strongly-typed +message pipe endpoints. If an `InterfacePtr<T>` is bound to a message pipe +endpoint, it can be dereferenced to make calls on an opaque `T` interface. These +calls immediately serialize their arguments (using generated code) and write a +corresponding message to the pipe. + +An `InterfaceRequest<T>` is essentially just a typed container to hold the other +end of an `InterfacePtr<T>`'s pipe -- the receiving end -- until it can be +routed to some implementation which will **bind** it. The `InterfaceRequest<T>` +doesn't actually *do* anything other than hold onto a pipe endpoint and carry +useful compile-time type information. + +![Diagram illustrating InterfacePtr and InterfaceRequest on either end of a message pipe](https://docs.google.com/drawings/d/17d5gvErbQ6DthEBMS7I1WhCh9bz0n12pvNjydzuRfTI/pub?w=600&h=100) + +So how do we create a strongly-typed message pipe? + +### Creating Interface Pipes + +One way to do this is by manually creating a pipe and binding each end: + +``` cpp +#include "sample/logger.mojom.h" + +mojo::MessagePipe pipe; +sample::mojom::LoggerPtr logger; +sample::mojom::LoggerRequest request; + +logger.Bind(sample::mojom::LoggerPtrInfo(std::move(pipe.handle0), 0u)); +request.Bind(std::move(pipe.handle1)); +``` + +That's pretty verbose, but the C++ Bindings library provides more convenient +ways to accomplish the same thing. [interface_request.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/interface_request.h) +defines a `MakeRequest` function: + +``` cpp +sample::mojom::LoggerPtr logger; +sample::mojom::LoggerRequest request = mojo::MakeRequest(&logger); +``` + +and the `InterfaceRequest<T>` constructor can also take an explicit +`InterfacePtr<T>*` output argument: + +``` cpp +sample::mojom::LoggerPtr logger; +sample::mojom::LoggerRequest request(&logger); +``` + +Both of these last two snippets are equivalent to the first one. + +*** note +**NOTE:** In the first example above you may notice usage of the `LoggerPtrInfo` +type, which is a generated alias for `mojo::InterfacePtrInfo<Logger>`. This is +similar to an `InterfaceRequest<T>` in that it merely holds onto a pipe handle +and cannot actually read or write messages on the pipe. Both this type and +`InterfaceRequest<T>` are safe to move freely from thread to thread, whereas a +bound `InterfacePtr<T>` is bound to a single thread. + +An `InterfacePtr<T>` may be unbound by calling its `PassInterface()` method, +which returns a new `InterfacePtrInfo<T>`. Conversely, an `InterfacePtr<T>` may +bind (and thus take ownership of) an `InterfacePtrInfo<T>` so that interface +calls can be made on the pipe. + +The thread-bound nature of `InterfacePtr<T>` is necessary to support safe +dispatch of its [message responses](#Receiving-Responses) and +[connection error notifications](#Connection-Errors). +*** + +Once the `LoggerPtr` is bound we can immediately begin calling `Logger` +interface methods on it, which will immediately write messages into the pipe. +These messages will stay queued on the receiving end of the pipe until someone +binds to it and starts reading them. + +``` cpp +logger->Log("Hello!"); +``` + +This actually writes a `Log` message to the pipe. + +![Diagram illustrating a message traveling on a pipe from LoggerPtr to LoggerRequest](https://docs.google.com/a/google.com/drawings/d/1jWEc6jJIP2ed77Gg4JJ3EVC7hvnwcImNqQJywFwpT8g/pub?w=648&h=123) + +But as mentioned above, `InterfaceRequest` *doesn't actually do anything*, so +that message will just sit on the pipe forever. We need a way to read messages +off the other end of the pipe and dispatch them. We have to +**bind the interface request**. + +### Binding an Interface Request + +There are many different helper classes in the bindings library for binding the +receiving end of a message pipe. The most primitive among them is the aptly +named `mojo::Binding<T>`. A `mojo::Binding<T>` bridges an implementation of `T` +with a single bound message pipe endpoint (via a `mojo::InterfaceRequest<T>`), +which it continuously watches for readability. + +Any time the bound pipe becomes readable, the `Binding` will schedule a task to +read, deserialize (using generated code), and dispatch all available messages to +the bound `T` implementation. Below is a sample implementation of the `Logger` +interface. Notice that the implementation itself owns a `mojo::Binding`. This is +a common pattern, since a bound implementation must outlive any `mojo::Binding` +which binds it. + +``` cpp +#include "base/logging.h" +#include "base/macros.h" +#include "sample/logger.mojom.h" + +class LoggerImpl : public sample::mojom::Logger { + public: + // NOTE: A common pattern for interface implementations which have one + // instance per client is to take an InterfaceRequest in the constructor. + + explicit LoggerImpl(sample::mojom::LoggerRequest request) + : binding_(this, std::move(request)) {} + ~Logger() override {} + + // sample::mojom::Logger: + void Log(const std::string& message) override { + LOG(ERROR) << "[Logger] " << message; + } + + private: + mojo::Binding<sample::mojom::Logger> binding_; + + DISALLOW_COPY_AND_ASSIGN(LoggerImpl); +}; +``` + +Now we can construct a `LoggerImpl` over our pending `LoggerRequest`, and the +previously queued `Log` message will be dispatched ASAP on the `LoggerImpl`'s +thread: + +``` cpp +LoggerImpl impl(std::move(request)); +``` + +The diagram below illustrates the following sequence of events, all set in +motion by the above line of code: + +1. The `LoggerImpl` constructor is called, passing the `LoggerRequest` along + to the `Binding`. +2. The `Binding` takes ownership of the `LoggerRequest`'s pipe endpoint and + begins watching it for readability. The pipe is readable immediately, so a + task is scheduled to read the pending `Log` message from the pipe ASAP. +3. The `Log` message is read and deserialized, causing the `Binding` to invoke + the `Logger::Log` implementation on its bound `LoggerImpl`. + +![Diagram illustrating the progression of binding a request, reading a pending message, and dispatching it](https://docs.google.com/drawings/d/1c73-PegT4lmjfHoxhWrHTQXRvzxgb0wdeBa35WBwZ3Q/pub?w=550&h=500) + +As a result, our implementation will eventually log the client's `"Hello!"` +message via `LOG(ERROR)`. + +*** note +**NOTE:** Messages will only be read and dispatched from a pipe as long as the +object which binds it (*i.e.* the `mojo::Binding` in the above example) remains +alive. +*** + +### Receiving Responses + +Some Mojom interface methods expect a response. Suppose we modify our `Logger` +interface so that the last logged line can be queried like so: + +``` cpp +module sample.mojom; + +interface Logger { + Log(string message); + GetTail() => (string message); +}; +``` + +The generated C++ interface will now look like: + +``` cpp +namespace sample { +namespace mojom { + +class Logger { + public: + virtual ~Logger() {} + + virtual void Log(const std::string& message) = 0; + + using GetTailCallback = base::Callback<void(const std::string& message)>; + + virtual void GetTail(const GetTailCallback& callback) = 0; +} + +} // namespace mojom +} // namespace sample +``` + +As before, both clients and implementations of this interface use the same +signature for the `GetTail` method: implementations use the `callback` argument +to *respond* to the request, while clients pass a `callback` argument to +asynchronously `receive` the response. Here's an updated implementation: + +```cpp +class LoggerImpl : public sample::mojom::Logger { + public: + // NOTE: A common pattern for interface implementations which have one + // instance per client is to take an InterfaceRequest in the constructor. + + explicit LoggerImpl(sample::mojom::LoggerRequest request) + : binding_(this, std::move(request)) {} + ~Logger() override {} + + // sample::mojom::Logger: + void Log(const std::string& message) override { + LOG(ERROR) << "[Logger] " << message; + lines_.push_back(message); + } + + void GetTail(const GetTailCallback& callback) override { + callback.Run(lines_.back()); + } + + private: + mojo::Binding<sample::mojom::Logger> binding_; + std::vector<std::string> lines_; + + DISALLOW_COPY_AND_ASSIGN(LoggerImpl); +}; +``` + +And an updated client call: + +``` cpp +void OnGetTail(const std::string& message) { + LOG(ERROR) << "Tail was: " << message; +} + +logger->GetTail(base::Bind(&OnGetTail)); +``` + +Behind the scenes, the implementation-side callback is actually serializing the +response arguments and writing them onto the pipe for delivery back to the +client. Meanwhile the client-side callback is invoked by some internal logic +which watches the pipe for an incoming response message, reads and deserializes +it once it arrives, and then invokes the callback with the deserialized +parameters. + +### Connection Errors + +If there are no remaining messages available on a pipe and the remote end has +been closed, a connection error will be triggered on the local end. Connection +errors may also be triggered by automatic forced local pipe closure due to +*e.g.* a validation error when processing a received message. + +Regardless of the underlying cause, when a connection error is encountered on +a binding endpoint, that endpoint's **connection error handler** (if set) is +invoked. This handler is a simple `base::Closure` and may only be invoked +*once* as long as the endpoint is bound to the same pipe. Typically clients and +implementations use this handler to do some kind of cleanup or -- particuarly if +the error was unexpected -- create a new pipe and attempt to establish a new +connection with it. + +All message pipe-binding C++ objects (*e.g.*, `mojo::Binding<T>`, +`mojo::InterfacePtr<T>`, *etc.*) support setting their connection error handler +via a `set_connection_error_handler` method. + +We can set up another end-to-end `Logger` example to demonstrate error handler +invocation: + +``` cpp +sample::mojom::LoggerPtr logger; +LoggerImpl impl(mojo::MakeRequest(&logger)); +impl.set_connection_error_handler(base::Bind([] { LOG(ERROR) << "Bye."; })); +logger->Log("OK cool"); +logger.reset(); // Closes the client end. +``` + +As long as `impl` stays alive here, it will eventually receive the `Log` message +followed immediately by an invocation of the bound callback which outputs +`"Bye."`. Like all other bindings callbacks, a connection error handler will +**never** be invoked once its corresponding binding object has been destroyed. + +In fact, suppose instead that `LoggerImpl` had set up the following error +handler within its constructor: + +``` cpp +LoggerImpl::LoggerImpl(sample::mojom::LoggerRequest request) + : binding_(this, std::move(request)) { + binding_.set_connection_error_handler( + base::Bind(&LoggerImpl::OnError, base::Unretained(this))); +} + +void LoggerImpl::OnError() { + LOG(ERROR) << "Client disconnected! Purging log lines."; + lines_.clear(); +} +``` + +The use of `base::Unretained` is *safe* because the error handler will never be +invoked beyond the lifetime of `binding_`, and `this` owns `binding_`. + +### A Note About Ordering + +As mentioned in the previous section, closing one end of a pipe will eventually +trigger a connection error on the other end. However it's important to note that +this event is itself ordered with respect to any other event (*e.g.* writing a +message) on the pipe. + +This means that it's safe to write something contrived like: + +``` cpp +void GoBindALogger(sample::mojom::LoggerRequest request) { + LoggerImpl impl(std::move(request)); + base::RunLoop loop; + impl.set_connection_error_handler(loop.QuitClosure()); + loop.Run(); +} + +void LogSomething() { + sample::mojom::LoggerPtr logger; + bg_thread->task_runner()->PostTask( + FROM_HERE, base::BindOnce(&GoBindALogger, mojo::MakeRequest(&logger))); + logger->Log("OK Computer"); +} +``` + +When `logger` goes out of scope it immediately closes its end of the message +pipe, but the impl-side won't notice this until it receives the sent `Log` +message. Thus the `impl` above will first log our message and *then* see a +connection error and break out of the run loop. + +### Sending Interfaces Over Interfaces + +Now we know how to create interface pipes and use their Ptr and Request +endpoints in some interesting ways. This still doesn't add up to interesting +IPC! The bread and butter of Mojo IPC is the ability to transfer interface +endpoints across other interfaces, so let's take a look at how to accomplish +that. + +#### Sending Interface Requests + +Consider a new example Mojom in `//sample/db.mojom`: + +``` cpp +module db.mojom; + +interface Table { + void AddRow(int32 key, string data); +}; + +interface Database { + AddTable(Table& table); +}; +``` + +As noted in the +[Mojom IDL documentation](/mojo/public/tools/bindings#Primitive-Types), +the `Table&` syntax denotes a `Table` interface request. This corresponds +precisely to the `InterfaceRequest<T>` type discussed in the sections above, and +in fact the generated code for these interfaces is approximately: + +``` cpp +namespace db { +namespace mojom { + +class Table { + public: + virtual ~Table() {} + + virtual void AddRow(int32_t key, const std::string& data) = 0; +} + +using TablePtr = mojo::InterfacePtr<Table>; +using TableRequest = mojo::InterfaceRequest<Table>; + +class Database { + public: + virtual ~Database() {} + + virtual void AddTable(TableRequest table); +}; + +using DatabasePtr = mojo::InterfacePtr<Database>; +using DatabaseRequest = mojo::InterfaceRequest<Database>; + +} // namespace mojom +} // namespace db +``` + +We can put this all together now with an implementation of `Table` and +`Database`: + +``` cpp +#include "sample/db.mojom.h" + +class TableImpl : public db::mojom:Table { + public: + explicit TableImpl(db::mojom::TableRequest request) + : binding_(this, std::move(request)) {} + ~TableImpl() override {} + + // db::mojom::Table: + void AddRow(int32_t key, const std::string& data) override { + rows_.insert({key, data}); + } + + private: + mojo::Binding<db::mojom::Table> binding_; + std::map<int32_t, std::string> rows_; +}; + +class DatabaseImpl : public db::mojom::Database { + public: + explicit DatabaseImpl(db::mojom::DatabaseRequest request) + : binding_(this, std::move(request)) {} + ~DatabaseImpl() override {} + + // db::mojom::Database: + void AddTable(db::mojom::TableRequest table) { + tables_.emplace_back(base::MakeUnique<TableImpl>(std::move(table))); + } + + private: + mojo::Binding<db::mojom::Database> binding_; + std::vector<std::unique_ptr<TableImpl>> tables_; +}; +``` + +Pretty straightforward. The `Table&` Mojom paramter to `AddTable` translates to +a C++ `db::mojom::TableRequest`, aliased from +`mojo::InterfaceRequest<db::mojom::Table>`, which we know is just a +strongly-typed message pipe handle. When `DatabaseImpl` gets an `AddTable` call, +it constructs a new `TableImpl` and binds it to the received `TableRequest`. + +Let's see how this can be used. + +``` cpp +db::mojom::DatabasePtr database; +DatabaseImpl db_impl(mojo::MakeRequest(&database)); + +db::mojom::TablePtr table1, table2; +database->AddTable(mojo::MakeRequest(&table1)); +database->AddTable(mojo::MakeRequest(&table2)); + +table1->AddRow(1, "hiiiiiiii"); +table2->AddRow(2, "heyyyyyy"); +``` + +Notice that we can again start using the new `Table` pipes immediately, even +while their `TableRequest` endpoints are still in transit. + +#### Sending InterfacePtrs + +Of course we can also send `InterfacePtr`s: + +``` cpp +interface TableListener { + OnRowAdded(int32 key, string data); +}; + +interface Table { + AddRow(int32 key, string data); + + AddListener(TableListener listener); +}; +``` + +This would generate a `Table::AddListener` signature like so: + +``` cpp + virtual void AddListener(TableListenerPtr listener) = 0; +``` + +and this could be used like so: + +``` cpp +db::mojom::TableListenerPtr listener; +TableListenerImpl impl(mojo::MakeRequest(&listener)); +table->AddListener(std::move(listener)); +``` + +## Other Interface Binding Types + +The [Interfaces](#Interfaces) section above covers basic usage of the most +common bindings object types: `InterfacePtr`, `InterfaceRequest`, and `Binding`. +While these types are probably the most commonly used in practice, there are +several other ways of binding both client- and implementation-side interface +pipes. + +### Strong Bindings + +A **strong binding** exists as a standalone object which owns its interface +implementation and automatically cleans itself up when its bound interface +endpoint detects an error. The +[**`MakeStrongBinding`**](https://cs.chromim.org/chromium/src//mojo/public/cpp/bindings/strong_binding.h) +function is used to create such a binding. +. + +``` cpp +class LoggerImpl : public sample::mojom::Logger { + public: + LoggerImpl() {} + ~LoggerImpl() override {} + + // sample::mojom::Logger: + void Log(const std::string& message) override { + LOG(ERROR) << "[Logger] " << message; + } + + private: + // NOTE: This doesn't own any Binding object! +}; + +db::mojom::LoggerPtr logger; +mojo::MakeStrongBinding(base::MakeUnique<DatabaseImpl>(), + mojo::MakeRequest(&logger)); + +logger->Log("NOM NOM NOM MESSAGES"); +``` + +Now as long as `logger` remains open somewhere in the system, the bound +`DatabaseImpl` on the other end will remain alive. + +### Binding Sets + +Sometimes it's useful to share a single implementation instance with multiple +clients. [**`BindingSet`**](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/binding_set.h) +makes this easy. Consider the Mojom: + +``` cpp +module system.mojom; + +interface Logger { + Log(string message); +}; + +interface LoggerProvider { + GetLogger(Logger& logger); +}; +``` + +We can use `BindingSet` to bind multiple `Logger` requests to a single +implementation instance: + +``` cpp +class LogManager : public system::mojom::LoggerProvider, + public system::mojom::Logger { + public: + explicit LogManager(system::mojom::LoggerProviderRequest request) + : provider_binding_(this, std::move(request)) {} + ~LogManager() {} + + // system::mojom::LoggerProvider: + void GetLogger(LoggerRequest request) override { + logger_bindings_.AddBinding(this, std::move(request)); + } + + // system::mojom::Logger: + void Log(const std::string& message) override { + LOG(ERROR) << "[Logger] " << message; + } + + private: + mojo::Binding<system::mojom::LoggerProvider> provider_binding_; + mojo::BindingSet<system::mojom::Logger> logger_bindings_; +}; + +``` + + +### InterfacePtr Sets + +Similar to the `BindingSet` above, sometimes it's useful to maintain a set of +`InterfacePtr`s for *e.g.* a set of clients observing some event. +[**`InterfacePtrSet`**](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/interface_ptr_set.h) +is here to help. Take the Mojom: + +``` cpp +module db.mojom; + +interface TableListener { + OnRowAdded(int32 key, string data); +}; + +interface Table { + AddRow(int32 key, string data); + AddListener(TableListener listener); +}; +``` + +An implementation of `Table` might look something like like this: + +``` cpp +class TableImpl : public db::mojom::Table { + public: + TableImpl() {} + ~TableImpl() override {} + + // db::mojom::Table: + void AddRow(int32_t key, const std::string& data) override { + rows_.insert({key, data}); + listeners_.ForEach([key, &data](db::mojom::TableListener* listener) { + listener->OnRowAdded(key, data); + }); + } + + void AddListener(db::mojom::TableListenerPtr listener) { + listeners_.AddPtr(std::move(listener)); + } + + private: + mojo::InterfacePtrSet<db::mojom::Table> listeners_; + std::map<int32_t, std::string> rows_; +}; +``` + +## Associated Interfaces + +See [this document](https://www.chromium.org/developers/design-documents/mojo/associated-interfaces). + +TODO: Move the above doc into the repository markdown docs. + +## Synchronous Calls + +See [this document](https://www.chromium.org/developers/design-documents/mojo/synchronous-calls) + +TODO: Move the above doc into the repository markdown docs. + +## Type Mapping + +In many instances you might prefer that your generated C++ bindings use a more +natural type to represent certain Mojom types in your interface methods. For one +example consider a Mojom struct such as the `Rect` below: + +``` cpp +module gfx.mojom; + +struct Rect { + int32 x; + int32 y; + int32 width; + int32 height; +}; + +interface Canvas { + void FillRect(Rect rect); +}; +``` + +The `Canvas` Mojom interface would normally generate a C++ interface like: + +``` cpp +class Canvas { + public: + virtual void FillRect(RectPtr rect) = 0; +}; +``` + +However, the Chromium tree already defines a native +[`gfx::Rect`](https://cs.chromium.org/chromium/src/ui/gfx/geometry/rect.h) which +is equivalent in meaning but which also has useful helper methods. Instead of +manually converting between a `gfx::Rect` and the Mojom-generated `RectPtr` at +every message boundary, wouldn't it be nice if the Mojom bindings generator +could instead generate: + +``` cpp +class Canvas { + public: + virtual void FillRect(const gfx::Rect& rect) = 0; +} +``` + +The correct answer is, "Yes! That would be nice!" And fortunately, it can! + +### Global Configuration + +While this feature is quite powerful, it introduces some unavoidable complexity +into build system. This stems from the fact that type-mapping is an inherently +viral concept: if `gfx::mojom::Rect` is mapped to `gfx::Rect` anywhere, the +mapping needs to apply *everywhere*. + +For this reason we have a few global typemap configurations defined in +[chromium_bindings_configuration.gni](https://cs.chromium.com/chromium/src/mojo/public/tools/bindings/chromium_bindings_configuration.gni) +and +[blink_bindings_configuration.gni](https://cs.chromium.com/chromium/src/mojo/public/tools/bindings/blink_bindings_configuration.gni). These configure the two supported [variants](#Variants) of Mojom generated +bindings in the repository. Read more on this in the sections that follow. + +For now, let's take a look at how to express the mapping from `gfx::mojom::Rect` +to `gfx::Rect`. + +### Defining `StructTraits` + +In order to teach generated bindings code how to serialize an arbitrary native +type `T` as an arbitrary Mojom type `mojom::U`, we need to define an appropriate +specialization of the +[`mojo::StructTraits`](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/struct_traits.h) +template. + +A valid specialization of `StructTraits` MUST define the following static +methods: + +* A single static accessor for every field of the Mojom struct, with the exact + same name as the struct field. These accessors must all take a const ref to + an object of the native type, and must return a value compatible with the + Mojom struct field's type. This is used to safely and consistently extract + data from the native type during message serialization without incurring extra + copying costs. + +* A single static `Read` method which initializes an instance of the the native + type given a serialized representation of the Mojom struct. The `Read` method + must return a `bool` to indicate whether the incoming data is accepted + (`true`) or rejected (`false`). + +There are other methods a `StructTraits` specialization may define to satisfy +some less common requirements. See +[Advanced StructTraits Usage](#Advanced-StructTraits-Usage) for details. + +In order to define the mapping for `gfx::Rect`, we want the following +`StructTraits` specialization, which we'll define in +`//ui/gfx/geometry/mojo/geometry_struct_traits.h`: + +``` cpp +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/mojo/geometry.mojom.h" + +namespace mojo { + +template <> +class StructTraits<gfx::mojom::RectDataView, gfx::Rect> { + public: + static int32_t x(const gfx::Rect& r) { return r.x(); } + static int32_t y(const gfx::Rect& r) { return r.y(); } + static int32_t width(const gfx::Rect& r) { return r.width(); } + static int32_t height(const gfx::Rect& r) { return r.height(); } + + static bool Read(gfx::mojom::RectDataView data, gfx::Rect* out_rect); +}; + +} // namespace mojo +``` + +And in `//ui/gfx/geometry/mojo/geometry_struct_traits.cc`: + +``` cpp +#include "ui/gfx/geometry/mojo/geometry_struct_traits.h" + +namespace mojo { + +// static +template <> +bool StructTraits<gfx::mojom::RectDataView, gfx::Rect>::Read( + gfx::mojom::RectDataView data, + gfx::Rect* out_rect) { + if (data.width() < 0 || data.height() < 0) + return false; + + out_rect->SetRect(data.x(), data.y(), data.width(), data.height()); + return true; +}; + +} // namespace mojo +``` + +Note that the `Read()` method returns `false` if either the incoming `width` or +`height` fields are negative. This acts as a validation step during +deserialization: if a client sends a `gfx::Rect` with a negative width or +height, its message will be rejected and the pipe will be closed. In this way, +type mapping can serve to enable custom validation logic in addition to making +callsites and interface implemention more convenient. + +### Enabling a New Type Mapping + +We've defined the `StructTraits` necessary, but we still need to teach the +bindings generator (and hence the build system) about the mapping. To do this we +must create a **typemap** file, which uses familiar GN syntax to describe the +new type mapping. + +Let's place this `geometry.typemap` file alongside our Mojom file: + +``` +mojom = "//ui/gfx/geometry/mojo/geometry.mojom" +public_headers = [ "//ui/gfx/geometry/rect.h" ] +traits_headers = [ "//ui/gfx/geometry/mojo/geometry_struct_traits.h" ] +sources = [ "//ui/gfx/geometry/mojo/geometry_struct_traits.cc" ] +public_deps = [ "//ui/gfx/geometry" ] +type_mappings = [ + "gfx.mojom.Rect=gfx::Rect", +] +``` + +Let's look at each of the variables above: + +* `mojom`: Specifies the `mojom` file to which the typemap applies. Many + typemaps may apply to the same `mojom` file, but any given typemap may only + apply to a single `mojom` file. +* `public_headers`: Additional headers required by any code which would depend + on the Mojom definition of `gfx.mojom.Rect` now that the typemap is applied. + Any headers required for the native target type definition should be listed + here. +* `traits_headers`: Headers which contain the relevant `StructTraits` + specialization(s) for any type mappings described by this file. +* `sources`: Any private implementation sources needed for the `StructTraits` + definition. +* `public_deps`: Target dependencies exposed by the `public_headers` and + `traits_headers`. +* `deps`: Target dependencies exposed by `sources` but not already covered by + `public_deps`. +* `type_mappings`: A list of type mappings to be applied for this typemap. The + strings in this list are of the format `"MojomType=CppType"`, where + `MojomType` must be a fully qualified Mojom typename and `CppType` must be a + fully qualified C++ typename. Additional attributes may be specified in square + brackets following the `CppType`: + * `move_only`: The `CppType` is move-only and should be passed by value + in any generated method signatures. Note that `move_only` is transitive, + so containers of `MojomType` will translate to containers of `CppType` + also passed by value. + * `copyable_pass_by_value`: Forces values of type `CppType` to be passed by + value without moving them. Unlike `move_only`, this is not transitive. + * `nullable_is_same_type`: By default a non-nullable `MojomType` will be + mapped to `CppType` while a nullable `MojomType?` will be mapped to + `base::Optional<CppType>`. If this attribute is set, the `base::Optional` + wrapper is omitted for nullable `MojomType?` values, but the + `StructTraits` definition for this type mapping must define additional + `IsNull` and `SetToNull` methods. See + [Specializing Nullability](#Specializing-Nullability) below. + + +Now that we have the typemap file we need to add it to a local list of typemaps +that can be added to the global configuration. We create a new +`//ui/gfx/typemaps.gni` file with the following contents: + +``` +typemaps = [ + "//ui/gfx/geometry/mojo/geometry.typemap", +] +``` + +And finally we can reference this file in the global default (Chromium) bindings +configuration by adding it to `_typemap_imports` in +[chromium_bindings_configuration.gni](https://cs.chromium.com/chromium/src/mojo/public/tools/bindings/chromium_bindings_configuration.gni): + +``` +_typemap_imports = [ + ..., + "//ui/gfx/typemaps.gni", + ..., +] +``` + +### StructTraits Reference + +Each of a `StructTraits` specialization's static getter methods -- one per +struct field -- must return a type which can be used as a data source for the +field during serialization. This is a quick reference mapping Mojom field type +to valid getter return types: + +| Mojom Field Type | C++ Getter Return Type | +|------------------------------|------------------------| +| `bool` | `bool` +| `int8` | `int8_t` +| `uint8` | `uint8_t` +| `int16` | `int16_t` +| `uint16` | `uint16_t` +| `int32` | `int32_t` +| `uint32` | `uint32_t` +| `int64` | `int64_t` +| `uint64` | `uint64_t` +| `float` | `float` +| `double` | `double` +| `handle` | `mojo::ScopedHandle` +| `handle<message_pipe>` | `mojo::ScopedMessagePipeHandle` +| `handle<data_pipe_consumer>` | `mojo::ScopedDataPipeConsumerHandle` +| `handle<data_pipe_producer>` | `mojo::ScopedDataPipeProducerHandle` +| `handle<shared_buffer>` | `mojo::ScopedSharedBufferHandle` +| `FooInterface` | `FooInterfacePtr` +| `FooInterface&` | `FooInterfaceRequest` +| `associated FooInterface` | `FooAssociatedInterfacePtr` +| `associated FooInterface&` | `FooAssociatedInterfaceRequest` +| `string` | Value or reference to any type `T` that has a `mojo::StringTraits` specialization defined. By default this includes `std::string`, `base::StringPiece`, and `WTF::String` (Blink). +| `array<T>` | Value or reference to any type `T` that has a `mojo::ArrayTraits` specialization defined. By default this includes `std::vector<T>`, `mojo::CArray<T>`, and `WTF::Vector<T>` (Blink). +| `map<K, V>` | Value or reference to any type `T` that has a `mojo::MapTraits` specialization defined. By default this includes `std::map<T>`, `mojo::unordered_map<T>`, and `WTF::HashMap<T>` (Blink). +| `FooEnum` | Value of any type that has an appropriate `EnumTraits` specialization defined. By default this inlcudes only the generated `FooEnum` type. +| `FooStruct` | Value or reference to any type that has an appropriate `StructTraits` specialization defined. By default this includes only the generated `FooStructPtr` type. +| `FooUnion` | Value of reference to any type that has an appropriate `UnionTraits` specialization defined. By default this includes only the generated `FooUnionPtr` type. + +### Using Generated DataView Types + +Static `Read` methods on `StructTraits` specializations get a generated +`FooDataView` argument (such as the `RectDataView` in the example above) which +exposes a direct view of the serialized Mojom structure within an incoming +message's contents. In order to make this as easy to work with as possible, the +generated `FooDataView` types have a generated method corresponding to every +struct field: + +* For POD field types (*e.g.* bools, floats, integers) these are simple accessor + methods with names identical to the field name. Hence in the `Rect` example we + can access things like `data.x()` and `data.width()`. The return types + correspond exactly to the mappings listed in the table above, under + [StructTraits Reference](#StructTraits-Reference). + +* For handle and interface types (*e.g* `handle` or `FooInterface&`) these + are named `TakeFieldName` (for a field named `field_name`) and they return an + appropriate move-only handle type by value. The return types correspond + exactly to the mappings listed in the table above, under + [StructTraits Reference](#StructTraits-Reference). + +* For all other field types (*e.g.*, enums, strings, arrays, maps, structs) + these are named `ReadFieldName` (for a field named `field_name`) and they + return a `bool` (to indicate success or failure in reading). On success they + fill their output argument with the deserialized field value. The output + argument may be a pointer to any type with an appropriate `StructTraits` + specialization defined, as mentioned in the table above, under + [StructTraits Reference](#StructTraits-Reference). + +An example would be useful here. Suppose we introduced a new Mojom struct: + +``` cpp +struct RectPair { + Rect left; + Rect right; +}; +``` + +and a corresponding C++ type: + +``` cpp +class RectPair { + public: + RectPair() {} + + const gfx::Rect& left() const { return left_; } + const gfx::Rect& right() const { return right_; } + + void Set(const gfx::Rect& left, const gfx::Rect& right) { + left_ = left; + right_ = right; + } + + // ... some other stuff + + private: + gfx::Rect left_; + gfx::Rect right_; +}; +``` + +Our traits to map `gfx::mojom::RectPair` to `gfx::RectPair` might look like +this: + +``` cpp +namespace mojo { + +template <> +class StructTraits + public: + static const gfx::Rect& left(const gfx::RectPair& pair) { + return pair.left(); + } + + static const gfx::Rect& right(const gfx::RectPair& pair) { + return pair.right(); + } + + static bool Read(gfx::mojom::RectPairDataView data, gfx::RectPair* out_pair) { + gfx::Rect left, right; + if (!data.ReadLeft(&left) || !data.ReadRight(&right)) + return false; + out_pair->Set(left, right); + return true; + } +} // namespace mojo +``` + +Generated `ReadFoo` methods always convert `multi_word_field_name` fields to +`ReadMultiWordFieldName` methods. + +### Variants + +By now you may have noticed that additional C++ sources are generated when a +Mojom is processed. These exist due to type mapping, and the source files we +refer to throughout this docuemnt (namely `foo.mojom.cc` and `foo.mojom.h`) are +really only one **variant** (the *default* or *chromium* variant) of the C++ +bindings for a given Mojom file. + +The only other variant currently defined in the tree is the *blink* variant, +which produces a few additional files: + +``` +out/gen/sample/db.mojom-blink.cc +out/gen/sample/db.mojom-blink.h +``` + +These files mirror the definitions in the default variant but with different +C++ types in place of certain builtin field and parameter types. For example, +Mojom strings are represented by `WTF::String` instead of `std::string`. To +avoid symbol collisions, the variant's symbols are nested in an extra inner +namespace, so Blink consumer of the interface might write something like: + +``` +#include "sample/db.mojom-blink.h" + +class TableImpl : public db::mojom::blink::Table { + public: + void AddRow(int32_t key, const WTF::String& data) override { + // ... + } +}; +``` + +In addition to using different C++ types for builtin strings, arrays, and maps, +the global typemap configuration for default and "blink" variants are completely +separate. To add a typemap for the Blink configuration, you can modify +[blink_bindings_configuration.gni](https://cs.chromium.org/chromium/src/mojo/public/tools/bindings/blink_bindings_configuration.gni). + +All variants share some definitions which are unaffected by differences in the +type mapping configuration (enums, for example). These definitions are generated +in *shared* sources: + +``` +out/gen/sample/db.mojom-shared.cc +out/gen/sample/db.mojom-shared.h +out/gen/sample/db.mojom-shared-internal.h +``` + +Including either variant's header (`db.mojom.h` or `db.mojom-blink.h`) +implicitly includes the shared header, but you have on some occasions wish to +include *only* the shared header in some instances. + +Finally, note that for `mojom` GN targets, there is implicitly a corresponding +`mojom_{variant}` target defined for any supported bindings configuration. So +for example if you've defined in `//sample/BUILD.gn`: + +``` +import("mojo/public/tools/bindings/mojom.gni") + +mojom("interfaces") { + sources = [ + "db.mojom", + ] +} +``` + +Code in Blink which wishes to use the generated Blink-variant definitions must +depend on `"//sample:interfaces_blink"`. + +## Versioning Considerations + +For general documentation of versioning in the Mojom IDL see +[Versioning](/mojo/public/tools/bindings#Versioning). + +This section briefly discusses some C++-specific considerations relevant to +versioned Mojom types. + +### Querying Interface Versions + +`InterfacePtr` defines the following methods to query or assert remote interface +version: + +```cpp +void QueryVersion(const base::Callback<void(uint32_t)>& callback); +``` + +This queries the remote endpoint for the version number of its binding. When a +response is received `callback` is invoked with the remote version number. Note +that this value is cached by the `InterfacePtr` instance to avoid redundant +queries. + +```cpp +void RequireVersion(uint32_t version); +``` + +Informs the remote endpoint that a minimum version of `version` is required by +the client. If the remote endpoint cannot support that version, it will close +its end of the pipe immediately, preventing any other requests from being +received. + +### Versioned Enums + +For convenience, every extensible enum has a generated helper function to +determine whether a received enum value is known by the implementation's current +version of the enum definition. For example: + +```cpp +[Extensible] +enum Department { + SALES, + DEV, + RESEARCH, +}; +``` + +generates the function in the same namespace as the generated C++ enum type: + +```cpp +inline bool IsKnownEnumValue(Department value); +``` + +### Additional Documentation + +[Calling Mojo From Blink](https://www.chromium.org/developers/design-documents/mojo/calling-mojo-from-blink) +: A brief overview of what it looks like to use Mojom C++ bindings from + within Blink code. diff --git a/mojo/public/cpp/bindings/associated_interface_ptr.h b/mojo/public/cpp/bindings/associated_interface_ptr.h index 8e66f4e..8806a3e 100644 --- a/mojo/public/cpp/bindings/associated_interface_ptr.h +++ b/mojo/public/cpp/bindings/associated_interface_ptr.h @@ -18,6 +18,7 @@ #include "base/threading/thread_task_runner_handle.h" #include "mojo/public/cpp/bindings/associated_interface_ptr_info.h" #include "mojo/public/cpp/bindings/associated_interface_request.h" +#include "mojo/public/cpp/bindings/bindings_export.h" #include "mojo/public/cpp/bindings/connection_error_callback.h" #include "mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h" #include "mojo/public/cpp/bindings/lib/multiplex_router.h" @@ -232,10 +233,9 @@ AssociatedInterfaceRequest<Interface> MakeRequest( return request; } -// Like |GetProxy|, but the interface is never associated with any other -// interface. The returned request can be bound directly to the corresponding -// associated interface implementation, without first passing it through a -// message pipe endpoint. +// Like MakeRequest() above, but it creates a dedicated message pipe. The +// returned request can be bound directly to an implementation, without being +// first passed through a message pipe endpoint. // // This function has two main uses: // @@ -245,7 +245,7 @@ AssociatedInterfaceRequest<Interface> MakeRequest( // * When discarding messages sent on an interface, which can be done by // discarding the returned request. template <typename Interface> -AssociatedInterfaceRequest<Interface> GetIsolatedProxy( +AssociatedInterfaceRequest<Interface> MakeIsolatedRequest( AssociatedInterfacePtr<Interface>* ptr) { MessagePipe pipe; scoped_refptr<internal::MultiplexRouter> router0 = @@ -271,14 +271,12 @@ AssociatedInterfaceRequest<Interface> GetIsolatedProxy( return request; } -// Creates an associated interface proxy in its own AssociatedGroup. -// TODO(yzshen): Rename GetIsolatedProxy() to MakeIsolatedRequest(), and change -// all callsites of this function to directly use that. -template <typename Interface> -AssociatedInterfaceRequest<Interface> MakeRequestForTesting( - AssociatedInterfacePtr<Interface>* ptr) { - return GetIsolatedProxy(ptr); -} +// |handle| is supposed to be the request of an associated interface. This +// method associates the interface with a dedicated, disconnected message pipe. +// That way, the corresponding associated interface pointer of |handle| can +// safely make calls (although those calls are silently dropped). +MOJO_CPP_BINDINGS_EXPORT void GetIsolatedInterface( + ScopedInterfaceEndpointHandle handle); } // namespace mojo diff --git a/mojo/public/cpp/bindings/binding.h b/mojo/public/cpp/bindings/binding.h index 1da331b..88d2f4b 100644 --- a/mojo/public/cpp/bindings/binding.h +++ b/mojo/public/cpp/bindings/binding.h @@ -192,10 +192,10 @@ class Binding { // true if a method was successfully read and dispatched. // // This method may only be called if the object has been bound to a message - // pipe and there are no associated interfaces running. + // pipe. This returns once a message is received either on the master + // interface or any associated interfaces. bool WaitForIncomingMethodCall( MojoDeadline deadline = MOJO_DEADLINE_INDEFINITE) { - CHECK(!HasAssociatedInterfaces()); return internal_state_.WaitForIncomingMethodCall(deadline); } diff --git a/mojo/public/cpp/bindings/connector.h b/mojo/public/cpp/bindings/connector.h index 01e9236..cb065c1 100644 --- a/mojo/public/cpp/bindings/connector.h +++ b/mojo/public/cpp/bindings/connector.h @@ -18,7 +18,7 @@ #include "mojo/public/cpp/bindings/message.h" #include "mojo/public/cpp/bindings/sync_handle_watcher.h" #include "mojo/public/cpp/system/core.h" -#include "mojo/public/cpp/system/watcher.h" +#include "mojo/public/cpp/system/simple_watcher.h" namespace base { class Lock; @@ -156,7 +156,10 @@ class MOJO_CPP_BINDINGS_EXPORT Connector void SetWatcherHeapProfilerTag(const char* tag); private: - // Callback of mojo::Watcher. + class ActiveDispatchTracker; + class MessageLoopNestingObserver; + + // Callback of mojo::SimpleWatcher. void OnWatcherHandleReady(MojoResult result); // Callback of SyncHandleWatcher. void OnSyncHandleWatcherHandleReady(MojoResult result); @@ -188,7 +191,7 @@ class MOJO_CPP_BINDINGS_EXPORT Connector MessageReceiver* incoming_receiver_ = nullptr; scoped_refptr<base::SingleThreadTaskRunner> task_runner_; - std::unique_ptr<Watcher> handle_watcher_; + std::unique_ptr<SimpleWatcher> handle_watcher_; bool error_ = false; bool drop_writes_ = false; @@ -215,6 +218,14 @@ class MOJO_CPP_BINDINGS_EXPORT Connector // notification. const char* heap_profiler_tag_ = nullptr; + // A cached pointer to the MessageLoopNestingObserver for the MessageLoop on + // which this Connector was created. + MessageLoopNestingObserver* const nesting_observer_; + + // |true| iff the Connector is currently dispatching a message. Used to detect + // nested dispatch operations. + bool is_dispatching_ = false; + // Create a single weak ptr and use it everywhere, to avoid the malloc/free // cost of creating a new weak ptr whenever it is needed. // NOTE: This weak pointer is invalidated when the message pipe is closed or diff --git a/mojo/public/cpp/bindings/interface_endpoint_client.h b/mojo/public/cpp/bindings/interface_endpoint_client.h index 0aea756..b519fe9 100644 --- a/mojo/public/cpp/bindings/interface_endpoint_client.h +++ b/mojo/public/cpp/bindings/interface_endpoint_client.h @@ -96,7 +96,7 @@ class MOJO_CPP_BINDINGS_EXPORT InterfaceEndpointClient // state. bool Accept(Message* message) override; bool AcceptWithResponder(Message* message, - MessageReceiver* responder) override; + std::unique_ptr<MessageReceiver> responder) override; // The following methods are called by the router. They must be called // outside of the router's lock. diff --git a/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc b/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc new file mode 100644 index 0000000..78281ed --- /dev/null +++ b/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc @@ -0,0 +1,18 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" + +namespace mojo { + +void GetIsolatedInterface(ScopedInterfaceEndpointHandle handle) { + MessagePipe pipe; + scoped_refptr<internal::MultiplexRouter> router = + new internal::MultiplexRouter(std::move(pipe.handle0), + internal::MultiplexRouter::MULTI_INTERFACE, + false, base::ThreadTaskRunnerHandle::Get()); + router->AssociateInterface(std::move(handle)); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h index 72f7960..a4b5188 100644 --- a/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h +++ b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h @@ -131,7 +131,7 @@ class AssociatedInterfacePtrState { void ForwardMessageWithResponder(Message message, std::unique_ptr<MessageReceiver> responder) { - endpoint_client_->AcceptWithResponder(&message, responder.release()); + endpoint_client_->AcceptWithResponder(&message, std::move(responder)); } private: diff --git a/mojo/public/cpp/bindings/lib/connector.cc b/mojo/public/cpp/bindings/lib/connector.cc index 4426def..d93e45e 100644 --- a/mojo/public/cpp/bindings/lib/connector.cc +++ b/mojo/public/cpp/bindings/lib/connector.cc @@ -8,20 +8,131 @@ #include <utility> #include "base/bind.h" +#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/message_loop/message_loop.h" #include "base/synchronization/lock.h" +#include "base/threading/thread_local.h" #include "mojo/public/cpp/bindings/lib/may_auto_lock.h" #include "mojo/public/cpp/bindings/sync_handle_watcher.h" +#include "mojo/public/cpp/system/wait.h" namespace mojo { +namespace { + +// The NestingObserver for each thread. Note that this is always a +// Connector::MessageLoopNestingObserver; we use the base type here because that +// subclass is private to Connector. +base::LazyInstance< + base::ThreadLocalPointer<base::MessageLoop::NestingObserver>>::Leaky + g_tls_nesting_observer = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +// Used to efficiently maintain a doubly-linked list of all Connectors +// currently dispatching on any given thread. +class Connector::ActiveDispatchTracker { + public: + explicit ActiveDispatchTracker(const base::WeakPtr<Connector>& connector); + ~ActiveDispatchTracker(); + + void NotifyBeginNesting(); + + private: + const base::WeakPtr<Connector> connector_; + MessageLoopNestingObserver* const nesting_observer_; + ActiveDispatchTracker* outer_tracker_ = nullptr; + ActiveDispatchTracker* inner_tracker_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(ActiveDispatchTracker); +}; + +// Watches the MessageLoop on the current thread. Notifies the current chain of +// ActiveDispatchTrackers when a nested message loop is started. +class Connector::MessageLoopNestingObserver + : public base::MessageLoop::NestingObserver, + public base::MessageLoop::DestructionObserver { + public: + MessageLoopNestingObserver() { + base::MessageLoop::current()->AddNestingObserver(this); + base::MessageLoop::current()->AddDestructionObserver(this); + } + + ~MessageLoopNestingObserver() override {} + + // base::MessageLoop::NestingObserver: + void OnBeginNestedMessageLoop() override { + if (top_tracker_) + top_tracker_->NotifyBeginNesting(); + } + + // base::MessageLoop::DestructionObserver: + void WillDestroyCurrentMessageLoop() override { + base::MessageLoop::current()->RemoveNestingObserver(this); + base::MessageLoop::current()->RemoveDestructionObserver(this); + DCHECK_EQ(this, g_tls_nesting_observer.Get().Get()); + g_tls_nesting_observer.Get().Set(nullptr); + delete this; + } + + static MessageLoopNestingObserver* GetForThread() { + if (!base::MessageLoop::current() || + !base::MessageLoop::current()->nesting_allowed()) + return nullptr; + auto* observer = static_cast<MessageLoopNestingObserver*>( + g_tls_nesting_observer.Get().Get()); + if (!observer) { + observer = new MessageLoopNestingObserver; + g_tls_nesting_observer.Get().Set(observer); + } + return observer; + } + + private: + friend class ActiveDispatchTracker; + + ActiveDispatchTracker* top_tracker_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(MessageLoopNestingObserver); +}; + +Connector::ActiveDispatchTracker::ActiveDispatchTracker( + const base::WeakPtr<Connector>& connector) + : connector_(connector), nesting_observer_(connector_->nesting_observer_) { + DCHECK(nesting_observer_); + if (nesting_observer_->top_tracker_) { + outer_tracker_ = nesting_observer_->top_tracker_; + outer_tracker_->inner_tracker_ = this; + } + nesting_observer_->top_tracker_ = this; +} + +Connector::ActiveDispatchTracker::~ActiveDispatchTracker() { + if (nesting_observer_->top_tracker_ == this) + nesting_observer_->top_tracker_ = outer_tracker_; + else if (inner_tracker_) + inner_tracker_->outer_tracker_ = outer_tracker_; + if (outer_tracker_) + outer_tracker_->inner_tracker_ = inner_tracker_; +} + +void Connector::ActiveDispatchTracker::NotifyBeginNesting() { + if (connector_ && connector_->handle_watcher_) + connector_->handle_watcher_->ArmOrNotify(); + if (outer_tracker_) + outer_tracker_->NotifyBeginNesting(); +} + Connector::Connector(ScopedMessagePipeHandle message_pipe, ConnectorConfig config, scoped_refptr<base::SingleThreadTaskRunner> runner) : message_pipe_(std::move(message_pipe)), task_runner_(std::move(runner)), + nesting_observer_(MessageLoopNestingObserver::GetForThread()), weak_factory_(this) { if (config == MULTI_THREADED_SEND) lock_.emplace(); @@ -77,16 +188,24 @@ bool Connector::WaitForIncomingMessage(MojoDeadline deadline) { ResumeIncomingMethodCallProcessing(); - MojoResult rv = - Wait(message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE, deadline, nullptr); - if (rv == MOJO_RESULT_SHOULD_WAIT || rv == MOJO_RESULT_DEADLINE_EXCEEDED) - return false; - if (rv != MOJO_RESULT_OK) { - // Users that call WaitForIncomingMessage() should expect their code to be - // re-entered, so we call the error handler synchronously. - HandleError(rv != MOJO_RESULT_FAILED_PRECONDITION, false); + // TODO(rockot): Use a timed Wait here. Nobody uses anything but 0 or + // INDEFINITE deadlines at present, so we only support those. + DCHECK(deadline == 0 || deadline == MOJO_DEADLINE_INDEFINITE); + + MojoResult rv = MOJO_RESULT_UNKNOWN; + if (deadline == 0 && !message_pipe_->QuerySignalsState().readable()) return false; + + if (deadline == MOJO_DEADLINE_INDEFINITE) { + rv = Wait(message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE); + if (rv != MOJO_RESULT_OK) { + // Users that call WaitForIncomingMessage() should expect their code to be + // re-entered, so we call the error handler synchronously. + HandleError(rv != MOJO_RESULT_FAILED_PRECONDITION, false); + return false; + } } + ignore_result(ReadSingleMessage(&rv)); return (rv == MOJO_RESULT_OK); } @@ -211,6 +330,7 @@ void Connector::OnHandleReadyInternal(MojoResult result) { HandleError(result != MOJO_RESULT_FAILED_PRECONDITION, false); return; } + ReadAllAvailableMessages(); // At this point, this object might have been deleted. Return. } @@ -219,10 +339,11 @@ void Connector::WaitToReadMore() { CHECK(!paused_); DCHECK(!handle_watcher_); - handle_watcher_.reset(new Watcher(FROM_HERE, task_runner_)); + handle_watcher_.reset(new SimpleWatcher( + FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL, task_runner_)); if (heap_profiler_tag_) handle_watcher_->set_heap_profiler_tag(heap_profiler_tag_); - MojoResult rv = handle_watcher_->Start( + MojoResult rv = handle_watcher_->Watch( message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE, base::Bind(&Connector::OnWatcherHandleReady, base::Unretained(this))); @@ -232,6 +353,8 @@ void Connector::WaitToReadMore() { task_runner_->PostTask( FROM_HERE, base::Bind(&Connector::OnWatcherHandleReady, weak_self_, rv)); + } else { + handle_watcher_->ArmOrNotify(); } if (allow_woken_up_by_others_) { @@ -254,17 +377,25 @@ bool Connector::ReadSingleMessage(MojoResult* read_result) { *read_result = rv; if (rv == MOJO_RESULT_OK) { + base::Optional<ActiveDispatchTracker> dispatch_tracker; + if (!is_dispatching_ && nesting_observer_) { + is_dispatching_ = true; + dispatch_tracker.emplace(weak_self); + } + receiver_result = incoming_receiver_ && incoming_receiver_->Accept(&message); - } - if (!weak_self) - return false; + if (!weak_self) + return false; - if (rv == MOJO_RESULT_SHOULD_WAIT) + if (dispatch_tracker) { + is_dispatching_ = false; + dispatch_tracker.reset(); + } + } else if (rv == MOJO_RESULT_SHOULD_WAIT) { return true; - - if (rv != MOJO_RESULT_OK) { + } else { HandleError(rv != MOJO_RESULT_FAILED_PRECONDITION, false); return false; } @@ -278,19 +409,36 @@ bool Connector::ReadSingleMessage(MojoResult* read_result) { void Connector::ReadAllAvailableMessages() { while (!error_) { + base::WeakPtr<Connector> weak_self = weak_self_; MojoResult rv; - if (!ReadSingleMessage(&rv)) { - // Return immediately without touching any members. |this| may have been - // destroyed. + // May delete |this.| + if (!ReadSingleMessage(&rv)) return; - } - if (paused_) + if (!weak_self || paused_) return; - if (rv == MOJO_RESULT_SHOULD_WAIT) - break; + DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_SHOULD_WAIT); + + if (rv == MOJO_RESULT_SHOULD_WAIT) { + // Attempt to re-arm the Watcher. + MojoResult ready_result; + MojoResult arm_result = handle_watcher_->Arm(&ready_result); + if (arm_result == MOJO_RESULT_OK) + return; + + // The watcher is already ready to notify again. + DCHECK_EQ(MOJO_RESULT_FAILED_PRECONDITION, arm_result); + + if (ready_result == MOJO_RESULT_FAILED_PRECONDITION) { + HandleError(false, false); + return; + } + + // There's more to read now, so we'll just keep looping. + DCHECK_EQ(MOJO_RESULT_OK, ready_result); + } } } diff --git a/mojo/public/cpp/bindings/lib/control_message_handler.cc b/mojo/public/cpp/bindings/lib/control_message_handler.cc index c90aada..1b7bb78 100644 --- a/mojo/public/cpp/bindings/lib/control_message_handler.cc +++ b/mojo/public/cpp/bindings/lib/control_message_handler.cc @@ -9,6 +9,7 @@ #include <utility> #include "base/logging.h" +#include "base/macros.h" #include "mojo/public/cpp/bindings/lib/message_builder.h" #include "mojo/public/cpp/bindings/lib/serialization.h" #include "mojo/public/cpp/bindings/lib/validation_util.h" @@ -80,19 +81,20 @@ bool ControlMessageHandler::Accept(Message* message) { bool ControlMessageHandler::AcceptWithResponder( Message* message, - MessageReceiverWithStatus* responder) { + std::unique_ptr<MessageReceiverWithStatus> responder) { if (!ValidateControlRequestWithResponse(message)) return false; if (message->header()->name == interface_control::kRunMessageId) - return Run(message, responder); + return Run(message, std::move(responder)); NOTREACHED(); return false; } -bool ControlMessageHandler::Run(Message* message, - MessageReceiverWithStatus* responder) { +bool ControlMessageHandler::Run( + Message* message, + std::unique_ptr<MessageReceiverWithStatus> responder) { interface_control::internal::RunMessageParams_Data* params = reinterpret_cast<interface_control::internal::RunMessageParams_Data*>( message->mutable_payload()); @@ -124,9 +126,7 @@ bool ControlMessageHandler::Run(Message* message, nullptr; Serialize<interface_control::RunResponseMessageParamsDataView>( response_params_ptr, builder.buffer(), &response_params, &context_); - bool ok = responder->Accept(builder.message()); - ALLOW_UNUSED_LOCAL(ok); - delete responder; + ignore_result(responder->Accept(builder.message())); return true; } diff --git a/mojo/public/cpp/bindings/lib/control_message_handler.h b/mojo/public/cpp/bindings/lib/control_message_handler.h index 3c385e4..5d1f716 100644 --- a/mojo/public/cpp/bindings/lib/control_message_handler.h +++ b/mojo/public/cpp/bindings/lib/control_message_handler.h @@ -27,12 +27,13 @@ class MOJO_CPP_BINDINGS_EXPORT ControlMessageHandler // Call the following methods only if IsControlMessage() returned true. bool Accept(Message* message) override; - // Takes ownership of |responder|. - bool AcceptWithResponder(Message* message, - MessageReceiverWithStatus* responder) override; + bool AcceptWithResponder( + Message* message, + std::unique_ptr<MessageReceiverWithStatus> responder) override; private: - bool Run(Message* message, MessageReceiverWithStatus* responder); + bool Run(Message* message, + std::unique_ptr<MessageReceiverWithStatus> responder); bool RunOrClosePipe(Message* message); uint32_t interface_version_; diff --git a/mojo/public/cpp/bindings/lib/control_message_proxy.cc b/mojo/public/cpp/bindings/lib/control_message_proxy.cc index 23de991..d082b49 100644 --- a/mojo/public/cpp/bindings/lib/control_message_proxy.cc +++ b/mojo/public/cpp/bindings/lib/control_message_proxy.cc @@ -85,9 +85,10 @@ void SendRunMessage(MessageReceiverWithResponder* receiver, interface_control::internal::RunMessageParams_Data* params = nullptr; Serialize<interface_control::RunMessageParamsDataView>( params_ptr, builder.buffer(), ¶ms, &context); - MessageReceiver* responder = new RunResponseForwardToCallback(callback); - if (!receiver->AcceptWithResponder(builder.message(), responder)) - delete responder; + std::unique_ptr<MessageReceiver> responder = + base::MakeUnique<RunResponseForwardToCallback>(callback); + ignore_result( + receiver->AcceptWithResponder(builder.message(), std::move(responder))); } Message ConstructRunOrClosePipeMessage( @@ -115,8 +116,7 @@ void SendRunOrClosePipeMessage( interface_control::RunOrClosePipeInputPtr input_ptr) { Message message(ConstructRunOrClosePipeMessage(std::move(input_ptr))); - bool ok = receiver->Accept(&message); - ALLOW_UNUSED_LOCAL(ok); + ignore_result(receiver->Accept(&message)); } void RunVersionCallback( diff --git a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc index 3eca5a1..4682e72 100644 --- a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc +++ b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc @@ -234,8 +234,9 @@ bool InterfaceEndpointClient::Accept(Message* message) { return controller_->SendMessage(message); } -bool InterfaceEndpointClient::AcceptWithResponder(Message* message, - MessageReceiver* responder) { +bool InterfaceEndpointClient::AcceptWithResponder( + Message* message, + std::unique_ptr<MessageReceiver> responder) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(message->has_flag(Message::kFlagExpectsResponse)); DCHECK(!handle_.pending_association()); @@ -261,15 +262,13 @@ bool InterfaceEndpointClient::AcceptWithResponder(Message* message, return false; if (!is_sync) { - // We assume ownership of |responder|. - async_responders_[request_id] = base::WrapUnique(responder); + async_responders_[request_id] = std::move(responder); return true; } SyncCallRestrictions::AssertSyncCallAllowed(); bool response_received = false; - std::unique_ptr<MessageReceiver> sync_responder(responder); sync_responses_.insert(std::make_pair( request_id, base::MakeUnique<SyncResponseInfo>(&response_received))); @@ -282,11 +281,10 @@ bool InterfaceEndpointClient::AcceptWithResponder(Message* message, auto iter = sync_responses_.find(request_id); DCHECK_EQ(&response_received, iter->second->response_received); if (response_received) - ignore_result(sync_responder->Accept(&iter->second->response)); + ignore_result(responder->Accept(&iter->second->response)); sync_responses_.erase(iter); } - // Return true means that we take ownership of |responder|. return true; } @@ -375,17 +373,16 @@ bool InterfaceEndpointClient::HandleValidatedMessage(Message* message) { } if (message->has_flag(Message::kFlagExpectsResponse)) { - MessageReceiverWithStatus* responder = - new ResponderThunk(weak_ptr_factory_.GetWeakPtr(), task_runner_); - bool ok = false; + std::unique_ptr<MessageReceiverWithStatus> responder = + base::MakeUnique<ResponderThunk>(weak_ptr_factory_.GetWeakPtr(), + task_runner_); if (mojo::internal::ControlMessageHandler::IsControlMessage(message)) { - ok = control_message_handler_.AcceptWithResponder(message, responder); + return control_message_handler_.AcceptWithResponder(message, + std::move(responder)); } else { - ok = incoming_receiver_->AcceptWithResponder(message, responder); + return incoming_receiver_->AcceptWithResponder(message, + std::move(responder)); } - if (!ok) - delete responder; - return ok; } else if (message->has_flag(Message::kFlagIsResponse)) { uint64_t request_id = message->request_id(); diff --git a/mojo/public/cpp/bindings/lib/interface_ptr_state.h b/mojo/public/cpp/bindings/lib/interface_ptr_state.h index 8f5b4ff..fa54979 100644 --- a/mojo/public/cpp/bindings/lib/interface_ptr_state.h +++ b/mojo/public/cpp/bindings/lib/interface_ptr_state.h @@ -163,7 +163,7 @@ class InterfacePtrState { void ForwardMessageWithResponder(Message message, std::unique_ptr<MessageReceiver> responder) { ConfigureProxyIfNecessary(); - endpoint_client_->AcceptWithResponder(&message, responder.release()); + endpoint_client_->AcceptWithResponder(&message, std::move(responder)); } private: diff --git a/mojo/public/cpp/bindings/lib/multiplex_router.cc b/mojo/public/cpp/bindings/lib/multiplex_router.cc index 2da459a..ff7c678 100644 --- a/mojo/public/cpp/bindings/lib/multiplex_router.cc +++ b/mojo/public/cpp/bindings/lib/multiplex_router.cc @@ -14,11 +14,12 @@ #include "base/memory/ptr_util.h" #include "base/single_thread_task_runner.h" #include "base/stl_util.h" +#include "base/synchronization/waitable_event.h" #include "base/threading/thread_task_runner_handle.h" #include "mojo/public/cpp/bindings/interface_endpoint_client.h" #include "mojo/public/cpp/bindings/interface_endpoint_controller.h" #include "mojo/public/cpp/bindings/lib/may_auto_lock.h" -#include "mojo/public/cpp/bindings/sync_handle_watcher.h" +#include "mojo/public/cpp/bindings/sync_event_watcher.h" namespace mojo { namespace internal { @@ -28,7 +29,7 @@ namespace internal { // No one other than the router's |endpoints_| and |tasks_| should hold refs to // this object. class MultiplexRouter::InterfaceEndpoint - : public base::RefCounted<InterfaceEndpoint>, + : public base::RefCountedThreadSafe<InterfaceEndpoint>, public InterfaceEndpointController { public: InterfaceEndpoint(MultiplexRouter* router, InterfaceId id) @@ -37,8 +38,7 @@ class MultiplexRouter::InterfaceEndpoint closed_(false), peer_closed_(false), handle_created_(false), - client_(nullptr), - event_signalled_(false) {} + client_(nullptr) {} // --------------------------------------------------------------------------- // The following public methods are safe to call from any threads without @@ -108,33 +108,20 @@ class MultiplexRouter::InterfaceEndpoint void SignalSyncMessageEvent() { router_->AssertLockAcquired(); - if (event_signalled_) + if (sync_message_event_signaled_) return; - - event_signalled_ = true; - if (!sync_message_event_sender_.is_valid()) - return; - - MojoResult result = - WriteMessageRaw(sync_message_event_sender_.get(), nullptr, 0, nullptr, - 0, MOJO_WRITE_MESSAGE_FLAG_NONE); - DCHECK_EQ(MOJO_RESULT_OK, result); + sync_message_event_signaled_ = true; + if (sync_message_event_) + sync_message_event_->Signal(); } void ResetSyncMessageSignal() { router_->AssertLockAcquired(); - - if (!event_signalled_) - return; - - event_signalled_ = false; - if (!sync_message_event_receiver_.is_valid()) + if (!sync_message_event_signaled_) return; - - MojoResult result = - ReadMessageRaw(sync_message_event_receiver_.get(), nullptr, nullptr, - nullptr, nullptr, MOJO_READ_MESSAGE_FLAG_MAY_DISCARD); - DCHECK_EQ(MOJO_RESULT_OK, result); + sync_message_event_signaled_ = false; + if (sync_message_event_) + sync_message_event_->Reset(); } // --------------------------------------------------------------------------- @@ -163,7 +150,7 @@ class MultiplexRouter::InterfaceEndpoint } private: - friend class base::RefCounted<InterfaceEndpoint>; + friend class base::RefCountedThreadSafe<InterfaceEndpoint>; ~InterfaceEndpoint() override { router_->AssertLockAcquired(); @@ -174,14 +161,10 @@ class MultiplexRouter::InterfaceEndpoint DCHECK(!sync_watcher_); } - void OnHandleReady(MojoResult result) { + void OnSyncEventSignaled() { DCHECK(task_runner_->BelongsToCurrentThread()); scoped_refptr<MultiplexRouter> router_protector(router_); - // Because we never close |sync_message_event_{sender,receiver}_| before - // destruction or set a deadline, |result| should always be MOJO_RESULT_OK. - DCHECK_EQ(MOJO_RESULT_OK, result); - MayAutoLock locker(&router_->lock_); scoped_refptr<InterfaceEndpoint> self_protector(this); @@ -207,25 +190,18 @@ class MultiplexRouter::InterfaceEndpoint { MayAutoLock locker(&router_->lock_); - - if (!sync_message_event_sender_.is_valid()) { - MojoResult result = - CreateMessagePipe(nullptr, &sync_message_event_sender_, - &sync_message_event_receiver_); - DCHECK_EQ(MOJO_RESULT_OK, result); - - if (event_signalled_) { - // Reset the flag so that SignalSyncMessageEvent() will actually - // signal using the newly-created message pipe. - event_signalled_ = false; - SignalSyncMessageEvent(); - } + if (!sync_message_event_) { + sync_message_event_.emplace( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + if (sync_message_event_signaled_) + sync_message_event_->Signal(); } } - - sync_watcher_.reset(new SyncHandleWatcher( - sync_message_event_receiver_.get(), MOJO_HANDLE_SIGNAL_READABLE, - base::Bind(&InterfaceEndpoint::OnHandleReady, base::Unretained(this)))); + sync_watcher_.reset( + new SyncEventWatcher(&sync_message_event_.value(), + base::Bind(&InterfaceEndpoint::OnSyncEventSignaled, + base::Unretained(this)))); } // --------------------------------------------------------------------------- @@ -253,20 +229,18 @@ class MultiplexRouter::InterfaceEndpoint // Not owned. It is null if no client is attached to this endpoint. InterfaceEndpointClient* client_; - // A message pipe used as an event to signal that sync messages are available. - // The message pipe handles are initialized under the router's lock and remain - // unchanged afterwards. They may be accessed outside of the router's lock - // later. - ScopedMessagePipeHandle sync_message_event_sender_; - ScopedMessagePipeHandle sync_message_event_receiver_; - bool event_signalled_; + // An event used to signal that sync messages are available. The event is + // initialized under the router's lock and remains unchanged afterwards. It + // may be accessed outside of the router's lock later. + base::Optional<base::WaitableEvent> sync_message_event_; + bool sync_message_event_signaled_ = false; // --------------------------------------------------------------------------- // The following members are only valid while a client is attached. They are // used exclusively on the client's thread. They may be accessed outside of // the router's lock. - std::unique_ptr<SyncHandleWatcher> sync_watcher_; + std::unique_ptr<SyncEventWatcher> sync_watcher_; DISALLOW_COPY_AND_ASSIGN(InterfaceEndpoint); }; diff --git a/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc b/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc index 701108e..1029c2c 100644 --- a/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc +++ b/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc @@ -7,8 +7,8 @@ #include <stddef.h> #include <utility> -#include "base/compiler_specific.h" #include "base/logging.h" +#include "base/macros.h" #include "mojo/public/cpp/bindings/lib/message_builder.h" #include "mojo/public/cpp/bindings/lib/serialization.h" #include "mojo/public/interfaces/bindings/pipe_control_messages.mojom.h" @@ -44,8 +44,7 @@ void PipeControlMessageProxy::NotifyPeerEndpointClosed( InterfaceId id, const base::Optional<DisconnectReason>& reason) { Message message(ConstructPeerEndpointClosedMessage(id, reason)); - bool ok = receiver_->Accept(&message); - ALLOW_UNUSED_LOCAL(ok); + ignore_result(receiver_->Accept(&message)); } // static diff --git a/mojo/public/cpp/bindings/lib/serialization.h b/mojo/public/cpp/bindings/lib/serialization.h index 359b02b..2a7d288 100644 --- a/mojo/public/cpp/bindings/lib/serialization.h +++ b/mojo/public/cpp/bindings/lib/serialization.h @@ -64,7 +64,10 @@ DataArrayType StructSerializeImpl(UserType* input) { } template <typename MojomType, typename DataArrayType, typename UserType> -bool StructDeserializeImpl(const DataArrayType& input, UserType* output) { +bool StructDeserializeImpl(const DataArrayType& input, + UserType* output, + bool (*validate_func)(const void*, + ValidationContext*)) { static_assert(BelongsTo<MojomType, MojomTypeCategory::STRUCT>::value, "Unexpected type."); using DataType = typename MojomTypeTraits<MojomType>::Data; @@ -86,7 +89,7 @@ bool StructDeserializeImpl(const DataArrayType& input, UserType* output) { ValidationContext validation_context(input_buffer, input.size(), 0, 0); bool result = false; - if (DataType::Validate(input_buffer, &validation_context)) { + if (validate_func(input_buffer, &validation_context)) { auto data = reinterpret_cast<DataType*>(input_buffer); SerializationContext context; result = Deserialize<MojomType>(data, output, &context); diff --git a/mojo/public/cpp/bindings/lib/sync_event_watcher.cc b/mojo/public/cpp/bindings/lib/sync_event_watcher.cc new file mode 100644 index 0000000..b1c97e3 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/sync_event_watcher.cc @@ -0,0 +1,67 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/sync_event_watcher.h" + +#include "base/logging.h" + +namespace mojo { + +SyncEventWatcher::SyncEventWatcher(base::WaitableEvent* event, + const base::Closure& callback) + : event_(event), + callback_(callback), + registry_(SyncHandleRegistry::current()), + destroyed_(new base::RefCountedData<bool>(false)) {} + +SyncEventWatcher::~SyncEventWatcher() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (registered_) + registry_->UnregisterEvent(event_); + destroyed_->data = true; +} + +void SyncEventWatcher::AllowWokenUpBySyncWatchOnSameThread() { + DCHECK(thread_checker_.CalledOnValidThread()); + IncrementRegisterCount(); +} + +bool SyncEventWatcher::SyncWatch(const bool* should_stop) { + DCHECK(thread_checker_.CalledOnValidThread()); + IncrementRegisterCount(); + if (!registered_) { + DecrementRegisterCount(); + return false; + } + + // This object may be destroyed during the Wait() call. So we have to preserve + // the boolean that Wait uses. + auto destroyed = destroyed_; + const bool* should_stop_array[] = {should_stop, &destroyed->data}; + bool result = registry_->Wait(should_stop_array, 2); + + // This object has been destroyed. + if (destroyed->data) + return false; + + DecrementRegisterCount(); + return result; +} + +void SyncEventWatcher::IncrementRegisterCount() { + register_request_count_++; + if (!registered_) + registered_ = registry_->RegisterEvent(event_, callback_); +} + +void SyncEventWatcher::DecrementRegisterCount() { + DCHECK_GT(register_request_count_, 0u); + register_request_count_--; + if (register_request_count_ == 0 && registered_) { + registry_->UnregisterEvent(event_); + registered_ = false; + } +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/sync_handle_registry.cc b/mojo/public/cpp/bindings/lib/sync_handle_registry.cc index 5ae763b..fd3df39 100644 --- a/mojo/public/cpp/bindings/lib/sync_handle_registry.cc +++ b/mojo/public/cpp/bindings/lib/sync_handle_registry.cc @@ -37,8 +37,7 @@ bool SyncHandleRegistry::RegisterHandle(const Handle& handle, if (base::ContainsKey(handles_, handle)) return false; - MojoResult result = MojoAddHandle(wait_set_handle_.get().value(), - handle.value(), handle_signals); + MojoResult result = wait_set_.AddHandle(handle, handle_signals); if (result != MOJO_RESULT_OK) return false; @@ -51,19 +50,35 @@ void SyncHandleRegistry::UnregisterHandle(const Handle& handle) { if (!base::ContainsKey(handles_, handle)) return; - MojoResult result = - MojoRemoveHandle(wait_set_handle_.get().value(), handle.value()); + MojoResult result = wait_set_.RemoveHandle(handle); DCHECK_EQ(MOJO_RESULT_OK, result); handles_.erase(handle); } -bool SyncHandleRegistry::WatchAllHandles(const bool* should_stop[], - size_t count) { +bool SyncHandleRegistry::RegisterEvent(base::WaitableEvent* event, + const base::Closure& callback) { + auto result = events_.insert({event, callback}); + DCHECK(result.second); + MojoResult rv = wait_set_.AddEvent(event); + if (rv == MOJO_RESULT_OK) + return true; + DCHECK_EQ(MOJO_RESULT_ALREADY_EXISTS, rv); + return false; +} + +void SyncHandleRegistry::UnregisterEvent(base::WaitableEvent* event) { + auto it = events_.find(event); + DCHECK(it != events_.end()); + events_.erase(it); + MojoResult rv = wait_set_.RemoveEvent(event); + DCHECK_EQ(MOJO_RESULT_OK, rv); +} + +bool SyncHandleRegistry::Wait(const bool* should_stop[], size_t count) { DCHECK(thread_checker_.CalledOnValidThread()); - MojoResult result; - uint32_t num_ready_handles; - MojoHandle ready_handle; + size_t num_ready_handles; + Handle ready_handle; MojoResult ready_handle_result; scoped_refptr<SyncHandleRegistry> preserver(this); @@ -71,36 +86,30 @@ bool SyncHandleRegistry::WatchAllHandles(const bool* should_stop[], for (size_t i = 0; i < count; ++i) if (*should_stop[i]) return true; - do { - result = Wait(wait_set_handle_.get(), MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, nullptr); - if (result != MOJO_RESULT_OK) - return false; - - // TODO(yzshen): Theoretically it can reduce sync call re-entrancy if we - // give priority to the handle that is waiting for sync response. - num_ready_handles = 1; - result = MojoGetReadyHandles(wait_set_handle_.get().value(), - &num_ready_handles, &ready_handle, - &ready_handle_result, nullptr); - if (result != MOJO_RESULT_OK && result != MOJO_RESULT_SHOULD_WAIT) - return false; - } while (result == MOJO_RESULT_SHOULD_WAIT); - - const auto iter = handles_.find(Handle(ready_handle)); - iter->second.Run(ready_handle_result); + + // TODO(yzshen): Theoretically it can reduce sync call re-entrancy if we + // give priority to the handle that is waiting for sync response. + base::WaitableEvent* ready_event = nullptr; + num_ready_handles = 1; + wait_set_.Wait(&ready_event, &num_ready_handles, &ready_handle, + &ready_handle_result); + if (num_ready_handles) { + DCHECK_EQ(1u, num_ready_handles); + const auto iter = handles_.find(ready_handle); + iter->second.Run(ready_handle_result); + } + + if (ready_event) { + const auto iter = events_.find(ready_event); + DCHECK(iter != events_.end()); + iter->second.Run(); + } }; return false; } SyncHandleRegistry::SyncHandleRegistry() { - MojoHandle handle; - MojoResult result = MojoCreateWaitSet(&handle); - CHECK_EQ(MOJO_RESULT_OK, result); - wait_set_handle_.reset(Handle(handle)); - CHECK(wait_set_handle_.is_valid()); - DCHECK(!g_current_sync_handle_watcher.Pointer()->Get()); g_current_sync_handle_watcher.Pointer()->Set(this); } diff --git a/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc b/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc index 92b91f4..f20af56 100644 --- a/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc +++ b/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc @@ -41,11 +41,11 @@ bool SyncHandleWatcher::SyncWatch(const bool* should_stop) { return false; } - // This object may be destroyed during the WatchAllHandles() call. So we have - // to preserve the boolean that WatchAllHandles uses. + // This object may be destroyed during the Wait() call. So we have to preserve + // the boolean that Wait uses. auto destroyed = destroyed_; const bool* should_stop_array[] = {should_stop, &destroyed->data}; - bool result = registry_->WatchAllHandles(should_stop_array, 2); + bool result = registry_->Wait(should_stop_array, 2); // This object has been destroyed. if (destroyed->data) diff --git a/mojo/public/cpp/bindings/message.h b/mojo/public/cpp/bindings/message.h index 65d6cec..48e6900 100644 --- a/mojo/public/cpp/bindings/message.h +++ b/mojo/public/cpp/bindings/message.h @@ -183,14 +183,8 @@ class MessageReceiverWithResponder : public MessageReceiver { // responder) to handle the response message generated from the given // message. The responder's Accept method may be called during // AcceptWithResponder or some time after its return. - // - // NOTE: Upon returning true, AcceptWithResponder assumes ownership of - // |responder| and will delete it after calling |responder->Accept| or upon - // its own destruction. - // - // TODO(yzshen): consider changing |responder| to - // std::unique_ptr<MessageReceiver>. - virtual bool AcceptWithResponder(Message* message, MessageReceiver* responder) + virtual bool AcceptWithResponder(Message* message, + std::unique_ptr<MessageReceiver> responder) WARN_UNUSED_RESULT = 0; }; @@ -222,16 +216,9 @@ class MessageReceiverWithResponderStatus : public MessageReceiver { // the responder) to handle the response message generated from the given // message. Any of the responder's methods (Accept or IsValid) may be called // during AcceptWithResponder or some time after its return. - // - // NOTE: Upon returning true, AcceptWithResponder assumes ownership of - // |responder| and will delete it after calling |responder->Accept| or upon - // its own destruction. - // - // TODO(yzshen): consider changing |responder| to - // std::unique_ptr<MessageReceiver>. virtual bool AcceptWithResponder(Message* message, - MessageReceiverWithStatus* responder) - WARN_UNUSED_RESULT = 0; + std::unique_ptr<MessageReceiverWithStatus> + responder) WARN_UNUSED_RESULT = 0; }; class MOJO_CPP_BINDINGS_EXPORT PassThroughFilter diff --git a/mojo/public/cpp/bindings/sync_call_restrictions.h b/mojo/public/cpp/bindings/sync_call_restrictions.h index 39a77a8..5529042 100644 --- a/mojo/public/cpp/bindings/sync_call_restrictions.h +++ b/mojo/public/cpp/bindings/sync_call_restrictions.h @@ -19,6 +19,10 @@ namespace leveldb { class LevelDBMojoProxy; } +namespace prefs { +class PersistentPrefStoreClient; +} + namespace ui { class Gpu; } @@ -58,6 +62,9 @@ class MOJO_CPP_BINDINGS_EXPORT SyncCallRestrictions { friend class ui::Gpu; // http://crbug.com/620058 // LevelDBMojoProxy makes same-process sync calls from the DB thread. friend class leveldb::LevelDBMojoProxy; + // Pref service connection is sync at startup. + friend class prefs::PersistentPrefStoreClient; + // END ALLOWED USAGE. // BEGIN USAGE THAT NEEDS TO BE FIXED. diff --git a/mojo/public/cpp/bindings/sync_event_watcher.h b/mojo/public/cpp/bindings/sync_event_watcher.h new file mode 100644 index 0000000..6e25484 --- /dev/null +++ b/mojo/public/cpp/bindings/sync_event_watcher.h @@ -0,0 +1,68 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_SYNC_EVENT_WATCHER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_SYNC_EVENT_WATCHER_H_ + +#include <stddef.h> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread_checker.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/sync_handle_registry.h" + +namespace mojo { + +// SyncEventWatcher supports waiting on a base::WaitableEvent to signal while +// also allowing other SyncEventWatchers and SyncHandleWatchers on the same +// thread to wake up as needed. +// +// This class is not thread safe. +class MOJO_CPP_BINDINGS_EXPORT SyncEventWatcher { + public: + SyncEventWatcher(base::WaitableEvent* event, const base::Closure& callback); + + ~SyncEventWatcher(); + + // Registers |event_| with SyncHandleRegistry, so that when others perform + // sync watching on the same thread, |event_| will be watched along with them. + void AllowWokenUpBySyncWatchOnSameThread(); + + // Waits on |event_| plus all other events and handles registered with this + // thread's SyncHandleRegistry, running callbacks synchronously for any ready + // events and handles. + // This method: + // - returns true when |should_stop| is set to true; + // - return false when any error occurs, including this object being + // destroyed during a callback. + bool SyncWatch(const bool* should_stop); + + private: + void IncrementRegisterCount(); + void DecrementRegisterCount(); + + base::WaitableEvent* const event_; + const base::Closure callback_; + + // Whether |event_| has been registered with SyncHandleRegistry. + bool registered_ = false; + + // If non-zero, |event_| should be registered with SyncHandleRegistry. + size_t register_request_count_ = 0; + + scoped_refptr<SyncHandleRegistry> registry_; + + scoped_refptr<base::RefCountedData<bool>> destroyed_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(SyncEventWatcher); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_SYNC_EVENT_WATCHER_H_ diff --git a/mojo/public/cpp/bindings/sync_handle_registry.h b/mojo/public/cpp/bindings/sync_handle_registry.h index b5415af..afb3b56 100644 --- a/mojo/public/cpp/bindings/sync_handle_registry.h +++ b/mojo/public/cpp/bindings/sync_handle_registry.h @@ -5,14 +5,17 @@ #ifndef MOJO_PUBLIC_CPP_BINDINGS_SYNC_HANDLE_REGISTRY_H_ #define MOJO_PUBLIC_CPP_BINDINGS_SYNC_HANDLE_REGISTRY_H_ +#include <map> #include <unordered_map> #include "base/callback.h" #include "base/macros.h" #include "base/memory/ref_counted.h" +#include "base/synchronization/waitable_event.h" #include "base/threading/thread_checker.h" #include "mojo/public/cpp/bindings/bindings_export.h" #include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/wait_set.h" namespace mojo { @@ -33,29 +36,30 @@ class MOJO_CPP_BINDINGS_EXPORT SyncHandleRegistry void UnregisterHandle(const Handle& handle); - // Waits on all the registered handles and runs callbacks synchronously for - // those ready handles. + // Registers a |base::WaitableEvent| which can be used to wake up + // Wait() before any handle signals. |event| is not owned, and if it signals + // during Wait(), |callback| is invoked. Returns |true| if registered + // successfully or |false| if |event| was already registered. + bool RegisterEvent(base::WaitableEvent* event, const base::Closure& callback); + + void UnregisterEvent(base::WaitableEvent* event); + + // Waits on all the registered handles and events and runs callbacks + // synchronously for any that become ready. // The method: // - returns true when any element of |should_stop| is set to true; // - returns false when any error occurs. - bool WatchAllHandles(const bool* should_stop[], size_t count); + bool Wait(const bool* should_stop[], size_t count); private: friend class base::RefCounted<SyncHandleRegistry>; - struct HandleHasher { - size_t operator()(const Handle& handle) const { - return std::hash<uint32_t>()(static_cast<uint32_t>(handle.value())); - } - }; - using HandleMap = std::unordered_map<Handle, HandleCallback, HandleHasher>; - SyncHandleRegistry(); ~SyncHandleRegistry(); - HandleMap handles_; - - ScopedHandle wait_set_handle_; + WaitSet wait_set_; + std::map<Handle, HandleCallback> handles_; + std::map<base::WaitableEvent*, base::Closure> events_; base::ThreadChecker thread_checker_; diff --git a/mojo/public/cpp/bindings/tests/BUILD.gn b/mojo/public/cpp/bindings/tests/BUILD.gn index 6244226..668ca6d 100644 --- a/mojo/public/cpp/bindings/tests/BUILD.gn +++ b/mojo/public/cpp/bindings/tests/BUILD.gn @@ -49,9 +49,9 @@ source_set("tests") { "//mojo/public/cpp/test_support:test_utils", "//mojo/public/interfaces/bindings/tests:test_associated_interfaces", "//mojo/public/interfaces/bindings/tests:test_export_component", + "//mojo/public/interfaces/bindings/tests:test_export_component2", "//mojo/public/interfaces/bindings/tests:test_exported_import", "//mojo/public/interfaces/bindings/tests:test_interfaces", - "//mojo/public/interfaces/bindings/tests:test_interfaces_experimental", "//mojo/public/interfaces/bindings/tests:test_struct_traits_interfaces", "//testing/gtest", ] diff --git a/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc index 625c49c..be225e4 100644 --- a/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc +++ b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc @@ -1178,6 +1178,12 @@ TEST_F(AssociatedInterfaceTest, CloseWithoutBindingAssociatedRequest) { run_loop.Run(); } +TEST_F(AssociatedInterfaceTest, GetIsolatedInterface) { + IntegerSenderAssociatedPtr sender; + GetIsolatedInterface(MakeRequest(&sender).PassHandle()); + sender->Send(42); +} + } // namespace } // namespace test } // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc b/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc index 0c777ec..569eb51 100644 --- a/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc +++ b/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <utility> + #include "base/bind.h" #include "base/callback.h" #include "base/message_loop/message_loop.h" @@ -30,18 +32,18 @@ class TestTaskRunner : public base::SingleThreadTaskRunner { base::WaitableEvent::InitialState::NOT_SIGNALED) {} bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here, - const base::Closure& task, + base::OnceClosure task, base::TimeDelta delay) override { NOTREACHED(); return false; } bool PostDelayedTask(const tracked_objects::Location& from_here, - const base::Closure& task, + base::OnceClosure task, base::TimeDelta delay) override { { base::AutoLock locker(lock_); - tasks_.push(task); + tasks_.push(std::move(task)); } task_ready_.Signal(); return true; @@ -59,12 +61,12 @@ class TestTaskRunner : public base::SingleThreadTaskRunner { { base::AutoLock locker(lock_); while (!tasks_.empty()) { - auto task = tasks_.front(); + auto task = std::move(tasks_.front()); tasks_.pop(); { base::AutoUnlock unlocker(lock_); - task.Run(); + std::move(task).Run(); if (quit_called_) return; } @@ -87,12 +89,12 @@ class TestTaskRunner : public base::SingleThreadTaskRunner { { base::AutoLock locker(lock_); if (!tasks_.empty()) { - auto task = tasks_.front(); + auto task = std::move(tasks_.front()); tasks_.pop(); { base::AutoUnlock unlocker(lock_); - task.Run(); + std::move(task).Run(); return; } } @@ -110,7 +112,7 @@ class TestTaskRunner : public base::SingleThreadTaskRunner { // Protect |tasks_|. base::Lock lock_; - std::queue<base::Closure> tasks_; + std::queue<base::OnceClosure> tasks_; DISALLOW_COPY_AND_ASSIGN(TestTaskRunner); }; diff --git a/mojo/public/cpp/bindings/tests/bindings_perftest.cc b/mojo/public/cpp/bindings/tests/bindings_perftest.cc index 6a50de4..65b3c8c 100644 --- a/mojo/public/cpp/bindings/tests/bindings_perftest.cc +++ b/mojo/public/cpp/bindings/tests/bindings_perftest.cc @@ -160,8 +160,9 @@ class PingPongPaddle : public MessageReceiverWithResponderStatus { return true; } - bool AcceptWithResponder(Message* message, - MessageReceiverWithStatus* responder) override { + bool AcceptWithResponder( + Message* message, + std::unique_ptr<MessageReceiverWithStatus> responder) override { NOTREACHED(); return true; } @@ -232,8 +233,9 @@ class CounterReceiver : public MessageReceiverWithResponderStatus { return true; } - bool AcceptWithResponder(Message* message, - MessageReceiverWithStatus* responder) override { + bool AcceptWithResponder( + Message* message, + std::unique_ptr<MessageReceiverWithStatus> responder) override { NOTREACHED(); return true; } diff --git a/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc b/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc index 6797fe4..ef977af 100644 --- a/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc +++ b/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc @@ -10,6 +10,7 @@ #include "base/run_loop.h" #include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/cpp/system/wait.h" #include "mojo/public/cpp/test_support/test_utils.h" #include "mojo/public/interfaces/bindings/tests/sample_factory.mojom.h" #include "testing/gtest/include/gtest/gtest.h" @@ -111,8 +112,7 @@ class SampleFactoryImpl : public sample::Factory { MojoHandleSignalsState state; ASSERT_EQ(MOJO_RESULT_OK, - MojoWait(pipe.get().value(), MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); + mojo::Wait(pipe.get(), MOJO_HANDLE_SIGNAL_READABLE, &state)); ASSERT_TRUE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE); ASSERT_EQ(MOJO_RESULT_OK, ReadDataRaw( diff --git a/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc b/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc index 31963e0..8950928 100644 --- a/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc +++ b/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc @@ -72,8 +72,8 @@ TEST_F(MultiplexRouterTest, BasicRequestResponse) { MessageQueue message_queue; base::RunLoop run_loop; client0.AcceptWithResponder( - &request, - new MessageAccumulator(&message_queue, run_loop.QuitClosure())); + &request, base::MakeUnique<MessageAccumulator>(&message_queue, + run_loop.QuitClosure())); run_loop.Run(); @@ -91,8 +91,8 @@ TEST_F(MultiplexRouterTest, BasicRequestResponse) { base::RunLoop run_loop2; client0.AcceptWithResponder( - &request2, - new MessageAccumulator(&message_queue, run_loop2.QuitClosure())); + &request2, base::MakeUnique<MessageAccumulator>(&message_queue, + run_loop2.QuitClosure())); run_loop2.Run(); @@ -117,7 +117,8 @@ TEST_F(MultiplexRouterTest, BasicRequestResponse_Synchronous) { AllocRequestMessage(1, "hello", &request); MessageQueue message_queue; - client0.AcceptWithResponder(&request, new MessageAccumulator(&message_queue)); + client0.AcceptWithResponder( + &request, base::MakeUnique<MessageAccumulator>(&message_queue)); router1_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); router0_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); @@ -134,8 +135,8 @@ TEST_F(MultiplexRouterTest, BasicRequestResponse_Synchronous) { Message request2; AllocRequestMessage(1, "hello again", &request2); - client0.AcceptWithResponder(&request2, - new MessageAccumulator(&message_queue)); + client0.AcceptWithResponder( + &request2, base::MakeUnique<MessageAccumulator>(&message_queue)); router1_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); router0_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); @@ -167,8 +168,8 @@ TEST_F(MultiplexRouterTest, LazyResponses) { MessageQueue message_queue; base::RunLoop run_loop2; client0.AcceptWithResponder( - &request, - new MessageAccumulator(&message_queue, run_loop2.QuitClosure())); + &request, base::MakeUnique<MessageAccumulator>(&message_queue, + run_loop2.QuitClosure())); run_loop.Run(); // The request has been received but the response has not been sent yet. @@ -194,8 +195,8 @@ TEST_F(MultiplexRouterTest, LazyResponses) { base::RunLoop run_loop4; client0.AcceptWithResponder( - &request2, - new MessageAccumulator(&message_queue, run_loop4.QuitClosure())); + &request2, base::MakeUnique<MessageAccumulator>(&message_queue, + run_loop4.QuitClosure())); run_loop3.Run(); // The request has been received but the response has not been sent yet. @@ -246,7 +247,8 @@ TEST_F(MultiplexRouterTest, MissingResponses) { AllocRequestMessage(1, "hello", &request); MessageQueue message_queue; - client0.AcceptWithResponder(&request, new MessageAccumulator(&message_queue)); + client0.AcceptWithResponder( + &request, base::MakeUnique<MessageAccumulator>(&message_queue)); run_loop3.Run(); // The request has been received but no response has been sent. @@ -293,8 +295,8 @@ TEST_F(MultiplexRouterTest, LateResponse) { AllocRequestMessage(1, "hello", &request); MessageQueue message_queue; - client0.AcceptWithResponder(&request, - new MessageAccumulator(&message_queue)); + client0.AcceptWithResponder( + &request, base::MakeUnique<MessageAccumulator>(&message_queue)); run_loop.Run(); diff --git a/mojo/public/cpp/bindings/tests/router_test_util.cc b/mojo/public/cpp/bindings/tests/router_test_util.cc index b9b93d8..9bab1cb 100644 --- a/mojo/public/cpp/bindings/tests/router_test_util.cc +++ b/mojo/public/cpp/bindings/tests/router_test_util.cc @@ -58,14 +58,13 @@ bool ResponseGenerator::Accept(Message* message) { bool ResponseGenerator::AcceptWithResponder( Message* message, - MessageReceiverWithStatus* responder) { + std::unique_ptr<MessageReceiverWithStatus> responder) { EXPECT_TRUE(message->has_flag(Message::kFlagExpectsResponse)); bool result = SendResponse(message->name(), message->request_id(), reinterpret_cast<const char*>(message->payload()), - responder); + responder.get()); EXPECT_TRUE(responder->IsValid()); - delete responder; return result; } @@ -84,18 +83,16 @@ bool ResponseGenerator::SendResponse(uint32_t name, LazyResponseGenerator::LazyResponseGenerator(const base::Closure& closure) : responder_(nullptr), name_(0), request_id_(0), closure_(closure) {} -LazyResponseGenerator::~LazyResponseGenerator() { - delete responder_; -} +LazyResponseGenerator::~LazyResponseGenerator() = default; bool LazyResponseGenerator::AcceptWithResponder( Message* message, - MessageReceiverWithStatus* responder) { + std::unique_ptr<MessageReceiverWithStatus> responder) { name_ = message->name(); request_id_ = message->request_id(); request_string_ = std::string(reinterpret_cast<const char*>(message->payload())); - responder_ = responder; + responder_ = std::move(responder); if (!closure_.is_null()) { closure_.Run(); closure_.Reset(); @@ -105,9 +102,8 @@ bool LazyResponseGenerator::AcceptWithResponder( void LazyResponseGenerator::Complete(bool send_response) { if (send_response) { - SendResponse(name_, request_id_, request_string_.c_str(), responder_); + SendResponse(name_, request_id_, request_string_.c_str(), responder_.get()); } - delete responder_; responder_ = nullptr; } diff --git a/mojo/public/cpp/bindings/tests/router_test_util.h b/mojo/public/cpp/bindings/tests/router_test_util.h index c6fb372..dd6aff6 100644 --- a/mojo/public/cpp/bindings/tests/router_test_util.h +++ b/mojo/public/cpp/bindings/tests/router_test_util.h @@ -42,9 +42,9 @@ class ResponseGenerator : public MessageReceiverWithResponderStatus { bool Accept(Message* message) override; - bool AcceptWithResponder(Message* message, - MessageReceiverWithStatus* responder) override; - + bool AcceptWithResponder( + Message* message, + std::unique_ptr<MessageReceiverWithStatus> responder) override; bool SendResponse(uint32_t name, uint64_t request_id, const char* request_string, @@ -58,8 +58,9 @@ class LazyResponseGenerator : public ResponseGenerator { ~LazyResponseGenerator() override; - bool AcceptWithResponder(Message* message, - MessageReceiverWithStatus* responder) override; + bool AcceptWithResponder( + Message* message, + std::unique_ptr<MessageReceiverWithStatus> responder) override; bool has_responder() const { return !!responder_; } @@ -78,7 +79,7 @@ class LazyResponseGenerator : public ResponseGenerator { // also sends a response. void Complete(bool send_response); - MessageReceiverWithStatus* responder_; + std::unique_ptr<MessageReceiverWithStatus> responder_; uint32_t name_; uint64_t request_id_; std::string request_string_; diff --git a/mojo/public/cpp/bindings/tests/sample_service_unittest.cc b/mojo/public/cpp/bindings/tests/sample_service_unittest.cc index 579576f..1f95a27 100644 --- a/mojo/public/cpp/bindings/tests/sample_service_unittest.cc +++ b/mojo/public/cpp/bindings/tests/sample_service_unittest.cc @@ -295,8 +295,9 @@ class SimpleMessageReceiver : public mojo::MessageReceiverWithResponder { return stub.Accept(message); } - bool AcceptWithResponder(mojo::Message* message, - mojo::MessageReceiver* responder) override { + bool AcceptWithResponder( + mojo::Message* message, + std::unique_ptr<mojo::MessageReceiver> responder) override { return false; } }; diff --git a/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc b/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc index 4c06267..77b448a 100644 --- a/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc +++ b/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc @@ -15,6 +15,7 @@ #include "mojo/public/cpp/bindings/tests/struct_with_traits_impl.h" #include "mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h" #include "mojo/public/cpp/bindings/tests/variant_test_util.h" +#include "mojo/public/cpp/system/wait.h" #include "mojo/public/interfaces/bindings/tests/struct_with_traits.mojom.h" #include "mojo/public/interfaces/bindings/tests/test_native_types.mojom-blink.h" #include "mojo/public/interfaces/bindings/tests/test_native_types.mojom.h" @@ -391,8 +392,7 @@ TEST_F(StructTraitsTest, EchoMoveOnlyStructWithTraits) { WriteMessageRaw(mp.handle1.get(), kHello, kHelloSize, nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE)); - EXPECT_EQ(MOJO_RESULT_OK, Wait(received.get(), MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, Wait(received.get(), MOJO_HANDLE_SIGNAL_READABLE)); char buffer[10] = {0}; uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer)); diff --git a/mojo/public/cpp/bindings/tests/struct_unittest.cc b/mojo/public/cpp/bindings/tests/struct_unittest.cc index 13ba507..a687052 100644 --- a/mojo/public/cpp/bindings/tests/struct_unittest.cc +++ b/mojo/public/cpp/bindings/tests/struct_unittest.cc @@ -9,6 +9,7 @@ #include "mojo/public/cpp/bindings/lib/fixed_buffer.h" #include "mojo/public/cpp/system/message_pipe.h" +#include "mojo/public/interfaces/bindings/tests/test_export2.mojom.h" #include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" #include "testing/gtest/include/gtest/gtest.h" @@ -435,7 +436,7 @@ TEST_F(StructTest, Serialization_PublicAPI) { // Initialize it to non-null. RectPtr output(Rect::New()); - ASSERT_TRUE(Rect::Deserialize(std::move(data), &output)); + ASSERT_TRUE(Rect::Deserialize(data, &output)); EXPECT_TRUE(output.is_null()); } @@ -446,7 +447,7 @@ TEST_F(StructTest, Serialization_PublicAPI) { EXPECT_FALSE(data.empty()); EmptyStructPtr output; - ASSERT_TRUE(EmptyStruct::Deserialize(std::move(data), &output)); + ASSERT_TRUE(EmptyStruct::Deserialize(data, &output)); EXPECT_FALSE(output.is_null()); } @@ -457,7 +458,7 @@ TEST_F(StructTest, Serialization_PublicAPI) { auto data = Rect::Serialize(&rect); RectPtr output; - ASSERT_TRUE(Rect::Deserialize(std::move(data), &output)); + ASSERT_TRUE(Rect::Deserialize(data, &output)); EXPECT_TRUE(output.Equals(cloned_rect)); } @@ -475,7 +476,7 @@ TEST_F(StructTest, Serialization_PublicAPI) { // Make sure that the serialized result gets pointers encoded properly. auto cloned_data = data; NamedRegionPtr output; - ASSERT_TRUE(NamedRegion::Deserialize(std::move(cloned_data), &output)); + ASSERT_TRUE(NamedRegion::Deserialize(cloned_data, &output)); EXPECT_TRUE(output.Equals(cloned_region)); } @@ -485,7 +486,17 @@ TEST_F(StructTest, Serialization_PublicAPI) { auto data = Rect::Serialize(&rect); NamedRegionPtr output; - EXPECT_FALSE(NamedRegion::Deserialize(std::move(data), &output)); + EXPECT_FALSE(NamedRegion::Deserialize(data, &output)); + } + + { + // A struct from another component. + auto pair = test_export2::StringPair::New("hello", "world"); + auto data = test_export2::StringPair::Serialize(&pair); + + test_export2::StringPairPtr output; + ASSERT_TRUE(test_export2::StringPair::Deserialize(data, &output)); + EXPECT_TRUE(output.Equals(pair)); } } diff --git a/mojo/public/cpp/bindings/tests/sync_method_unittest.cc b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc index d0e5f10..084e080 100644 --- a/mojo/public/cpp/bindings/tests/sync_method_unittest.cc +++ b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc @@ -202,6 +202,60 @@ struct ImplTraits<TestSyncMaster> { }; template <typename Interface> +using ImplTypeFor = typename ImplTraits<Interface>::Type; + +// A wrapper for either an InterfacePtr or scoped_refptr<ThreadSafeInterfacePtr> +// that exposes the InterfacePtr interface. +template <typename Interface> +class PtrWrapper { + public: + explicit PtrWrapper(InterfacePtr<Interface> ptr) : ptr_(std::move(ptr)) {} + + explicit PtrWrapper( + scoped_refptr<ThreadSafeInterfacePtr<Interface>> thread_safe_ptr) + : thread_safe_ptr_(thread_safe_ptr) {} + + PtrWrapper(PtrWrapper&& other) = default; + + Interface* operator->() { + return thread_safe_ptr_ ? thread_safe_ptr_->get() : ptr_.get(); + } + + void set_connection_error_handler(const base::Closure& error_handler) { + DCHECK(!thread_safe_ptr_); + ptr_.set_connection_error_handler(error_handler); + } + + void reset() { + ptr_ = nullptr; + thread_safe_ptr_ = nullptr; + } + + private: + InterfacePtr<Interface> ptr_; + scoped_refptr<ThreadSafeInterfacePtr<Interface>> thread_safe_ptr_; + + DISALLOW_COPY_AND_ASSIGN(PtrWrapper); +}; + +// The type parameter for SyncMethodCommonTests for varying the Interface and +// whether to use InterfacePtr or ThreadSafeInterfacePtr. +template <typename InterfaceT, bool use_thread_safe_ptr> +struct TestParams { + using Interface = InterfaceT; + static const bool kIsThreadSafeInterfacePtrTest = use_thread_safe_ptr; + + static PtrWrapper<InterfaceT> Wrap(InterfacePtr<Interface> ptr) { + if (kIsThreadSafeInterfacePtrTest) { + return PtrWrapper<Interface>( + ThreadSafeInterfacePtr<Interface>::Create(std::move(ptr))); + } else { + return PtrWrapper<Interface>(std::move(ptr)); + } + } +}; + +template <typename Interface> class TestSyncServiceThread { public: TestSyncServiceThread() @@ -211,7 +265,7 @@ class TestSyncServiceThread { void SetUp(InterfaceRequest<Interface> request) { CHECK(thread_.task_runner()->BelongsToCurrentThread()); - impl_.reset(new typename ImplTraits<Interface>::Type(std::move(request))); + impl_.reset(new ImplTypeFor<Interface>(std::move(request))); impl_->set_ping_handler( [this](const typename Interface::PingCallback& callback) { { @@ -236,7 +290,7 @@ class TestSyncServiceThread { private: base::Thread thread_; - std::unique_ptr<typename ImplTraits<Interface>::Type> impl_; + std::unique_ptr<ImplTypeFor<Interface>> impl_; mutable base::Lock lock_; bool ping_called_; @@ -334,12 +388,20 @@ TestSync::AsyncEchoCallback BindAsyncEchoCallback(Func func) { // TestSync (without associated interfaces) and TestSyncMaster (with associated // interfaces) exercise MultiplexRouter with different configurations. -using InterfaceTypes = testing::Types<TestSync, TestSyncMaster>; +// Each test is run once with an InterfacePtr and once with a +// ThreadSafeInterfacePtr to ensure that they behave the same with respect to +// sync calls. +using InterfaceTypes = testing::Types<TestParams<TestSync, true>, + TestParams<TestSync, false>, + TestParams<TestSyncMaster, true>, + TestParams<TestSyncMaster, false>>; TYPED_TEST_CASE(SyncMethodCommonTest, InterfaceTypes); TYPED_TEST(SyncMethodCommonTest, CallSyncMethodAsynchronously) { - InterfacePtr<TypeParam> ptr; - typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr)); + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; + ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); base::RunLoop run_loop; ptr->Echo(123, base::Bind(&ExpectValueAndRunClosure, 123, @@ -348,13 +410,16 @@ TYPED_TEST(SyncMethodCommonTest, CallSyncMethodAsynchronously) { } TYPED_TEST(SyncMethodCommonTest, BasicSyncCalls) { - InterfacePtr<TypeParam> ptr; + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; + InterfaceRequest<Interface> request = MakeRequest(&interface_ptr); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); - TestSyncServiceThread<TypeParam> service_thread; + TestSyncServiceThread<Interface> service_thread; service_thread.thread()->task_runner()->PostTask( - FROM_HERE, base::Bind(&TestSyncServiceThread<TypeParam>::SetUp, - base::Unretained(&service_thread), - base::Passed(MakeRequest(&ptr)))); + FROM_HERE, + base::Bind(&TestSyncServiceThread<Interface>::SetUp, + base::Unretained(&service_thread), base::Passed(&request))); ASSERT_TRUE(ptr->Ping()); ASSERT_TRUE(service_thread.ping_called()); @@ -364,8 +429,9 @@ TYPED_TEST(SyncMethodCommonTest, BasicSyncCalls) { base::RunLoop run_loop; service_thread.thread()->task_runner()->PostTaskAndReply( - FROM_HERE, base::Bind(&TestSyncServiceThread<TypeParam>::TearDown, - base::Unretained(&service_thread)), + FROM_HERE, + base::Bind(&TestSyncServiceThread<Interface>::TearDown, + base::Unretained(&service_thread)), run_loop.QuitClosure()); run_loop.Run(); } @@ -374,9 +440,11 @@ TYPED_TEST(SyncMethodCommonTest, ReenteredBySyncMethodBinding) { // Test that an interface pointer waiting for a sync call response can be // reentered by a binding serving sync methods on the same thread. - InterfacePtr<TypeParam> ptr; + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; // The binding lives on the same thread as the interface pointer. - typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr)); + ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); int32_t output_value = -1; ASSERT_TRUE(ptr->Echo(42, &output_value)); EXPECT_EQ(42, output_value); @@ -386,8 +454,10 @@ TYPED_TEST(SyncMethodCommonTest, InterfacePtrDestroyedDuringSyncCall) { // Test that it won't result in crash or hang if an interface pointer is // destroyed while it is waiting for a sync call response. - InterfacePtr<TypeParam> ptr; - typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr)); + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; + ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); impl.set_ping_handler([&ptr](const TestSync::PingCallback& callback) { ptr.reset(); callback.Run(); @@ -400,8 +470,10 @@ TYPED_TEST(SyncMethodCommonTest, BindingDestroyedDuringSyncCall) { // closed (and therefore the message pipe handle is closed) while the // corresponding interface pointer is waiting for a sync call response. - InterfacePtr<TypeParam> ptr; - typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr)); + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; + ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); impl.set_ping_handler([&impl](const TestSync::PingCallback& callback) { impl.binding()->Close(); callback.Run(); @@ -413,8 +485,10 @@ TYPED_TEST(SyncMethodCommonTest, NestedSyncCallsWithInOrderResponses) { // Test that we can call a sync method on an interface ptr, while there is // already a sync call ongoing. The responses arrive in order. - InterfacePtr<TypeParam> ptr; - typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr)); + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; + ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); // The same variable is used to store the output of the two sync calls, in // order to test that responses are handled in the correct order. @@ -439,8 +513,10 @@ TYPED_TEST(SyncMethodCommonTest, NestedSyncCallsWithOutOfOrderResponses) { // Test that we can call a sync method on an interface ptr, while there is // already a sync call ongoing. The responses arrive out of order. - InterfacePtr<TypeParam> ptr; - typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr)); + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; + ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); // The same variable is used to store the output of the two sync calls, in // order to test that responses are handled in the correct order. @@ -465,8 +541,10 @@ TYPED_TEST(SyncMethodCommonTest, AsyncResponseQueuedDuringSyncCall) { // Test that while an interface pointer is waiting for the response to a sync // call, async responses are queued until the sync call completes. - InterfacePtr<TypeParam> ptr; - typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr)); + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; + ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); int32_t async_echo_request_value = -1; TestSync::AsyncEchoCallback async_echo_request_callback; @@ -521,8 +599,10 @@ TYPED_TEST(SyncMethodCommonTest, AsyncRequestQueuedDuringSyncCall) { // call, async requests for a binding running on the same thread are queued // until the sync call completes. - InterfacePtr<TypeParam> ptr; - typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr)); + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; + ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); bool async_echo_request_dispatched = false; impl.set_async_echo_handler([&async_echo_request_dispatched]( @@ -572,8 +652,14 @@ TYPED_TEST(SyncMethodCommonTest, // before the queued messages are processed, the connection error // notification is delayed until all the queued messages are processed. - InterfacePtr<TypeParam> ptr; - typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr)); + // ThreadSafeInterfacePtr doesn't guarantee that messages are delivered before + // error notifications, so skip it for this test. + if (TypeParam::kIsThreadSafeInterfacePtrTest) + return; + + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> ptr; + ImplTypeFor<Interface> impl(MakeRequest(&ptr)); int32_t async_echo_request_value = -1; TestSync::AsyncEchoCallback async_echo_request_callback; @@ -648,14 +734,15 @@ TYPED_TEST(SyncMethodCommonTest, InvalidMessageDuringSyncCall) { // the sync call to return false, and run the connection error handler // asynchronously. + using Interface = typename TypeParam::Interface; MessagePipe pipe; - InterfacePtr<TypeParam> ptr; - ptr.Bind(InterfacePtrInfo<TypeParam>(std::move(pipe.handle0), 0u)); + InterfacePtr<Interface> interface_ptr; + interface_ptr.Bind(InterfacePtrInfo<Interface>(std::move(pipe.handle0), 0u)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); MessagePipeHandle raw_binding_handle = pipe.handle1.get(); - typename ImplTraits<TypeParam>::Type impl( - MakeRequest<TypeParam>(std::move(pipe.handle1))); + ImplTypeFor<Interface> impl(MakeRequest<Interface>(std::move(pipe.handle1))); impl.set_echo_handler([&raw_binding_handle]( int32_t value, const TestSync::EchoCallback& callback) { @@ -670,17 +757,22 @@ TYPED_TEST(SyncMethodCommonTest, InvalidMessageDuringSyncCall) { bool connection_error_dispatched = false; base::RunLoop run_loop; - ptr.set_connection_error_handler( - base::Bind(&SetFlagAndRunClosure, &connection_error_dispatched, - run_loop.QuitClosure())); + // ThreadSafeInterfacePtr doesn't support setting connection error handlers. + if (!TypeParam::kIsThreadSafeInterfacePtrTest) { + ptr.set_connection_error_handler(base::Bind(&SetFlagAndRunClosure, + &connection_error_dispatched, + run_loop.QuitClosure())); + } int32_t result_value = -1; ASSERT_FALSE(ptr->Echo(456, &result_value)); EXPECT_EQ(-1, result_value); ASSERT_FALSE(connection_error_dispatched); - run_loop.Run(); - ASSERT_TRUE(connection_error_dispatched); + if (!TypeParam::kIsThreadSafeInterfacePtrTest) { + run_loop.Run(); + ASSERT_TRUE(connection_error_dispatched); + } } TEST_F(SyncMethodAssociatedTest, ReenteredBySyncMethodAssoBindingOfSameRouter) { diff --git a/mojo/public/cpp/bindings/tests/wtf_map_unittest.cc b/mojo/public/cpp/bindings/tests/wtf_map_unittest.cc index 5028087..dc40143 100644 --- a/mojo/public/cpp/bindings/tests/wtf_map_unittest.cc +++ b/mojo/public/cpp/bindings/tests/wtf_map_unittest.cc @@ -19,7 +19,7 @@ TEST(WTFMapTest, StructKey) { ASSERT_NE(map.end(), map.find(key)); ASSERT_EQ(123, map.find(key)->value); - map.remove(key); + map.erase(key); ASSERT_EQ(0u, map.size()); } @@ -32,7 +32,7 @@ TEST(WTFMapTest, TypemappedStructKey) { ASSERT_NE(map.end(), map.find(key)); ASSERT_EQ(123, map.find(key)->value); - map.remove(key); + map.erase(key); ASSERT_EQ(0u, map.size()); } diff --git a/mojo/public/cpp/bindings/thread_safe_interface_ptr.h b/mojo/public/cpp/bindings/thread_safe_interface_ptr.h index bab6d22..740687f 100644 --- a/mojo/public/cpp/bindings/thread_safe_interface_ptr.h +++ b/mojo/public/cpp/bindings/thread_safe_interface_ptr.h @@ -10,12 +10,26 @@ #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/memory/ref_counted.h" +#include "base/stl_util.h" +#include "base/synchronization/waitable_event.h" #include "base/task_runner.h" #include "base/threading/thread_task_runner_handle.h" #include "mojo/public/cpp/bindings/associated_group.h" #include "mojo/public/cpp/bindings/associated_interface_ptr.h" #include "mojo/public/cpp/bindings/interface_ptr.h" #include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/bindings/sync_call_restrictions.h" +#include "mojo/public/cpp/bindings/sync_event_watcher.h" + +// ThreadSafeInterfacePtr wraps a non-thread-safe InterfacePtr and proxies +// messages to it. Async calls are posted to the thread that the InteracePtr is +// bound to, and the responses are posted back. Sync calls are dispatched +// directly if the call is made on the thread that the wrapped InterfacePtr is +// bound to, or posted otherwise. It's important to be aware that sync calls +// block both the calling thread and the InterfacePtr thread. That means that +// you cannot make sync calls through a ThreadSafeInterfacePtr if the +// underlying InterfacePtr is bound to a thread that cannot block, like the IO +// thread. namespace mojo { @@ -46,9 +60,15 @@ class ThreadSafeForwarder : public MessageReceiverWithResponder { task_runner_(task_runner), forward_(forward), forward_with_responder_(forward_with_responder), - associated_group_(associated_group) {} - - ~ThreadSafeForwarder() override {} + associated_group_(associated_group), + sync_calls_(new InProgressSyncCalls()) {} + + ~ThreadSafeForwarder() override { + // If there are ongoing sync calls signal their completion now. + base::AutoLock l(sync_calls_->lock); + for (const auto& pending_response : sync_calls_->pending_responses) + pending_response->event.Signal(); + } ProxyType& proxy() { return proxy_; } @@ -73,35 +93,130 @@ class ThreadSafeForwarder : public MessageReceiverWithResponder { return true; } - bool AcceptWithResponder(Message* message, - MessageReceiver* response_receiver) override { + bool AcceptWithResponder( + Message* message, + std::unique_ptr<MessageReceiver> responder) override { if (!message->associated_endpoint_handles()->empty()) { // Please see comment for the DCHECK in the previous method. DCHECK(associated_group_.GetController()); message->SerializeAssociatedEndpointHandles( associated_group_.GetController()); } - auto responder = base::MakeUnique<ForwardToCallingThread>( - base::WrapUnique(response_receiver)); + + // Async messages are always posted (even if |task_runner_| runs tasks on + // this thread) to guarantee that two async calls can't be reordered. + if (!message->has_flag(Message::kFlagIsSync)) { + auto reply_forwarder = + base::MakeUnique<ForwardToCallingThread>(std::move(responder)); + task_runner_->PostTask( + FROM_HERE, base::Bind(forward_with_responder_, base::Passed(message), + base::Passed(&reply_forwarder))); + return true; + } + + SyncCallRestrictions::AssertSyncCallAllowed(); + + // If the InterfacePtr is bound to this thread, dispatch it directly. + if (task_runner_->RunsTasksOnCurrentThread()) { + forward_with_responder_.Run(std::move(*message), std::move(responder)); + return true; + } + + // If the InterfacePtr is bound on another thread, post the call. + // TODO(yzshen, watk): We block both this thread and the InterfacePtr + // thread. Ideally only this thread would block. + auto response = make_scoped_refptr(new SyncResponseInfo()); + auto response_signaler = base::MakeUnique<SyncResponseSignaler>(response); task_runner_->PostTask( FROM_HERE, base::Bind(forward_with_responder_, base::Passed(message), - base::Passed(&responder))); + base::Passed(&response_signaler))); + + // Save the pending SyncResponseInfo so that if the sync call deletes + // |this|, we can signal the completion of the call to return from + // SyncWatch(). + auto sync_calls = sync_calls_; + { + base::AutoLock l(sync_calls->lock); + sync_calls->pending_responses.push_back(response.get()); + } + + auto assign_true = [](bool* b) { *b = true; }; + bool event_signaled = false; + SyncEventWatcher watcher(&response->event, + base::Bind(assign_true, &event_signaled)); + watcher.SyncWatch(&event_signaled); + + { + base::AutoLock l(sync_calls->lock); + base::Erase(sync_calls->pending_responses, response.get()); + } + + if (response->received) + ignore_result(responder->Accept(&response->message)); + return true; } + // Data that we need to share between the threads involved in a sync call. + struct SyncResponseInfo + : public base::RefCountedThreadSafe<SyncResponseInfo> { + Message message; + bool received = false; + base::WaitableEvent event{base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED}; + + private: + friend class base::RefCountedThreadSafe<SyncResponseInfo>; + }; + + // A MessageReceiver that signals |response| when it either accepts the + // response message, or is destructed. + class SyncResponseSignaler : public MessageReceiver { + public: + explicit SyncResponseSignaler(scoped_refptr<SyncResponseInfo> response) + : response_(response) {} + + ~SyncResponseSignaler() override { + // If Accept() was not called we must still notify the waiter that the + // sync call is finished. + if (response_) + response_->event.Signal(); + } + + bool Accept(Message* message) { + response_->message = std::move(*message); + response_->received = true; + response_->event.Signal(); + response_ = nullptr; + return true; + } + + private: + scoped_refptr<SyncResponseInfo> response_; + }; + + // A record of the pending sync responses for canceling pending sync calls + // when the owning ThreadSafeForwarder is destructed. + struct InProgressSyncCalls + : public base::RefCountedThreadSafe<InProgressSyncCalls> { + // |lock| protects access to |pending_responses|. + base::Lock lock; + std::vector<SyncResponseInfo*> pending_responses; + }; + class ForwardToCallingThread : public MessageReceiver { public: explicit ForwardToCallingThread(std::unique_ptr<MessageReceiver> responder) : responder_(std::move(responder)), - caller_task_runner_(base::ThreadTaskRunnerHandle::Get()) { - } + caller_task_runner_(base::ThreadTaskRunnerHandle::Get()) {} private: bool Accept(Message* message) { // The current instance will be deleted when this method returns, so we // have to relinquish the responder's ownership so it does not get // deleted. - caller_task_runner_->PostTask(FROM_HERE, + caller_task_runner_->PostTask( + FROM_HERE, base::Bind(&ForwardToCallingThread::CallAcceptAndDeleteResponder, base::Passed(std::move(responder_)), base::Passed(std::move(*message)))); @@ -123,6 +238,7 @@ class ThreadSafeForwarder : public MessageReceiverWithResponder { const ForwardMessageCallback forward_; const ForwardMessageWithResponderCallback forward_with_responder_; AssociatedGroup associated_group_; + scoped_refptr<InProgressSyncCalls> sync_calls_; DISALLOW_COPY_AND_ASSIGN(ThreadSafeForwarder); }; diff --git a/mojo/public/cpp/system/BUILD.gn b/mojo/public/cpp/system/BUILD.gn index 0dc7af9..35087ef 100644 --- a/mojo/public/cpp/system/BUILD.gn +++ b/mojo/public/cpp/system/BUILD.gn @@ -29,11 +29,18 @@ component("system") { "data_pipe.h", "functions.h", "handle.h", + "handle_signals_state.h", "message.h", "message_pipe.h", "platform_handle.cc", "platform_handle.h", + "simple_watcher.cc", + "simple_watcher.h", "system_export.h", + "wait.cc", + "wait.h", + "wait_set.cc", + "wait_set.h", "watcher.cc", "watcher.h", ] diff --git a/mojo/public/cpp/system/README.md b/mojo/public/cpp/system/README.md new file mode 100644 index 0000000..782744f --- /dev/null +++ b/mojo/public/cpp/system/README.md @@ -0,0 +1,396 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo C++ System API +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview +The Mojo C++ System API provides a convenient set of helper classes and +functions for working with Mojo primitives. Unlike the low-level +[C API](/mojo/public/c/system) (upon which this is built) this library takes +advantage of C++ language features and common STL and `//base` types to provide +a slightly more idiomatic interface to the Mojo system layer, making it +generally easier to use. + +This document provides a brief guide to API usage with example code snippets. +For a detailed API references please consult the headers in +[//mojo/public/cpp/system](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/). + +Note that all API symbols referenced in this document are implicitly in the +top-level `mojo` namespace. + +## Scoped, Typed Handles + +All types of Mojo handles in the C API are simply opaque, integral `MojoHandle` +values. The C++ API has more strongly typed wrappers defined for different +handle types: `MessagePipeHandle`, `SharedBufferHandle`, +`DataPipeConsumerHandle`, `DataPipeProducerHandle`, and `WatcherHandle`. + +Each of these also has a corresponding, move-only, scoped type for safer usage: +`ScopedMessagePipeHandle`, `ScopedSharedBufferHandle`, and so on. When a scoped +handle type is destroyed, its handle is automatically closed via `MojoClose`. +When working with raw handles you should **always** prefer to use one of the +scoped types for ownership. + +Similar to `std::unique_ptr`, scoped handle types expose a `get()` method to get +at the underlying unscoped handle type as well as the `->` operator to +dereference the scoper and make calls directly on the underlying handle type. + +## Message Pipes + +There are two ways to create a new message pipe using the C++ API. You may +construct a `MessagePipe` object: + +``` cpp +mojo::MessagePipe pipe; + +// NOTE: Because pipes are bi-directional there is no implicit semantic +// difference between |handle0| or |handle1| here. They're just two ends of a +// pipe. The choice to treat one as a "client" and one as a "server" is entirely +// a the API user's decision. +mojo::ScopedMessagePipeHandle client = std::move(pipe.handle0); +mojo::ScopedMessagePipeHandle server = std::move(pipe.handle1); +``` + +or you may call `CreateMessagePipe`: + +``` cpp +mojo::ScopedMessagePipeHandle client; +mojo::ScopedMessagePipeHandle server; +mojo::CreateMessagePipe(nullptr, &client, &server); +``` + +There are also some helper functions for constructing message objects and +reading/writing them on pipes using the library's more strongly-typed C++ +handles: + +``` cpp +mojo::ScopedMessageHandle message; +mojo::AllocMessage(6, nullptr, 0, MOJO_ALLOC_MESSAGE_FLAG_NONE, &message); + +void *buffer; +mojo::GetMessageBuffer(message.get(), &buffer); + +const std::string kMessage = "hello"; +std::copy(kMessage.begin(), kMessage.end(), static_cast<char*>(buffer)); + +mojo::WriteMessageNew(client.get(), std::move(message), + MOJO_WRITE_MESSAGE_FLAG_NONE); + +// Some time later... + +mojo::ScopedMessageHandle received_message; +uint32_t num_bytes; +mojo::ReadMessageNew(server.get(), &received_message, &num_bytes, nullptr, + nullptr, MOJO_READ_MESSAGE_FLAG_NONE); +``` + +See [message_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/message_pipe.h) +for detailed C++ message pipe API documentation. + +## Data Pipes + +Similar to [Message Pipes](#Message-Pipes), the C++ library has some simple +helpers for more strongly-typed data pipe usage: + +``` cpp +mojo::DataPipe pipe; +mojo::ScopedDataPipeProducerHandle producer = std::move(pipe.producer); +mojo::ScopedDataPipeConsumerHandle consumer = std::move(pipe.consumer); + +// Or alternatively: +mojo::ScopedDataPipeProducerHandle producer; +mojo::ScopedDataPipeConsumerHandle consumer; +mojo::CreateDataPipe(null, &producer, &consumer); +``` + +// Reads from a data pipe. See |MojoReadData()| for complete documentation. +inline MojoResult ReadDataRaw(DataPipeConsumerHandle data_pipe_consumer, + void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) { + return MojoReadData(data_pipe_consumer.value(), elements, num_bytes, flags); +} + +// Begins a two-phase read +C++ helpers which correspond directly to the +[Data Pipe C API](/mojo/public/c/system#Data-Pipes) for immediate and two-phase +I/O are provided as well. For example: + +``` cpp +uint32_t num_bytes = 7; +mojo::WriteDataRaw(producer.get(), "hihihi", + &num_bytes, MOJO_WRITE_DATA_FLAG_NONE); + +// Some time later... + +char buffer[64]; +uint32_t num_bytes = 64; +mojo::ReadDataRaw(consumer.get(), buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE); +``` + +See [data_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/data_pipe.h) +for detailed C++ data pipe API documentation. + +## Shared Buffers + +A new shared buffers can be allocated like so: + +``` cpp +mojo::ScopedSharedBufferHandle buffer = + mojo::ScopedSharedBufferHandle::Create(4096); +``` + +This new handle can be cloned arbitrarily many times by using the underlying +handle's `Clone` method: + +``` cpp +mojo::ScopedSharedBufferHandle another_handle = buffer->Clone(); +mojo::ScopedSharedBufferHandle read_only_handle = + buffer->Clone(mojo::SharedBufferHandle::AccessMode::READ_ONLY); +``` + +And finally the library also provides a scoper for mapping the shared buffer's +memory: + +``` cpp +mojo::ScopedSharedBufferMapping mapping = buffer->Map(64); +static_cast<int*>(mapping.get()) = 42; + +mojo::ScopedSharedBufferMapping another_mapping = buffer->MapAtOffset(64, 4); +static_cast<int*>(mapping.get()) = 43; +``` + +When `mapping` and `another_mapping` are destroyed, they automatically unmap +their respective memory regions. + +See [buffer.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/buffer.h) +for detailed C++ shared buffer API documentation. + +## Native Platform Handles (File Descriptors, Windows Handles, *etc.*) + +The C++ library provides several helpers for wrapping system handle types. +These are specifically useful when working with a few `//base` types, namely +`base::PlatformFile` and `base::SharedMemoryHandle`. See +[platform_handle.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/platform_handle.h) +for detailed C++ platform handle API documentation. + +## Signals & Watchers + +For an introduction to the concepts of handle signals and watchers, check out +the C API's documentation on [Signals & Watchers](/mojo/public/c/system#Signals-Watchers). + +### Querying Signals + +Any C++ handle type's last known signaling state can be queried by calling the +`QuerySignalsState` method on the handle: + +``` cpp +mojo::MessagePipe message_pipe; +mojo::DataPipe data_pipe; +mojo::HandleSignalsState a = message_pipe.handle0->QuerySignalsState(); +mojo::HandleSignalsState b = data_pipe.consumer->QuerySignalsState(); +``` + +The `HandleSignalsState` is a thin wrapper interface around the C API's +`MojoHandleSignalsState` structure with convenient accessors for testing +the signal bitmasks. Whereas when using the C API you might write: + +``` c +struct MojoHandleSignalsState state; +MojoQueryHandleSignalsState(handle0, &state); +if (state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE) { + // ... +} +``` + +the C++ API equivalent would be: + +``` cpp +if (message_pipe.handle0->QuerySignalsState().readable()) { + // ... +} +``` + +### Watching Handles + +The [`mojo::SimpleWatcher`](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/simple_watcher.h) +class serves as a convenient helper for using the [low-level watcher API](/mojo/public/c/system#Signals-Watchers) +to watch a handle for signaling state changes. A `SimpleWatcher` is bound to a +single thread and always dispatches its notifications on a +`base::SingleThreadTaskRunner`. + +`SimpleWatcher` has two possible modes of operation, selected at construction +time by the `mojo::SimpleWatcher::ArmingPolicy` enum: + +* `MANUAL` mode requires the user to manually call `Arm` and/or `ArmOrNotify` + before any notifications will fire regarding the state of the watched handle. + Every time the notification callback is run, the `SimpleWatcher` must be + rearmed again before the next one can fire. See + [Arming a Watcher](/mojo/public/c/system#Arming-a-Watcher) and the + documentation in `SimpleWatcher`'s header. + +* `AUTOMATIC` mode ensures that the `SimpleWatcher` always either is armed or + has a pending notification task queued for execution. + +`AUTOMATIC` mode is more convenient but can result in redundant notification +tasks, especially if the provided callback does not make a strong effort to +return the watched handle to an uninteresting signaling state (by *e.g.*, +reading all its available messages when notified of readability.) + +Example usage: + +``` cpp +class PipeReader { + public: + PipeReader(mojo::ScopedMessagePipeHandle pipe) + : pipe_(std::move(pipe)), + watcher_(mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC) { + // NOTE: base::Unretained is safe because the callback can never be run + // after SimpleWatcher destruction. + watcher_.Watch(pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind(&PipeReader::OnReadable, base::Unretained(this))); + } + + ~PipeReader() {} + + private: + void OnReadable(MojoResult result) { + while (result == MOJO_RESULT_OK) { + mojo::ScopedMessageHandle message; + uint32_t num_bytes; + result = mojo::ReadMessageNew(pipe_.get(), &message, &num_bytes, nullptr, + nullptr, MOJO_READ_MESSAGE_FLAG_NONE); + DCHECK_EQ(result, MOJO_RESULT_OK); + messages_.emplace_back(std::move(message)); + } + } + + mojo::ScopedMessagePipeHandle pipe_; + mojo::SimpleWatcher watcher_; + std::vector<mojo::ScopedMessageHandle> messages_; +}; + +mojo::MessagePipe pipe; +PipeReader reader(std::move(pipe.handle0)); + +// Written messages will asynchronously end up in |reader.messages_|. +WriteABunchOfStuff(pipe.handle1.get()); +``` + +## Synchronous Waiting + +The C++ System API defines some utilities to block a calling thread while +waiting for one or more handles to change signaling state in an interesting way. +These threads combine usage of the [low-level Watcher API](/mojo/public/c/system#Signals-Watchers) +with common synchronization primitives (namely `base::WaitableEvent`.) + +While these API features should be used sparingly, they are sometimes necessary. + +See the documentation in +[wait.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait.h) +and [wait_set.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait_set.h) +for a more detailed API reference. + +### Waiting On a Single Handle + +The `mojo::Wait` function simply blocks the calling thread until a given signal +mask is either partially satisfied or fully unsatisfiable on a given handle. + +``` cpp +mojo::MessagePipe pipe; +mojo::WriteMessageRaw(pipe.handle0.get(), "hey", 3, nullptr, nullptr, + MOJO_WRITE_MESSAGE_FLAG_NONE); +MojoResult result = mojo::Wait(pipe.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); +DCHECK_EQ(result, MOJO_RESULT_OK); + +// Guaranteed to succeed because we know |handle1| is readable now. +mojo::ScopedMessageHandle message; +uint32_t num_bytes; +mojo::ReadMessageNew(pipe.handle1.get(), &num_bytes, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE); +``` + +`mojo::Wait` is most typically useful in limited testing scenarios. + +### Waiting On Multiple Handles + +`mojo::WaitMany` provides a simple API to wait on multiple handles +simultaneously, returning when any handle's given signal mask is either +partially satisfied or fully unsatisfiable. + +``` cpp +mojo::MessagePipe a, b; +GoDoSomethingWithPipes(std:move(a.handle1), std::move(b.handle1)); + +mojo::MessagePipeHandle handles[2] = {a.handle0.get(), b.handle0.get()}; +MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE}; +size_t ready_index; +MojoResult result = mojo::WaitMany(handles, signals, 2, &ready_index); +if (ready_index == 0) { + // a.handle0 was ready. +} else { + // b.handle0 was ready. +} +``` + +Similar to `mojo::Wait`, `mojo::WaitMany` is primarily useful in testing. When +waiting on multiple handles in production code, you should almost always instead +use a more efficient and more flexible `mojo::WaitSet` as described in the next +section. + +### Waiting On Handles and Events Simultaneously + +Typically when waiting on one or more handles to signal, the set of handles and +conditions being waited upon do not change much between consecutive blocking +waits. It's also often useful to be able to interrupt the blocking operation +as efficiently as possible. + +[`mojo::WaitSet`](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait_set.h) +is designed with these conditions in mind. A `WaitSet` maintains a persistent +set of (not-owned) Mojo handles and `base::WaitableEvent`s, which may be +explicitly added to or removed from the set at any time. + +The `WaitSet` may be waited upon repeatedly, each time blocking the calling +thread until either one of the handles attains an interesting signaling state or +one of the events is signaled. For example let's suppose we want to wait up to 5 +seconds for either one of two handles to become readable: + +``` cpp +base::WaitableEvent timeout_event( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); +mojo::MessagePipe a, b; + +GoDoStuffWithPipes(std::move(a.handle1), std::move(b.handle1)); + +mojo::WaitSet wait_set; +wait_set.AddHandle(a.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); +wait_set.AddHandle(b.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); +wait_set.AddEvent(&timeout_event); + +// Ensure the Wait() lasts no more than 5 seconds. +bg_thread->task_runner()->PostDelayedTask( + FROM_HERE, + base::Bind([](base::WaitableEvent* e) { e->Signal(); }, &timeout_event); + base::TimeDelta::FromSeconds(5)); + +base::WaitableEvent* ready_event = nullptr; +size_t num_ready_handles = 1; +mojo::Handle ready_handle; +MojoResult ready_result; +wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result); + +// The apex of thread-safety. +bg_thread->Stop(); + +if (ready_event) { + // The event signaled... +} + +if (num_ready_handles > 0) { + // At least one of the handles signaled... + // NOTE: This and the above condition are not mutually exclusive. If handle + // signaling races with timeout, both things might be true. +} +``` diff --git a/mojo/public/cpp/system/functions.h b/mojo/public/cpp/system/functions.h index 9cfe316..31edf57 100644 --- a/mojo/public/cpp/system/functions.h +++ b/mojo/public/cpp/system/functions.h @@ -21,12 +21,6 @@ inline MojoTimeTicks GetTimeTicksNow() { return MojoGetTimeTicksNow(); } -// The C++ wrappers for |MojoWait()| and |MojoWaitMany()| are defined in -// "handle.h". -// TODO(ggowan): Consider making the C and C++ APIs more consistent in the -// organization of the functions into different header files (since in the C -// API, those functions are defined in "functions.h"). - } // namespace mojo #endif // MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_ diff --git a/mojo/public/cpp/system/handle.h b/mojo/public/cpp/system/handle.h index 5b2eb7b..781944e 100644 --- a/mojo/public/cpp/system/handle.h +++ b/mojo/public/cpp/system/handle.h @@ -13,6 +13,7 @@ #include "base/macros.h" #include "mojo/public/c/system/functions.h" #include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/handle_signals_state.h" namespace mojo { @@ -170,6 +171,14 @@ class Handle { DCHECK_EQ(MOJO_RESULT_OK, result); } + HandleSignalsState QuerySignalsState() const { + HandleSignalsState signals_state; + MojoResult result = MojoQueryHandleSignalsState( + value_, static_cast<MojoHandleSignalsState*>(&signals_state)); + DCHECK_EQ(MOJO_RESULT_OK, result); + return signals_state; + } + private: MojoHandle value_; @@ -184,103 +193,6 @@ typedef ScopedHandleBase<Handle> ScopedHandle; static_assert(sizeof(ScopedHandle) == sizeof(Handle), "Bad size for C++ ScopedHandle"); -inline MojoResult Wait(Handle handle, - MojoHandleSignals signals, - MojoDeadline deadline, - MojoHandleSignalsState* signals_state) { - return MojoWait(handle.value(), signals, deadline, signals_state); -} - -const uint32_t kInvalidWaitManyIndexValue = static_cast<uint32_t>(-1); - -// Simplify the interpretation of the output from |MojoWaitMany()|. -class WaitManyResult { - public: - explicit WaitManyResult(MojoResult mojo_wait_many_result) - : result(mojo_wait_many_result), index(kInvalidWaitManyIndexValue) {} - - WaitManyResult(MojoResult mojo_wait_many_result, uint32_t result_index) - : result(mojo_wait_many_result), index(result_index) {} - - // A valid handle index is always returned if |WaitMany()| succeeds, but may - // or may not be returned if |WaitMany()| returns an error. Use this helper - // function to check if |index| is a valid index into the handle array. - bool IsIndexValid() const { return index != kInvalidWaitManyIndexValue; } - - // The |signals_states| array is always returned by |WaitMany()| on success, - // but may or may not be returned if |WaitMany()| returns an error. Use this - // helper function to check if |signals_states| holds valid data. - bool AreSignalsStatesValid() const { - return result != MOJO_RESULT_INVALID_ARGUMENT && - result != MOJO_RESULT_RESOURCE_EXHAUSTED; - } - - MojoResult result; - uint32_t index; -}; - -// |HandleVectorType| and |FlagsVectorType| should be similar enough to -// |std::vector<Handle>| and |std::vector<MojoHandleSignals>|, respectively: -// - They should have a (const) |size()| method that returns an unsigned type. -// - They must provide contiguous storage, with access via (const) reference to -// that storage provided by a (const) |operator[]()| (by reference). -template <class HandleVectorType, - class FlagsVectorType, - class SignalsStateVectorType> -inline WaitManyResult WaitMany(const HandleVectorType& handles, - const FlagsVectorType& signals, - MojoDeadline deadline, - SignalsStateVectorType* signals_states) { - if (signals.size() != handles.size() || - (signals_states && signals_states->size() != signals.size())) - return WaitManyResult(MOJO_RESULT_INVALID_ARGUMENT); - if (handles.size() >= kInvalidWaitManyIndexValue) - return WaitManyResult(MOJO_RESULT_RESOURCE_EXHAUSTED); - - if (handles.size() == 0) { - return WaitManyResult( - MojoWaitMany(nullptr, nullptr, 0, deadline, nullptr, nullptr)); - } - - uint32_t result_index = kInvalidWaitManyIndexValue; - const Handle& first_handle = handles[0]; - const MojoHandleSignals& first_signals = signals[0]; - MojoHandleSignalsState* first_state = - signals_states ? &(*signals_states)[0] : nullptr; - MojoResult result = - MojoWaitMany(reinterpret_cast<const MojoHandle*>(&first_handle), - &first_signals, static_cast<uint32_t>(handles.size()), - deadline, &result_index, first_state); - return WaitManyResult(result, result_index); -} - -// C++ 4.10, regarding pointer conversion, says that an integral null pointer -// constant can be converted to |std::nullptr_t| (which is a typedef for -// |decltype(nullptr)|). The opposite direction is not allowed. -template <class HandleVectorType, class FlagsVectorType> -inline WaitManyResult WaitMany(const HandleVectorType& handles, - const FlagsVectorType& signals, - MojoDeadline deadline, - decltype(nullptr) signals_states) { - if (signals.size() != handles.size()) - return WaitManyResult(MOJO_RESULT_INVALID_ARGUMENT); - if (handles.size() >= kInvalidWaitManyIndexValue) - return WaitManyResult(MOJO_RESULT_RESOURCE_EXHAUSTED); - - if (handles.size() == 0) { - return WaitManyResult( - MojoWaitMany(nullptr, nullptr, 0, deadline, nullptr, nullptr)); - } - - uint32_t result_index = kInvalidWaitManyIndexValue; - const Handle& first_handle = handles[0]; - const MojoHandleSignals& first_signals = signals[0]; - MojoResult result = MojoWaitMany( - reinterpret_cast<const MojoHandle*>(&first_handle), &first_signals, - static_cast<uint32_t>(handles.size()), deadline, &result_index, nullptr); - return WaitManyResult(result, result_index); -} - // |Close()| takes ownership of the handle, since it'll invalidate it. // Note: There's nothing to do, since the argument will be destroyed when it // goes out of scope. diff --git a/mojo/public/cpp/system/handle_signals_state.h b/mojo/public/cpp/system/handle_signals_state.h new file mode 100644 index 0000000..9e2430f --- /dev/null +++ b/mojo/public/cpp/system/handle_signals_state.h @@ -0,0 +1,83 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_HANDLE_SIGNALS_STATE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_HANDLE_SIGNALS_STATE_H_ + +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/system_export.h" + +namespace mojo { + +// A convenience wrapper around the MojoHandleSignalsState struct. +struct MOJO_CPP_SYSTEM_EXPORT HandleSignalsState final + : public MojoHandleSignalsState { + HandleSignalsState() { + satisfied_signals = MOJO_HANDLE_SIGNAL_NONE; + satisfiable_signals = MOJO_HANDLE_SIGNAL_NONE; + } + + HandleSignalsState(MojoHandleSignals satisfied, + MojoHandleSignals satisfiable) { + satisfied_signals = satisfied; + satisfiable_signals = satisfiable; + } + + bool operator==(const HandleSignalsState& other) const { + return satisfied_signals == other.satisfied_signals && + satisfiable_signals == other.satisfiable_signals; + } + + // TODO(rockot): Remove this in favor of operator==. + bool equals(const HandleSignalsState& other) const { + return satisfied_signals == other.satisfied_signals && + satisfiable_signals == other.satisfiable_signals; + } + + bool satisfies(MojoHandleSignals signals) const { + return !!(satisfied_signals & signals); + } + + bool can_satisfy(MojoHandleSignals signals) const { + return !!(satisfiable_signals & signals); + } + + // The handle is currently readable. May apply to a message pipe handle or + // data pipe consumer handle. + bool readable() const { return satisfies(MOJO_HANDLE_SIGNAL_READABLE); } + + // The handle is currently writable. May apply to a message pipe handle or + // data pipe producer handle. + bool writable() const { return satisfies(MOJO_HANDLE_SIGNAL_WRITABLE); } + + // The handle's peer is closed. May apply to any message pipe or data pipe + // handle. + bool peer_closed() const { return satisfies(MOJO_HANDLE_SIGNAL_PEER_CLOSED); } + + // The handle will never be |readable()| again. + bool never_readable() const { + return !can_satisfy(MOJO_HANDLE_SIGNAL_READABLE); + } + + // The handle will never be |writable()| again. + bool never_writable() const { + return !can_satisfy(MOJO_HANDLE_SIGNAL_WRITABLE); + } + + // The handle can never indicate |peer_closed()|. Never true for message pipe + // or data pipe handles (they can always signal peer closure), but always true + // for other types of handles (they have no peer.) + bool never_peer_closed() const { + return !can_satisfy(MOJO_HANDLE_SIGNAL_PEER_CLOSED); + } + + // (Copy and assignment allowed.) +}; + +static_assert(sizeof(HandleSignalsState) == sizeof(MojoHandleSignalsState), + "HandleSignalsState should add no overhead"); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_HANDLE_SIGNALS_STATE_H_ diff --git a/mojo/public/cpp/system/simple_watcher.cc b/mojo/public/cpp/system/simple_watcher.cc new file mode 100644 index 0000000..ae96faa --- /dev/null +++ b/mojo/public/cpp/system/simple_watcher.cc @@ -0,0 +1,279 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/system/simple_watcher.h" + +#include "base/bind.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/lock.h" +#include "base/trace_event/heap_profiler.h" +#include "mojo/public/c/system/watcher.h" + +namespace mojo { + +// Thread-safe Context object used to dispatch watch notifications from a +// arbitrary threads. +class SimpleWatcher::Context : public base::RefCountedThreadSafe<Context> { + public: + // Creates a |Context| instance for a new watch on |watcher|, to watch + // |handle| for |signals|. + static scoped_refptr<Context> Create( + base::WeakPtr<SimpleWatcher> watcher, + scoped_refptr<base::SingleThreadTaskRunner> task_runner, + WatcherHandle watcher_handle, + Handle handle, + MojoHandleSignals signals, + int watch_id, + MojoResult* watch_result) { + scoped_refptr<Context> context = + new Context(watcher, task_runner, watch_id); + + // If MojoWatch succeeds, it assumes ownership of a reference to |context|. + // In that case, this reference is balanced in CallNotify() when |result| is + // |MOJO_RESULT_CANCELLED|. + context->AddRef(); + + *watch_result = MojoWatch(watcher_handle.value(), handle.value(), signals, + context->value()); + if (*watch_result != MOJO_RESULT_OK) { + // Balanced by the AddRef() above since watching failed. + context->Release(); + return nullptr; + } + + return context; + } + + static void CallNotify(uintptr_t context_value, + MojoResult result, + MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags) { + auto* context = reinterpret_cast<Context*>(context_value); + context->Notify(result, signals_state, flags); + + // That was the last notification for the context. We can release the ref + // owned by the watch, which may in turn delete the Context. + if (result == MOJO_RESULT_CANCELLED) + context->Release(); + } + + uintptr_t value() const { return reinterpret_cast<uintptr_t>(this); } + + void DisableCancellationNotifications() { + base::AutoLock lock(lock_); + enable_cancellation_notifications_ = false; + } + + private: + friend class base::RefCountedThreadSafe<Context>; + + Context(base::WeakPtr<SimpleWatcher> weak_watcher, + scoped_refptr<base::SingleThreadTaskRunner> task_runner, + int watch_id) + : weak_watcher_(weak_watcher), + task_runner_(task_runner), + watch_id_(watch_id) {} + ~Context() {} + + void Notify(MojoResult result, + MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags) { + if (result == MOJO_RESULT_CANCELLED) { + // The SimpleWatcher may have explicitly cancelled this watch, so we don't + // bother dispatching the notification - it would be ignored anyway. + // + // TODO(rockot): This shouldn't really be necessary, but there are already + // instances today where bindings object may be bound and subsequently + // closed due to pipe error, all before the thread's TaskRunner has been + // properly initialized. + base::AutoLock lock(lock_); + if (!enable_cancellation_notifications_) + return; + } + + if ((flags & MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM) && + task_runner_->RunsTasksOnCurrentThread() && weak_watcher_ && + weak_watcher_->is_default_task_runner_) { + // System notifications will trigger from the task runner passed to + // mojo::edk::InitIPCSupport(). In Chrome this happens to always be the + // default task runner for the IO thread. + weak_watcher_->OnHandleReady(watch_id_, result); + } else { + task_runner_->PostTask( + FROM_HERE, base::Bind(&SimpleWatcher::OnHandleReady, weak_watcher_, + watch_id_, result)); + } + } + + const base::WeakPtr<SimpleWatcher> weak_watcher_; + const scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + const int watch_id_; + + base::Lock lock_; + bool enable_cancellation_notifications_ = true; + + DISALLOW_COPY_AND_ASSIGN(Context); +}; + +SimpleWatcher::SimpleWatcher(const tracked_objects::Location& from_here, + ArmingPolicy arming_policy, + scoped_refptr<base::SingleThreadTaskRunner> runner) + : arming_policy_(arming_policy), + task_runner_(std::move(runner)), + is_default_task_runner_(task_runner_ == + base::ThreadTaskRunnerHandle::Get()), + heap_profiler_tag_(from_here.file_name()), + weak_factory_(this) { + MojoResult rv = CreateWatcher(&Context::CallNotify, &watcher_handle_); + DCHECK_EQ(MOJO_RESULT_OK, rv); + DCHECK(task_runner_->BelongsToCurrentThread()); +} + +SimpleWatcher::~SimpleWatcher() { + if (IsWatching()) + Cancel(); +} + +bool SimpleWatcher::IsWatching() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return context_ != nullptr; +} + +MojoResult SimpleWatcher::Watch(Handle handle, + MojoHandleSignals signals, + const ReadyCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!IsWatching()); + DCHECK(!callback.is_null()); + + callback_ = callback; + handle_ = handle; + watch_id_ += 1; + + MojoResult watch_result = MOJO_RESULT_UNKNOWN; + context_ = Context::Create(weak_factory_.GetWeakPtr(), task_runner_, + watcher_handle_.get(), handle_, signals, watch_id_, + &watch_result); + if (!context_) { + handle_.set_value(kInvalidHandleValue); + callback_.Reset(); + DCHECK_EQ(MOJO_RESULT_INVALID_ARGUMENT, watch_result); + return watch_result; + } + + if (arming_policy_ == ArmingPolicy::AUTOMATIC) + ArmOrNotify(); + + return MOJO_RESULT_OK; +} + +void SimpleWatcher::Cancel() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // The watcher may have already been cancelled if the handle was closed. + if (!context_) + return; + + // Prevent the cancellation notification from being dispatched to + // OnHandleReady() when cancellation is explicit. See the note in the + // implementation of DisableCancellationNotifications() above. + context_->DisableCancellationNotifications(); + + handle_.set_value(kInvalidHandleValue); + callback_.Reset(); + + // Ensure |context_| is unset by the time we call MojoCancelWatch, as may + // re-enter the notification callback and we want to ensure |context_| is + // unset by then. + scoped_refptr<Context> context; + std::swap(context, context_); + MojoResult rv = + MojoCancelWatch(watcher_handle_.get().value(), context->value()); + + // It's possible this cancellation could race with a handle closure + // notification, in which case the watch may have already been implicitly + // cancelled. + DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_NOT_FOUND); +} + +MojoResult SimpleWatcher::Arm(MojoResult* ready_result) { + DCHECK(thread_checker_.CalledOnValidThread()); + uint32_t num_ready_contexts = 1; + uintptr_t ready_context; + MojoResult local_ready_result; + MojoHandleSignalsState ready_state; + MojoResult rv = + MojoArmWatcher(watcher_handle_.get().value(), &num_ready_contexts, + &ready_context, &local_ready_result, &ready_state); + if (rv == MOJO_RESULT_FAILED_PRECONDITION) { + DCHECK(context_); + DCHECK_EQ(1u, num_ready_contexts); + DCHECK_EQ(context_->value(), ready_context); + if (ready_result) + *ready_result = local_ready_result; + } + + return rv; +} + +void SimpleWatcher::ArmOrNotify() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Already cancelled, nothing to do. + if (!IsWatching()) + return; + + MojoResult ready_result; + MojoResult rv = Arm(&ready_result); + if (rv == MOJO_RESULT_OK) + return; + + DCHECK_EQ(MOJO_RESULT_FAILED_PRECONDITION, rv); + task_runner_->PostTask(FROM_HERE, base::Bind(&SimpleWatcher::OnHandleReady, + weak_factory_.GetWeakPtr(), + watch_id_, ready_result)); +} + +void SimpleWatcher::OnHandleReady(int watch_id, MojoResult result) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // This notification may be for a previously watched context, in which case + // we just ignore it. + if (watch_id != watch_id_) + return; + + ReadyCallback callback = callback_; + if (result == MOJO_RESULT_CANCELLED) { + // Implicit cancellation due to someone closing the watched handle. We clear + // the SimppleWatcher's state before dispatching this. + context_ = nullptr; + handle_.set_value(kInvalidHandleValue); + callback_.Reset(); + } + + // NOTE: It's legal for |callback| to delete |this|. + if (!callback.is_null()) { + TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION event(heap_profiler_tag_); + + base::WeakPtr<SimpleWatcher> weak_self = weak_factory_.GetWeakPtr(); + callback.Run(result); + if (!weak_self) + return; + + if (unsatisfiable_) + return; + + // Prevent |MOJO_RESULT_FAILED_PRECONDITION| task spam by only notifying + // at most once in AUTOMATIC arming mode. + if (result == MOJO_RESULT_FAILED_PRECONDITION) + unsatisfiable_ = true; + + if (arming_policy_ == ArmingPolicy::AUTOMATIC && IsWatching()) + ArmOrNotify(); + } +} + +} // namespace mojo diff --git a/mojo/public/cpp/system/simple_watcher.h b/mojo/public/cpp/system/simple_watcher.h new file mode 100644 index 0000000..9001884 --- /dev/null +++ b/mojo/public/cpp/system/simple_watcher.h @@ -0,0 +1,215 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_SIMPLE_WATCHER_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_SIMPLE_WATCHER_H_ + +#include "base/callback.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/system_export.h" +#include "mojo/public/cpp/system/watcher.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace mojo { + +// This provides a convenient thread-bound watcher implementation to safely +// watch a single handle, dispatching state change notifications to an arbitrary +// SingleThreadTaskRunner running on the same thread as the SimpleWatcher. +// +// SimpleWatcher exposes the concept of "arming" from the low-level Watcher API. +// In general, a SimpleWatcher must be "armed" in order to dispatch a single +// notification, and must then be rearmed before it will dispatch another. For +// more details, see the documentation for ArmingPolicy and the Arm() and +// ArmOrNotify() methods below. +class MOJO_CPP_SYSTEM_EXPORT SimpleWatcher { + public: + // A callback to be called any time a watched handle changes state in some + // interesting way. The |result| argument indicates one of the following + // conditions depending on its value: + // + // |MOJO_RESULT_OK|: One or more of the signals being watched is satisfied. + // + // |MOJO_RESULT_FAILED_PRECONDITION|: None of the signals being watched can + // ever be satisfied again. + // + // |MOJO_RESULT_CANCELLED|: The watched handle has been closed. No further + // notifications will be fired, as this equivalent to an implicit + // CancelWatch(). + // + // Note that unlike the first two conditions, this callback may be invoked + // with |MOJO_RESULT_CANCELLED| even while the SimpleWatcher is disarmed. + using ReadyCallback = base::Callback<void(MojoResult result)>; + + // Selects how this SimpleWatcher is to be armed. + enum class ArmingPolicy { + // The SimpleWatcher is armed automatically on Watch() and rearmed again + // after every invocation of the ReadyCallback. There is no need to manually + // call Arm() on a SimpleWatcher using this policy. This mode is equivalent + // to calling ArmOrNotify() once after Watch() and once again after every + // dispatched notification in MANUAL mode. + // + // This provides a reasonable approximation of edge-triggered behavior, + // mitigating (but not completely eliminating) the potential for redundant + // notifications. + // + // NOTE: It is important when using AUTOMATIC policy that your ReadyCallback + // always attempt to change the state of the handle (e.g. read available + // messages on a message pipe.) Otherwise this will result in a potentially + // large number of avoidable redundant tasks. + // + // For perfect edge-triggered behavior, use MANUAL policy and manually Arm() + // the SimpleWatcher as soon as it becomes possible to do so again. + AUTOMATIC, + + // The SimpleWatcher is never armed automatically. Arm() or ArmOrNotify() + // must be called manually before any non-cancellation notification can be + // dispatched to the ReadyCallback. See the documentation for Arm() and + // ArmNotify() methods below for more details. + MANUAL, + }; + + SimpleWatcher(const tracked_objects::Location& from_here, + ArmingPolicy arming_policy, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()); + ~SimpleWatcher(); + + // Indicates if the SimpleWatcher is currently watching a handle. + bool IsWatching() const; + + // Starts watching |handle|. A SimpleWatcher may only watch one handle at a + // time, but it is safe to call this more than once as long as the previous + // watch has been cancelled (i.e. |IsWatching()| returns |false|.) + // + // If |handle| is not a valid watchable (message or data pipe) handle or + // |signals| is not a valid set of signals to watch, this returns + // |MOJO_RESULT_INVALID_ARGUMENT|. + // + // Otherwise |MOJO_RESULT_OK| is returned and the handle will be watched until + // either |handle| is closed, the SimpleWatcher is destroyed, or Cancel() is + // explicitly called. + // + // Once the watch is started, |callback| may be called at any time on the + // current thread until |Cancel()| is called or the handle is closed. Note + // that |callback| can be called for results other than + // |MOJO_RESULT_CANCELLED| only if the SimpleWatcher is currently armed. Use + // ArmingPolicy to configure how a SimpleWatcher is armed. + // + // |MOJO_RESULT_CANCELLED| may be dispatched even while the SimpleWatcher + // is disarmed, and no further notifications will be dispatched after that. + // + // Destroying the SimpleWatcher implicitly calls |Cancel()|. + MojoResult Watch(Handle handle, + MojoHandleSignals signals, + const ReadyCallback& callback); + + // Cancels the current watch. Once this returns, the ReadyCallback previously + // passed to |Watch()| will never be called again for this SimpleWatcher. + // + // Note that when cancelled with an explicit call to |Cancel()| the + // ReadyCallback will not be invoked with a |MOJO_RESULT_CANCELLED| result. + void Cancel(); + + // Manually arms the SimpleWatcher. + // + // Arming the SimpleWatcher allows it to fire a single notification regarding + // some future relevant change in the watched handle's state. It's only valid + // to call Arm() while a handle is being watched (see Watch() above.) + // + // SimpleWatcher is always disarmed immediately before invoking its + // ReadyCallback and must be rearmed again before another notification can + // fire. + // + // If the watched handle already meets the watched signaling conditions - + // i.e., if it would have notified immediately once armed - the SimpleWatcher + // is NOT armed, and this call fails with a return value of + // |MOJO_RESULT_FAILED_PRECONDITION|. In that case, what would have been the + // result code for that immediate notification is instead placed in + // |*ready_result| if |ready_result| is non-null. + // + // If the watcher is successfully armed, this returns |MOJO_RESULT_OK| and + // |ready_result| is ignored. + MojoResult Arm(MojoResult* ready_result = nullptr); + + // Manually arms the SimpleWatcher OR posts a task to invoke the ReadyCallback + // with the ready result of the failed arming attempt. + // + // This is meant as a convenient helper for a common usage of Arm(), and it + // ensures that the ReadyCallback will be invoked asynchronously again as soon + // as the watch's conditions are satisfied, assuming the SimpleWatcher isn't + // cancelled first. + // + // Unlike Arm() above, this can never fail. + void ArmOrNotify(); + + Handle handle() const { return handle_; } + ReadyCallback ready_callback() const { return callback_; } + + // Sets the tag used by the heap profiler. + // |tag| must be a const string literal. + void set_heap_profiler_tag(const char* heap_profiler_tag) { + heap_profiler_tag_ = heap_profiler_tag; + } + + private: + class Context; + + void OnHandleReady(int watch_id, MojoResult result); + + base::ThreadChecker thread_checker_; + + // The policy used to determine how this SimpleWatcher is armed. + const ArmingPolicy arming_policy_; + + // The TaskRunner of this SimpleWatcher's owning thread. This field is safe to + // access from any thread. + const scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + + // Whether |task_runner_| is the same as base::ThreadTaskRunnerHandle::Get() + // for the thread. + const bool is_default_task_runner_; + + ScopedWatcherHandle watcher_handle_; + + // A thread-safe context object corresponding to the currently active watch, + // if any. + scoped_refptr<Context> context_; + + // Fields below must only be accessed on the SimpleWatcher's owning thread. + + // The handle currently under watch. Not owned. + Handle handle_; + + // A simple counter to disambiguate notifications from multiple watch contexts + // in the event that this SimpleWatcher cancels and watches multiple times. + int watch_id_ = 0; + + // The callback to call when the handle is signaled. + ReadyCallback callback_; + + // Tracks if the SimpleWatcher has already notified of unsatisfiability. This + // is used to prevent redundant notifications in AUTOMATIC mode. + bool unsatisfiable_ = false; + + // Tag used to ID memory allocations that originated from notifications in + // this watcher. + const char* heap_profiler_tag_ = nullptr; + + base::WeakPtrFactory<SimpleWatcher> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(SimpleWatcher); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_SIMPLE_WATCHER_H_ diff --git a/mojo/public/cpp/system/tests/BUILD.gn b/mojo/public/cpp/system/tests/BUILD.gn index 8f98b92..705d009 100644 --- a/mojo/public/cpp/system/tests/BUILD.gn +++ b/mojo/public/cpp/system/tests/BUILD.gn @@ -7,7 +7,10 @@ source_set("tests") { sources = [ "core_unittest.cc", - "watcher_unittest.cc", + "handle_signals_state_unittest.cc", + "simple_watcher_unittest.cc", + "wait_set_unittest.cc", + "wait_unittest.cc", ] deps = [ diff --git a/mojo/public/cpp/system/tests/core_unittest.cc b/mojo/public/cpp/system/tests/core_unittest.cc index e503db0..40a94f0 100644 --- a/mojo/public/cpp/system/tests/core_unittest.cc +++ b/mojo/public/cpp/system/tests/core_unittest.cc @@ -13,6 +13,7 @@ #include <map> #include <utility> +#include "mojo/public/cpp/system/wait.h" #include "testing/gtest/include/gtest/gtest.h" namespace mojo { @@ -108,25 +109,15 @@ TEST(CoreCppTest, Basic) { EXPECT_EQ(kInvalidHandleValue, h.get().value()); EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - Wait(h.get(), ~MOJO_HANDLE_SIGNAL_NONE, 1000000, nullptr)); + Wait(h.get(), ~MOJO_HANDLE_SIGNAL_NONE)); std::vector<Handle> wh; wh.push_back(h.get()); std::vector<MojoHandleSignals> sigs; sigs.push_back(~MOJO_HANDLE_SIGNAL_NONE); - WaitManyResult wait_many_result = - WaitMany(wh, sigs, MOJO_DEADLINE_INDEFINITE, nullptr); - EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, wait_many_result.result); - EXPECT_TRUE(wait_many_result.IsIndexValid()); - EXPECT_FALSE(wait_many_result.AreSignalsStatesValid()); - - // Make sure that our specialized template correctly handles |NULL| as well - // as |nullptr|. - wait_many_result = WaitMany(wh, sigs, MOJO_DEADLINE_INDEFINITE, NULL); - EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, wait_many_result.result); - EXPECT_EQ(0u, wait_many_result.index); - EXPECT_TRUE(wait_many_result.IsIndexValid()); - EXPECT_FALSE(wait_many_result.AreSignalsStatesValid()); + size_t result_index; + MojoResult rv = WaitMany(wh.data(), sigs.data(), wh.size(), &result_index); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, rv); } // |MakeScopedHandle| (just compilation tests): @@ -186,10 +177,7 @@ TEST(CoreCppTest, Basic) { // correctly. hv0 = h0.get().value(); MojoHandle hv1 = h1.get().value(); - MojoHandleSignalsState state; - - EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, - Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, 0, &state)); + MojoHandleSignalsState state = h0->QuerySignalsState(); EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); EXPECT_EQ(kSignalAll, state.satisfiable_signals); @@ -201,11 +189,12 @@ TEST(CoreCppTest, Basic) { sigs.push_back(MOJO_HANDLE_SIGNAL_READABLE); sigs.push_back(MOJO_HANDLE_SIGNAL_WRITABLE); std::vector<MojoHandleSignalsState> states(sigs.size()); - WaitManyResult wait_many_result = WaitMany(wh, sigs, 1000, &states); - EXPECT_EQ(MOJO_RESULT_OK, wait_many_result.result); - EXPECT_EQ(1u, wait_many_result.index); - EXPECT_TRUE(wait_many_result.IsIndexValid()); - EXPECT_TRUE(wait_many_result.AreSignalsStatesValid()); + + size_t result_index; + MojoResult rv = WaitMany(wh.data(), sigs.data(), wh.size(), &result_index, + states.data()); + EXPECT_EQ(MOJO_RESULT_OK, rv); + EXPECT_EQ(1u, result_index); EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, states[0].satisfied_signals); EXPECT_EQ(kSignalAll, states[0].satisfiable_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, states[1].satisfied_signals); @@ -217,12 +206,10 @@ TEST(CoreCppTest, Basic) { // Make sure |h1| is closed. EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - Wait(Handle(hv1), ~MOJO_HANDLE_SIGNAL_NONE, - MOJO_DEADLINE_INDEFINITE, nullptr)); + Wait(Handle(hv1), ~MOJO_HANDLE_SIGNAL_NONE)); EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); + Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, &state)); EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); @@ -248,8 +235,8 @@ TEST(CoreCppTest, Basic) { MOJO_WRITE_MESSAGE_FLAG_NONE)); MojoHandleSignalsState state; - EXPECT_EQ(MOJO_RESULT_OK, Wait(h1.get(), MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); + EXPECT_EQ(MOJO_RESULT_OK, + Wait(h1.get(), MOJO_HANDLE_SIGNAL_READABLE, &state)); EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals); EXPECT_EQ(kSignalAll, state.satisfiable_signals); @@ -298,8 +285,8 @@ TEST(CoreCppTest, Basic) { EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handles[0])); // Read "hello" and the sent handle. - EXPECT_EQ(MOJO_RESULT_OK, Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); + EXPECT_EQ(MOJO_RESULT_OK, + Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, &state)); EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals); EXPECT_EQ(kSignalAll, state.satisfiable_signals); @@ -326,8 +313,7 @@ TEST(CoreCppTest, Basic) { hv0 = handles[0]; EXPECT_EQ(MOJO_RESULT_OK, - Wait(mp.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, - MOJO_DEADLINE_INDEFINITE, &state)); + Wait(mp.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &state)); EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals); EXPECT_EQ(kSignalAll, state.satisfiable_signals); diff --git a/mojo/public/cpp/system/tests/handle_signals_state_unittest.cc b/mojo/public/cpp/system/tests/handle_signals_state_unittest.cc new file mode 100644 index 0000000..82f538e --- /dev/null +++ b/mojo/public/cpp/system/tests/handle_signals_state_unittest.cc @@ -0,0 +1,42 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/system/handle_signals_state.h" + +#include "mojo/public/c/system/types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +using HandleSignalsStateTest = testing::Test; + +TEST_F(HandleSignalsStateTest, SanityCheck) { + // There's not much to test here. Just a quick sanity check to make sure the + // code compiles and the helper methods do what they're supposed to do. + + HandleSignalsState empty_signals(MOJO_HANDLE_SIGNAL_NONE, + MOJO_HANDLE_SIGNAL_NONE); + EXPECT_FALSE(empty_signals.readable()); + EXPECT_FALSE(empty_signals.writable()); + EXPECT_FALSE(empty_signals.peer_closed()); + EXPECT_TRUE(empty_signals.never_readable()); + EXPECT_TRUE(empty_signals.never_writable()); + EXPECT_TRUE(empty_signals.never_peer_closed()); + + HandleSignalsState all_signals( + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + EXPECT_TRUE(all_signals.readable()); + EXPECT_TRUE(all_signals.writable()); + EXPECT_TRUE(all_signals.peer_closed()); + EXPECT_FALSE(all_signals.never_readable()); + EXPECT_FALSE(all_signals.never_writable()); + EXPECT_FALSE(all_signals.never_peer_closed()); +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/system/tests/simple_watcher_unittest.cc b/mojo/public/cpp/system/tests/simple_watcher_unittest.cc new file mode 100644 index 0000000..795f262 --- /dev/null +++ b/mojo/public/cpp/system/tests/simple_watcher_unittest.cc @@ -0,0 +1,277 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/system/simple_watcher.h" + +#include <memory> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +template <typename Handler> +void RunResultHandler(Handler f, MojoResult result) { + f(result); +} + +template <typename Handler> +SimpleWatcher::ReadyCallback OnReady(Handler f) { + return base::Bind(&RunResultHandler<Handler>, f); +} + +SimpleWatcher::ReadyCallback NotReached() { + return OnReady([](MojoResult) { NOTREACHED(); }); +} + +class SimpleWatcherTest : public testing::Test { + public: + SimpleWatcherTest() {} + ~SimpleWatcherTest() override {} + + private: + base::MessageLoop message_loop_; + + DISALLOW_COPY_AND_ASSIGN(SimpleWatcherTest); +}; + +TEST_F(SimpleWatcherTest, WatchBasic) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + bool notified = false; + base::RunLoop run_loop; + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, + OnReady([&](MojoResult result) { + EXPECT_EQ(MOJO_RESULT_OK, result); + notified = true; + run_loop.Quit(); + }))); + EXPECT_TRUE(b_watcher.IsWatching()); + + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + run_loop.Run(); + EXPECT_TRUE(notified); + + b_watcher.Cancel(); +} + +TEST_F(SimpleWatcherTest, WatchUnsatisfiable) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + a.reset(); + + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL); + EXPECT_EQ( + MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached())); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, b_watcher.Arm()); +} + +TEST_F(SimpleWatcherTest, WatchInvalidHandle) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + a.reset(); + b.reset(); + + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached())); + EXPECT_FALSE(b_watcher.IsWatching()); +} + +TEST_F(SimpleWatcherTest, Cancel) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + base::RunLoop run_loop; + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ( + MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached())); + EXPECT_TRUE(b_watcher.IsWatching()); + b_watcher.Cancel(); + EXPECT_FALSE(b_watcher.IsWatching()); + + // This should never trigger the watcher. + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, + run_loop.QuitClosure()); + run_loop.Run(); +} + +TEST_F(SimpleWatcherTest, CancelOnClose) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + base::RunLoop run_loop; + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, + OnReady([&](MojoResult result) { + EXPECT_EQ(MOJO_RESULT_CANCELLED, result); + run_loop.Quit(); + }))); + EXPECT_TRUE(b_watcher.IsWatching()); + + // This should trigger the watcher above. + b.reset(); + + run_loop.Run(); + + EXPECT_FALSE(b_watcher.IsWatching()); +} + +TEST_F(SimpleWatcherTest, CancelOnDestruction) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + base::RunLoop run_loop; + { + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ( + MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached())); + EXPECT_TRUE(b_watcher.IsWatching()); + + // |b_watcher| should be cancelled when it goes out of scope. + } + + // This should never trigger the watcher above. + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, + run_loop.QuitClosure()); + run_loop.Run(); +} + +TEST_F(SimpleWatcherTest, CloseAndCancel) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, + OnReady([](MojoResult result) { FAIL(); }))); + EXPECT_TRUE(b_watcher.IsWatching()); + + // This should trigger the watcher above... + b.reset(); + // ...but the watcher is cancelled first. + b_watcher.Cancel(); + + EXPECT_FALSE(b_watcher.IsWatching()); + + base::RunLoop().RunUntilIdle(); +} + +TEST_F(SimpleWatcherTest, UnarmedCancel) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL); + base::RunLoop loop; + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind( + [](base::RunLoop* loop, MojoResult result) { + EXPECT_EQ(result, MOJO_RESULT_CANCELLED); + loop->Quit(); + }, + &loop))); + + // This message write will not wake up the watcher since the watcher isn't + // armed. Instead, the cancellation will dispatch due to the reset below. + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + b.reset(); + loop.Run(); +} + +TEST_F(SimpleWatcherTest, ManualArming) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL); + base::RunLoop loop; + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind( + [](base::RunLoop* loop, MojoResult result) { + EXPECT_EQ(result, MOJO_RESULT_OK); + loop->Quit(); + }, + &loop))); + EXPECT_EQ(MOJO_RESULT_OK, b_watcher.Arm()); + + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + loop.Run(); +} + +TEST_F(SimpleWatcherTest, ManualArmOrNotifyWhileSignaled) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + base::RunLoop loop1; + SimpleWatcher b_watcher1(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL); + bool notified1 = false; + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher1.Watch( + b.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind( + [](base::RunLoop* loop, bool* notified, MojoResult result) { + EXPECT_EQ(result, MOJO_RESULT_OK); + *notified = true; + loop->Quit(); + }, + &loop1, ¬ified1))); + + base::RunLoop loop2; + SimpleWatcher b_watcher2(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL); + bool notified2 = false; + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher2.Watch( + b.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind( + [](base::RunLoop* loop, bool* notified, MojoResult result) { + EXPECT_EQ(result, MOJO_RESULT_OK); + *notified = true; + loop->Quit(); + }, + &loop2, ¬ified2))); + + // First ensure that |b| is readable. + EXPECT_EQ(MOJO_RESULT_OK, b_watcher1.Arm()); + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + loop1.Run(); + + EXPECT_TRUE(notified1); + EXPECT_FALSE(notified2); + notified1 = false; + + // Now verify that ArmOrNotify results in a notification. + b_watcher2.ArmOrNotify(); + loop2.Run(); + + EXPECT_FALSE(notified1); + EXPECT_TRUE(notified2); +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/system/tests/wait_set_unittest.cc b/mojo/public/cpp/system/tests/wait_set_unittest.cc new file mode 100644 index 0000000..d60cb45 --- /dev/null +++ b/mojo/public/cpp/system/tests/wait_set_unittest.cc @@ -0,0 +1,376 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/system/wait_set.h" + +#include <set> +#include <vector> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/ptr_util.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/platform_thread.h" +#include "base/threading/simple_thread.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "mojo/public/cpp/system/wait.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +using WaitSetTest = testing::Test; + +void WriteMessage(const ScopedMessagePipeHandle& handle, + const base::StringPiece& message) { + MojoResult rv = WriteMessageRaw(handle.get(), message.data(), + static_cast<uint32_t>(message.size()), + nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE); + CHECK_EQ(MOJO_RESULT_OK, rv); +} + +std::string ReadMessage(const ScopedMessagePipeHandle& handle) { + uint32_t num_bytes = 0; + uint32_t num_handles = 0; + MojoResult rv = ReadMessageRaw(handle.get(), nullptr, &num_bytes, nullptr, + &num_handles, MOJO_READ_MESSAGE_FLAG_NONE); + CHECK_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, rv); + CHECK_EQ(0u, num_handles); + + std::vector<char> buffer(num_bytes); + rv = ReadMessageRaw(handle.get(), buffer.data(), &num_bytes, nullptr, + &num_handles, MOJO_READ_MESSAGE_FLAG_NONE); + CHECK_EQ(MOJO_RESULT_OK, rv); + return std::string(buffer.data(), buffer.size()); +} + +class ThreadedRunner : public base::SimpleThread { + public: + explicit ThreadedRunner(const base::Closure& callback) + : SimpleThread("ThreadedRunner"), callback_(callback) {} + ~ThreadedRunner() override { Join(); } + + void Run() override { callback_.Run(); } + + private: + const base::Closure callback_; + + DISALLOW_COPY_AND_ASSIGN(ThreadedRunner); +}; + +TEST_F(WaitSetTest, Satisfied) { + WaitSet wait_set; + MessagePipe p; + + const char kTestMessage1[] = "hello wake up"; + + // Watch only one handle and write to the other. + + wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); + WriteMessage(p.handle0, kTestMessage1); + + size_t num_ready_handles = 2; + Handle ready_handles[2]; + MojoResult ready_results[2] = {MOJO_RESULT_UNKNOWN, MOJO_RESULT_UNKNOWN}; + HandleSignalsState hss[2]; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss); + + EXPECT_EQ(1u, num_ready_handles); + EXPECT_EQ(p.handle1.get(), ready_handles[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(hss[0].readable() && hss[0].writable() && !hss[0].peer_closed()); + + wait_set.RemoveHandle(p.handle1.get()); + + // Now watch only the other handle and write to the first one. + + wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); + WriteMessage(p.handle1, kTestMessage1); + + num_ready_handles = 2; + ready_results[0] = MOJO_RESULT_UNKNOWN; + ready_results[1] = MOJO_RESULT_UNKNOWN; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss); + + EXPECT_EQ(1u, num_ready_handles); + EXPECT_EQ(p.handle0.get(), ready_handles[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(hss[0].readable() && hss[0].writable() && !hss[0].peer_closed()); + + // Now wait on both of them. + wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); + + num_ready_handles = 2; + ready_results[0] = MOJO_RESULT_UNKNOWN; + ready_results[1] = MOJO_RESULT_UNKNOWN; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss); + EXPECT_EQ(2u, num_ready_handles); + EXPECT_TRUE((ready_handles[0] == p.handle0.get() && + ready_handles[1] == p.handle1.get()) || + (ready_handles[0] == p.handle1.get() && + ready_handles[1] == p.handle0.get())); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[1]); + EXPECT_TRUE(hss[0].readable() && hss[0].writable() && !hss[0].peer_closed()); + EXPECT_TRUE(hss[1].readable() && hss[1].writable() && !hss[1].peer_closed()); + + // Wait on both again, but with only enough output space for one result. + num_ready_handles = 1; + ready_results[0] = MOJO_RESULT_UNKNOWN; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_TRUE(ready_handles[0] == p.handle0.get() || + ready_handles[0] == p.handle1.get()); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + + // Remove the ready handle from the set and wait one more time. + EXPECT_EQ(MOJO_RESULT_OK, wait_set.RemoveHandle(ready_handles[0])); + + num_ready_handles = 1; + ready_results[0] = MOJO_RESULT_UNKNOWN; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_TRUE(ready_handles[0] == p.handle0.get() || + ready_handles[0] == p.handle1.get()); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + + EXPECT_EQ(MOJO_RESULT_OK, wait_set.RemoveHandle(ready_handles[0])); + + // The wait set should be empty now. Nothing to wait on. + num_ready_handles = 2; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results); + EXPECT_EQ(0u, num_ready_handles); +} + +TEST_F(WaitSetTest, Unsatisfiable) { + MessagePipe p, q; + WaitSet wait_set; + + wait_set.AddHandle(q.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); + wait_set.AddHandle(q.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); + wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); + + size_t num_ready_handles = 2; + Handle ready_handles[2]; + MojoResult ready_results[2] = {MOJO_RESULT_UNKNOWN, MOJO_RESULT_UNKNOWN}; + + p.handle1.reset(); + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_EQ(p.handle0.get(), ready_handles[0]); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]); +} + +TEST_F(WaitSetTest, CloseWhileWaiting) { + MessagePipe p; + WaitSet wait_set; + + wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); + + const Handle handle0_value = p.handle0.get(); + ThreadedRunner close_after_delay(base::Bind( + [](ScopedMessagePipeHandle* handle) { + // Wait a little while, then close the handle. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + handle->reset(); + }, + &p.handle0)); + close_after_delay.Start(); + + size_t num_ready_handles = 2; + Handle ready_handles[2]; + MojoResult ready_results[2] = {MOJO_RESULT_UNKNOWN, MOJO_RESULT_UNKNOWN}; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_EQ(handle0_value, ready_handles[0]); + EXPECT_EQ(MOJO_RESULT_CANCELLED, ready_results[0]); + + EXPECT_EQ(MOJO_RESULT_NOT_FOUND, wait_set.RemoveHandle(handle0_value)); +} + +TEST_F(WaitSetTest, CloseBeforeWaiting) { + MessagePipe p; + WaitSet wait_set; + + wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); + wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); + + Handle handle0_value = p.handle0.get(); + Handle handle1_value = p.handle1.get(); + + p.handle0.reset(); + p.handle1.reset(); + + // Ensure that the WaitSet user is always made aware of all cancellations even + // if they happen while not waiting, or they have to be returned over the span + // of multiple Wait() calls due to insufficient output storage. + + size_t num_ready_handles = 1; + Handle ready_handle; + MojoResult ready_result = MOJO_RESULT_UNKNOWN; + wait_set.Wait(nullptr, &num_ready_handles, &ready_handle, &ready_result); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_TRUE(ready_handle == handle0_value || ready_handle == handle1_value); + EXPECT_EQ(MOJO_RESULT_CANCELLED, ready_result); + EXPECT_EQ(MOJO_RESULT_NOT_FOUND, wait_set.RemoveHandle(handle0_value)); + + wait_set.Wait(nullptr, &num_ready_handles, &ready_handle, &ready_result); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_TRUE(ready_handle == handle0_value || ready_handle == handle1_value); + EXPECT_EQ(MOJO_RESULT_CANCELLED, ready_result); + EXPECT_EQ(MOJO_RESULT_NOT_FOUND, wait_set.RemoveHandle(handle0_value)); + + // Nothing more to wait on. + wait_set.Wait(nullptr, &num_ready_handles, &ready_handle, &ready_result); + EXPECT_EQ(0u, num_ready_handles); +} + +TEST_F(WaitSetTest, SatisfiedThenUnsatisfied) { + MessagePipe p; + WaitSet wait_set; + + wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); + wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); + + const char kTestMessage1[] = "testing testing testing"; + WriteMessage(p.handle0, kTestMessage1); + + size_t num_ready_handles = 2; + Handle ready_handles[2]; + MojoResult ready_results[2] = {MOJO_RESULT_UNKNOWN, MOJO_RESULT_UNKNOWN}; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_EQ(p.handle1.get(), ready_handles[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + + EXPECT_EQ(kTestMessage1, ReadMessage(p.handle1)); + + ThreadedRunner write_after_delay(base::Bind( + [](ScopedMessagePipeHandle* handle) { + // Wait a little while, then write a message. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + WriteMessage(*handle, "wakey wakey"); + }, + &p.handle1)); + write_after_delay.Start(); + + num_ready_handles = 2; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_EQ(p.handle0.get(), ready_handles[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); +} + +TEST_F(WaitSetTest, EventOnly) { + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::SIGNALED); + WaitSet wait_set; + wait_set.AddEvent(&event); + + base::WaitableEvent* ready_event = nullptr; + size_t num_ready_handles = 1; + Handle ready_handle; + MojoResult ready_result = MOJO_RESULT_UNKNOWN; + wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result); + EXPECT_EQ(0u, num_ready_handles); + EXPECT_EQ(&event, ready_event); +} + +TEST_F(WaitSetTest, EventAndHandle) { + const char kTestMessage[] = "hello hello"; + + MessagePipe p; + WriteMessage(p.handle0, kTestMessage); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + WaitSet wait_set; + wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); + wait_set.AddEvent(&event); + + base::WaitableEvent* ready_event = nullptr; + size_t num_ready_handles = 1; + Handle ready_handle; + MojoResult ready_result = MOJO_RESULT_UNKNOWN; + wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_EQ(nullptr, ready_event); + EXPECT_EQ(p.handle1.get(), ready_handle); + EXPECT_EQ(MOJO_RESULT_OK, ready_result); + + EXPECT_EQ(kTestMessage, ReadMessage(p.handle1)); + + ThreadedRunner signal_after_delay(base::Bind( + [](base::WaitableEvent* event) { + // Wait a little while, then close the handle. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + event->Signal(); + }, + &event)); + signal_after_delay.Start(); + + wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result); + EXPECT_EQ(0u, num_ready_handles); + EXPECT_EQ(&event, ready_event); +} + +TEST_F(WaitSetTest, NoStarvation) { + const char kTestMessage[] = "wait for it"; + const size_t kNumTestPipes = 50; + const size_t kNumTestEvents = 10; + + // Create a bunch of handles and events which are always ready and add them + // to a shared WaitSet. + + WaitSet wait_set; + + MessagePipe pipes[kNumTestPipes]; + for (size_t i = 0; i < kNumTestPipes; ++i) { + WriteMessage(pipes[i].handle0, kTestMessage); + Wait(pipes[i].handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); + + WriteMessage(pipes[i].handle1, kTestMessage); + Wait(pipes[i].handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); + + wait_set.AddHandle(pipes[i].handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); + wait_set.AddHandle(pipes[i].handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); + } + + std::vector<std::unique_ptr<base::WaitableEvent>> events(kNumTestEvents); + for (auto& event_ptr : events) { + event_ptr = base::MakeUnique<base::WaitableEvent>( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + event_ptr->Signal(); + wait_set.AddEvent(event_ptr.get()); + } + + // Now verify that all handle and event signals are deteceted within a finite + // number of consecutive Wait() calls. Do it a few times for good measure. + for (size_t i = 0; i < 3; ++i) { + std::set<base::WaitableEvent*> ready_events; + std::set<Handle> ready_handles; + while (ready_events.size() < kNumTestEvents || + ready_handles.size() < kNumTestPipes * 2) { + base::WaitableEvent* ready_event = nullptr; + size_t num_ready_handles = 1; + Handle ready_handle; + MojoResult ready_result = MOJO_RESULT_UNKNOWN; + wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, + &ready_result); + if (ready_event) + ready_events.insert(ready_event); + + if (num_ready_handles) { + EXPECT_EQ(1u, num_ready_handles); + EXPECT_EQ(MOJO_RESULT_OK, ready_result); + ready_handles.insert(ready_handle); + } + } + } +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/system/tests/wait_unittest.cc b/mojo/public/cpp/system/tests/wait_unittest.cc new file mode 100644 index 0000000..1d9d3c6 --- /dev/null +++ b/mojo/public/cpp/system/tests/wait_unittest.cc @@ -0,0 +1,321 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/system/wait.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/strings/string_piece.h" +#include "base/threading/platform_thread.h" +#include "base/threading/simple_thread.h" +#include "base/time/time.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/handle_signals_state.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +using WaitTest = testing::Test; +using WaitManyTest = testing::Test; + +void WriteMessage(const ScopedMessagePipeHandle& handle, + const base::StringPiece& message) { + MojoResult rv = WriteMessageRaw(handle.get(), message.data(), + static_cast<uint32_t>(message.size()), + nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE); + CHECK_EQ(MOJO_RESULT_OK, rv); +} + +std::string ReadMessage(const ScopedMessagePipeHandle& handle) { + uint32_t num_bytes = 0; + uint32_t num_handles = 0; + MojoResult rv = ReadMessageRaw(handle.get(), nullptr, &num_bytes, nullptr, + &num_handles, MOJO_READ_MESSAGE_FLAG_NONE); + CHECK_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, rv); + CHECK_EQ(0u, num_handles); + + std::vector<char> buffer(num_bytes); + rv = ReadMessageRaw(handle.get(), buffer.data(), &num_bytes, nullptr, + &num_handles, MOJO_READ_MESSAGE_FLAG_NONE); + CHECK_EQ(MOJO_RESULT_OK, rv); + return std::string(buffer.data(), buffer.size()); +} + +class ThreadedRunner : public base::SimpleThread { + public: + explicit ThreadedRunner(const base::Closure& callback) + : SimpleThread("ThreadedRunner"), callback_(callback) {} + ~ThreadedRunner() override { Join(); } + + void Run() override { callback_.Run(); } + + private: + const base::Closure callback_; + + DISALLOW_COPY_AND_ASSIGN(ThreadedRunner); +}; + +TEST_F(WaitTest, InvalidArguments) { + Handle invalid_handle; + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + Wait(invalid_handle, MOJO_HANDLE_SIGNAL_READABLE)); + + MessagePipe p; + Handle valid_handles[2] = {p.handle0.get(), p.handle1.get()}; + Handle invalid_handles[2]; + MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_NONE, + MOJO_HANDLE_SIGNAL_NONE}; + size_t result_index = 0; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + WaitMany(invalid_handles, signals, 2, &result_index)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + WaitMany(nullptr, signals, 2, &result_index)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + WaitMany(valid_handles, nullptr, 2, &result_index)); +} + +TEST_F(WaitTest, Basic) { + MessagePipe p; + + // Write to one end of the pipe and wait on the other. + const char kTestMessage1[] = "how about a nice game of chess?"; + WriteMessage(p.handle0, kTestMessage1); + EXPECT_EQ(MOJO_RESULT_OK, Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE)); + + // And make sure we can also grab the handle signals state (with both the C + // and C++ library structs.) + + MojoHandleSignalsState c_hss = {0, 0}; + EXPECT_EQ(MOJO_RESULT_OK, + Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &c_hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + c_hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + c_hss.satisfiable_signals); + + HandleSignalsState hss; + EXPECT_EQ(MOJO_RESULT_OK, + Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE(hss.readable() && hss.writable() && !hss.peer_closed()); + EXPECT_FALSE(hss.never_readable() || hss.never_writable() || + hss.never_peer_closed()); + + // Now close the writing end and wait for peer closure. + + p.handle0.reset(); + EXPECT_EQ(MOJO_RESULT_OK, + Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + + // Still readable as there's still a message queued. No longer writable as + // peer closure has been detected. + EXPECT_TRUE(hss.readable() && hss.peer_closed() && !hss.writable()); + EXPECT_TRUE(hss.never_writable() && !hss.never_readable() && + !hss.never_peer_closed()); + + // Read the message and wait for readable again. Waiting should fail since + // there are no more messages and the peer is closed. + EXPECT_EQ(kTestMessage1, ReadMessage(p.handle1)); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &hss)); + + // Sanity check the signals state again. + EXPECT_TRUE(hss.peer_closed() && !hss.readable() && !hss.writable()); + EXPECT_TRUE(hss.never_readable() && hss.never_writable() && + !hss.never_peer_closed()); +} + +TEST_F(WaitTest, DelayedWrite) { + MessagePipe p; + + ThreadedRunner write_after_delay(base::Bind( + [](ScopedMessagePipeHandle* handle) { + // Wait a little while, then write a message. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + WriteMessage(*handle, "wakey wakey"); + }, + &p.handle0)); + write_after_delay.Start(); + + HandleSignalsState hss; + EXPECT_EQ(MOJO_RESULT_OK, + Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE(hss.readable() && hss.writable() && !hss.peer_closed()); + EXPECT_TRUE(!hss.never_readable() && !hss.never_writable() && + !hss.never_peer_closed()); +} + +TEST_F(WaitTest, DelayedPeerClosure) { + MessagePipe p; + + ThreadedRunner close_after_delay(base::Bind( + [](ScopedMessagePipeHandle* handle) { + // Wait a little while, then close the handle. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + handle->reset(); + }, + &p.handle0)); + close_after_delay.Start(); + + HandleSignalsState hss; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE(!hss.readable() && !hss.writable() && hss.peer_closed()); + EXPECT_TRUE(hss.never_readable() && hss.never_writable() && + !hss.never_peer_closed()); +} + +TEST_F(WaitTest, CloseWhileWaiting) { + MessagePipe p; + ThreadedRunner close_after_delay(base::Bind( + [](ScopedMessagePipeHandle* handle) { + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + handle->reset(); + }, + &p.handle0)); + close_after_delay.Start(); + EXPECT_EQ(MOJO_RESULT_CANCELLED, + Wait(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE)); +} + +TEST_F(WaitManyTest, Basic) { + MessagePipe p; + + const char kTestMessage1[] = "hello"; + WriteMessage(p.handle0, kTestMessage1); + + // Wait for either handle to become readable. Wait twice, just to verify that + // we can use either the C or C++ signaling state structure for the last + // argument. + + Handle handles[2] = {p.handle0.get(), p.handle1.get()}; + MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE}; + size_t result_index = 0; + MojoHandleSignalsState c_hss[2]; + HandleSignalsState hss[2]; + + EXPECT_EQ(MOJO_RESULT_OK, + WaitMany(handles, signals, 2, &result_index, c_hss)); + EXPECT_EQ(1u, result_index); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, c_hss[0].satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + c_hss[0].satisfiable_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + c_hss[1].satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + c_hss[1].satisfiable_signals); + + EXPECT_EQ(MOJO_RESULT_OK, WaitMany(handles, signals, 2, &result_index, hss)); + EXPECT_EQ(1u, result_index); + EXPECT_TRUE(!hss[0].readable() && hss[0].writable() && !hss[0].peer_closed()); + EXPECT_TRUE(!hss[0].never_readable() && !hss[0].never_writable() && + !hss[0].never_peer_closed()); + EXPECT_TRUE(hss[1].readable() && hss[1].writable() && !hss[1].peer_closed()); + EXPECT_TRUE(!hss[1].never_readable() && !hss[1].never_writable() && + !hss[1].never_peer_closed()); + + // Close the writer and read the message. Try to wait again, and it should + // fail due to the conditions being unsatisfiable. + + EXPECT_EQ(kTestMessage1, ReadMessage(p.handle1)); + p.handle0.reset(); + + // handles[0] is invalid. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + WaitMany(handles, signals, 2, &result_index, hss)); + handles[0] = handles[1]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitMany(handles, signals, 1, &result_index, hss)); + EXPECT_EQ(0u, result_index); + EXPECT_TRUE(!hss[0].readable() && !hss[0].writable() && hss[0].peer_closed()); + EXPECT_TRUE(hss[0].never_readable() && hss[0].never_writable() && + !hss[0].never_peer_closed()); +} + +TEST_F(WaitManyTest, CloseWhileWaiting) { + MessagePipe p, q; + + Handle handles[3] = {q.handle0.get(), q.handle1.get(), p.handle1.get()}; + MojoHandleSignals signals[3] = {MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE}; + + ThreadedRunner close_after_delay(base::Bind( + [](ScopedMessagePipeHandle* handle) { + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + handle->reset(); + }, + &p.handle1)); + close_after_delay.Start(); + + size_t result_index = 0; + EXPECT_EQ(MOJO_RESULT_CANCELLED, + WaitMany(handles, signals, 3, &result_index)); + EXPECT_EQ(2u, result_index); +} + +TEST_F(WaitManyTest, DelayedWrite) { + MessagePipe p; + + ThreadedRunner write_after_delay(base::Bind( + [](ScopedMessagePipeHandle* handle) { + // Wait a little while, then write a message. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + WriteMessage(*handle, "wakey wakey"); + }, + &p.handle0)); + write_after_delay.Start(); + + Handle handles[2] = {p.handle0.get(), p.handle1.get()}; + MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE}; + size_t result_index = 0; + HandleSignalsState hss[2]; + EXPECT_EQ(MOJO_RESULT_OK, WaitMany(handles, signals, 2, &result_index, hss)); + EXPECT_EQ(1u, result_index); + EXPECT_TRUE(!hss[0].readable() && hss[0].writable() && !hss[0].peer_closed()); + EXPECT_TRUE(!hss[0].never_readable() && !hss[0].never_writable() && + !hss[0].never_peer_closed()); + EXPECT_TRUE(hss[1].readable() && hss[1].writable() && !hss[1].peer_closed()); + EXPECT_TRUE(!hss[1].never_readable() && !hss[1].never_writable() && + !hss[1].never_peer_closed()); +} + +TEST_F(WaitManyTest, DelayedPeerClosure) { + MessagePipe p, q; + + ThreadedRunner close_after_delay(base::Bind( + [](ScopedMessagePipeHandle* handle) { + // Wait a little while, then close the handle. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + handle->reset(); + }, + &p.handle0)); + close_after_delay.Start(); + + Handle handles[3] = {q.handle0.get(), q.handle1.get(), p.handle1.get()}; + MojoHandleSignals signals[3] = {MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE}; + size_t result_index = 0; + HandleSignalsState hss[3]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitMany(handles, signals, 3, &result_index, hss)); + EXPECT_EQ(2u, result_index); + EXPECT_TRUE(!hss[2].readable() && !hss[2].writable() && hss[2].peer_closed()); + EXPECT_TRUE(hss[2].never_readable() && hss[2].never_writable() && + !hss[2].never_peer_closed()); +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/system/tests/watcher_unittest.cc b/mojo/public/cpp/system/tests/watcher_unittest.cc deleted file mode 100644 index 9b59240..0000000 --- a/mojo/public/cpp/system/tests/watcher_unittest.cc +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "mojo/public/cpp/system/watcher.h" - -#include <memory> - -#include "base/bind.h" -#include "base/callback.h" -#include "base/macros.h" -#include "base/message_loop/message_loop.h" -#include "base/run_loop.h" -#include "base/threading/thread_task_runner_handle.h" -#include "mojo/public/c/system/types.h" -#include "mojo/public/cpp/system/message_pipe.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace mojo { -namespace { - -template <typename Handler> -void RunResultHandler(Handler f, MojoResult result) { f(result); } - -template <typename Handler> -Watcher::ReadyCallback OnReady(Handler f) { - return base::Bind(&RunResultHandler<Handler>, f); -} - -Watcher::ReadyCallback NotReached() { - return OnReady([] (MojoResult) { NOTREACHED(); }); -} - -class WatcherTest : public testing::Test { - public: - WatcherTest() {} - ~WatcherTest() override {} - - private: - base::MessageLoop message_loop_; - - DISALLOW_COPY_AND_ASSIGN(WatcherTest); -}; - -TEST_F(WatcherTest, WatchBasic) { - ScopedMessagePipeHandle a, b; - CreateMessagePipe(nullptr, &a, &b); - - bool notified = false; - base::RunLoop run_loop; - Watcher b_watcher(FROM_HERE); - EXPECT_EQ(MOJO_RESULT_OK, - b_watcher.Start(b.get(), MOJO_HANDLE_SIGNAL_READABLE, - OnReady([&] (MojoResult result) { - EXPECT_EQ(MOJO_RESULT_OK, result); - notified = true; - run_loop.Quit(); - }))); - EXPECT_TRUE(b_watcher.IsWatching()); - - EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, - MOJO_WRITE_MESSAGE_FLAG_NONE)); - run_loop.Run(); - EXPECT_TRUE(notified); - - b_watcher.Cancel(); -} - -TEST_F(WatcherTest, WatchUnsatisfiable) { - ScopedMessagePipeHandle a, b; - CreateMessagePipe(nullptr, &a, &b); - a.reset(); - - Watcher b_watcher(FROM_HERE); - EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, - b_watcher.Start(b.get(), MOJO_HANDLE_SIGNAL_READABLE, - NotReached())); - EXPECT_FALSE(b_watcher.IsWatching()); -} - -TEST_F(WatcherTest, WatchInvalidHandle) { - ScopedMessagePipeHandle a, b; - CreateMessagePipe(nullptr, &a, &b); - a.reset(); - b.reset(); - - Watcher b_watcher(FROM_HERE); - EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, - b_watcher.Start(b.get(), MOJO_HANDLE_SIGNAL_READABLE, - NotReached())); - EXPECT_FALSE(b_watcher.IsWatching()); -} - -TEST_F(WatcherTest, Cancel) { - ScopedMessagePipeHandle a, b; - CreateMessagePipe(nullptr, &a, &b); - - base::RunLoop run_loop; - Watcher b_watcher(FROM_HERE); - EXPECT_EQ(MOJO_RESULT_OK, - b_watcher.Start(b.get(), MOJO_HANDLE_SIGNAL_READABLE, - NotReached())); - EXPECT_TRUE(b_watcher.IsWatching()); - b_watcher.Cancel(); - EXPECT_FALSE(b_watcher.IsWatching()); - - // This should never trigger the watcher. - EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, - MOJO_WRITE_MESSAGE_FLAG_NONE)); - - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, run_loop.QuitClosure()); - run_loop.Run(); -} - -TEST_F(WatcherTest, CancelOnClose) { - ScopedMessagePipeHandle a, b; - CreateMessagePipe(nullptr, &a, &b); - - base::RunLoop run_loop; - Watcher b_watcher(FROM_HERE); - EXPECT_EQ(MOJO_RESULT_OK, - b_watcher.Start(b.get(), MOJO_HANDLE_SIGNAL_READABLE, - OnReady([&] (MojoResult result) { - EXPECT_EQ(MOJO_RESULT_CANCELLED, result); - run_loop.Quit(); - }))); - EXPECT_TRUE(b_watcher.IsWatching()); - - // This should trigger the watcher above. - b.reset(); - - run_loop.Run(); - - EXPECT_FALSE(b_watcher.IsWatching()); -} - -TEST_F(WatcherTest, CancelOnDestruction) { - ScopedMessagePipeHandle a, b; - CreateMessagePipe(nullptr, &a, &b); - base::RunLoop run_loop; - { - Watcher b_watcher(FROM_HERE); - EXPECT_EQ(MOJO_RESULT_OK, - b_watcher.Start(b.get(), MOJO_HANDLE_SIGNAL_READABLE, - NotReached())); - EXPECT_TRUE(b_watcher.IsWatching()); - - // |b_watcher| should be cancelled when it goes out of scope. - } - - // This should never trigger the watcher above. - EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, - MOJO_WRITE_MESSAGE_FLAG_NONE)); - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, run_loop.QuitClosure()); - run_loop.Run(); -} - -TEST_F(WatcherTest, CloseAndCancel) { - ScopedMessagePipeHandle a, b; - CreateMessagePipe(nullptr, &a, &b); - - Watcher b_watcher(FROM_HERE); - EXPECT_EQ(MOJO_RESULT_OK, - b_watcher.Start(b.get(), MOJO_HANDLE_SIGNAL_READABLE, - OnReady([](MojoResult result) { FAIL(); }))); - EXPECT_TRUE(b_watcher.IsWatching()); - - // This should trigger the watcher above... - b.reset(); - // ...but the watcher is cancelled first. - b_watcher.Cancel(); - - EXPECT_FALSE(b_watcher.IsWatching()); - - base::RunLoop().RunUntilIdle(); -} - -} // namespace -} // namespace mojo diff --git a/mojo/public/cpp/system/wait.cc b/mojo/public/cpp/system/wait.cc new file mode 100644 index 0000000..e4e124f --- /dev/null +++ b/mojo/public/cpp/system/wait.cc @@ -0,0 +1,200 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/system/wait.h" + +#include <memory> +#include <vector> + +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/waitable_event.h" +#include "mojo/public/c/system/watcher.h" +#include "mojo/public/cpp/system/watcher.h" + +namespace mojo { +namespace { + +class WatchContext : public base::RefCountedThreadSafe<WatchContext> { + public: + WatchContext() + : event_(base::WaitableEvent::ResetPolicy::AUTOMATIC, + base::WaitableEvent::InitialState::NOT_SIGNALED) {} + + base::WaitableEvent& event() { return event_; } + MojoResult wait_result() const { return wait_result_; } + MojoHandleSignalsState wait_state() const { return wait_state_; } + uintptr_t context_value() const { return reinterpret_cast<uintptr_t>(this); } + + static void OnNotification(uintptr_t context_value, + MojoResult result, + MojoHandleSignalsState state, + MojoWatcherNotificationFlags flags) { + auto* context = reinterpret_cast<WatchContext*>(context_value); + context->Notify(result, state); + if (result == MOJO_RESULT_CANCELLED) { + // Balanced in Wait() or WaitMany(). + context->Release(); + } + } + + private: + friend class base::RefCountedThreadSafe<WatchContext>; + + ~WatchContext() {} + + void Notify(MojoResult result, MojoHandleSignalsState state) { + if (wait_result_ == MOJO_RESULT_UNKNOWN) { + wait_result_ = result; + wait_state_ = state; + } + event_.Signal(); + } + + base::WaitableEvent event_; + + // NOTE: Although these are modified in Notify() which may be called from any + // thread, Notify() is guaranteed to never run concurrently with itself. + // Furthermore, they are only modified once, before |event_| signals; so there + // is no need for a WatchContext user to synchronize access to these fields + // apart from waiting on |event()|. + MojoResult wait_result_ = MOJO_RESULT_UNKNOWN; + MojoHandleSignalsState wait_state_ = {0, 0}; + + DISALLOW_COPY_AND_ASSIGN(WatchContext); +}; + +} // namespace + +MojoResult Wait(Handle handle, + MojoHandleSignals signals, + MojoHandleSignalsState* signals_state) { + ScopedWatcherHandle watcher; + MojoResult rv = CreateWatcher(&WatchContext::OnNotification, &watcher); + DCHECK_EQ(MOJO_RESULT_OK, rv); + + scoped_refptr<WatchContext> context = new WatchContext; + + // Balanced in WatchContext::OnNotification if MojoWatch() is successful. + // Otherwise balanced immediately below. + context->AddRef(); + + rv = MojoWatch(watcher.get().value(), handle.value(), signals, + context->context_value()); + if (rv == MOJO_RESULT_INVALID_ARGUMENT) { + // Balanced above. + context->Release(); + return rv; + } + DCHECK_EQ(MOJO_RESULT_OK, rv); + + uint32_t num_ready_contexts = 1; + uintptr_t ready_context; + MojoResult ready_result; + MojoHandleSignalsState ready_state; + rv = MojoArmWatcher(watcher.get().value(), &num_ready_contexts, + &ready_context, &ready_result, &ready_state); + if (rv == MOJO_RESULT_FAILED_PRECONDITION) { + DCHECK_EQ(1u, num_ready_contexts); + if (signals_state) + *signals_state = ready_state; + return ready_result; + } + + // Wait for the first notification only. + context->event().Wait(); + + ready_result = context->wait_result(); + DCHECK_NE(MOJO_RESULT_UNKNOWN, ready_result); + + if (signals_state) + *signals_state = context->wait_state(); + + return ready_result; +} + +MojoResult WaitMany(const Handle* handles, + const MojoHandleSignals* signals, + size_t num_handles, + size_t* result_index, + MojoHandleSignalsState* signals_states) { + if (!handles || !signals) + return MOJO_RESULT_INVALID_ARGUMENT; + + ScopedWatcherHandle watcher; + MojoResult rv = CreateWatcher(&WatchContext::OnNotification, &watcher); + DCHECK_EQ(MOJO_RESULT_OK, rv); + + std::vector<scoped_refptr<WatchContext>> contexts(num_handles); + std::vector<base::WaitableEvent*> events(num_handles); + for (size_t i = 0; i < num_handles; ++i) { + contexts[i] = new WatchContext(); + + // Balanced in WatchContext::OnNotification if MojoWatch() is successful. + // Otherwise balanced immediately below. + contexts[i]->AddRef(); + + MojoResult rv = MojoWatch(watcher.get().value(), handles[i].value(), + signals[i], contexts[i]->context_value()); + if (rv == MOJO_RESULT_INVALID_ARGUMENT) { + if (result_index) + *result_index = i; + + // Balanced above. + contexts[i]->Release(); + + return MOJO_RESULT_INVALID_ARGUMENT; + } + + events[i] = &contexts[i]->event(); + } + + uint32_t num_ready_contexts = 1; + uintptr_t ready_context = 0; + MojoResult ready_result = MOJO_RESULT_UNKNOWN; + MojoHandleSignalsState ready_state{0, 0}; + rv = MojoArmWatcher(watcher.get().value(), &num_ready_contexts, + &ready_context, &ready_result, &ready_state); + + size_t index = num_handles; + if (rv == MOJO_RESULT_FAILED_PRECONDITION) { + DCHECK_EQ(1u, num_ready_contexts); + + // Most commonly we only watch a small number of handles. Just scan for + // the right index. + for (size_t i = 0; i < num_handles; ++i) { + if (contexts[i]->context_value() == ready_context) { + index = i; + break; + } + } + } else { + DCHECK_EQ(MOJO_RESULT_OK, rv); + + // Wait for one of the contexts to signal. First one wins. + index = base::WaitableEvent::WaitMany(events.data(), events.size()); + ready_result = contexts[index]->wait_result(); + ready_state = contexts[index]->wait_state(); + } + + DCHECK_NE(MOJO_RESULT_UNKNOWN, ready_result); + DCHECK_LT(index, num_handles); + + if (result_index) + *result_index = index; + + if (signals_states) { + for (size_t i = 0; i < num_handles; ++i) { + if (i == index) { + signals_states[i] = ready_state; + } else { + signals_states[i] = handles[i].QuerySignalsState(); + } + } + } + + return ready_result; +} + +} // namespace mojo diff --git a/mojo/public/cpp/system/wait.h b/mojo/public/cpp/system/wait.h new file mode 100644 index 0000000..808e44f --- /dev/null +++ b/mojo/public/cpp/system/wait.h @@ -0,0 +1,75 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_WAIT_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_WAIT_H_ + +#include <stddef.h> + +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/system_export.h" + +namespace mojo { + +// Blocks the calling thread, waiting for one or more signals in |signals| to be +// become satisfied -- or for all of them to become unsatisfiable -- on the +// given Handle. +// +// If |signals_state| is non-null, |handle| is valid, the wait is not cancelled +// (see return values below), the last known signaling state of |handle| is +// written to |*signals_state| before returning. +// +// Return values: +// |MOJO_RESULT_OK| if one or more signals in |signals| has been raised on +// |handle| . +// |MOJO_RESULT_FAILED_PRECONDITION| if the state of |handle| changes such +// that no signals in |signals| can ever be raised again. +// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle. +// |MOJO_RESULT_CANCELLED| if the wait was cancelled because |handle| was +// closed by some other thread while waiting. +MOJO_CPP_SYSTEM_EXPORT MojoResult +Wait(Handle handle, + MojoHandleSignals signals, + MojoHandleSignalsState* signals_state = nullptr); + +// Waits on |handles[0]|, ..., |handles[num_handles-1]| until: +// - At least one handle satisfies a signal indicated in its respective +// |signals[0]|, ..., |signals[num_handles-1]|. +// - It becomes known that no signal in some |signals[i]| will ever be +// satisfied. +// +// This means that |WaitMany()| behaves as if |Wait()| were called on each +// handle/signals pair simultaneously, completing when the first |Wait()| would +// complete. +// +// If |signals_states| is non-null, all other arguments are valid, and the wait +// is not cancelled (see return values below), the last known signaling state of +// each Handle |handles[i]| is written to its corresponding entry in +// |signals_states[i]| before returning. +// +// Returns values: +// |MOJO_RESULT_OK| if one of the Handles in |handles| had one or more of its +// correpsonding signals satisfied. |*result_index| contains the index +// of the Handle in question if |result_index| is non-null. +// |MOJO_RESULT_FAILED_PRECONDITION| if one of the Handles in |handles| +// changes state such that its corresponding signals become permanently +// unsatisfiable. |*result_index| contains the index of the handle in +// question if |result_index| is non-null. +// |MOJO_RESULT_INVALID_ARGUMENT| if any Handle in |handles| is invalid, +// or if either |handles| or |signals| is null. +// |MOJO_RESULT_CANCELLED| if the wait was cancelled because a handle in +// |handles| was closed by some other thread while waiting. +// |*result_index| contains the index of the closed Handle if +// |result_index| is non-null. +MOJO_CPP_SYSTEM_EXPORT MojoResult +WaitMany(const Handle* handles, + const MojoHandleSignals* signals, + size_t num_handles, + size_t* result_index = nullptr, + MojoHandleSignalsState* signals_states = nullptr); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_WAIT_H_ diff --git a/mojo/public/cpp/system/wait_set.cc b/mojo/public/cpp/system/wait_set.cc new file mode 100644 index 0000000..1728f81 --- /dev/null +++ b/mojo/public/cpp/system/wait_set.cc @@ -0,0 +1,371 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/system/wait_set.h" + +#include <algorithm> +#include <limits> +#include <map> +#include <set> +#include <vector> + +#include "base/containers/stack_container.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" +#include "mojo/public/cpp/system/watcher.h" + +namespace mojo { + +class WaitSet::State : public base::RefCountedThreadSafe<State> { + public: + State() + : handle_event_(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED) { + MojoResult rv = CreateWatcher(&Context::OnNotification, &watcher_handle_); + DCHECK_EQ(MOJO_RESULT_OK, rv); + } + + void ShutDown() { + // NOTE: This may immediately invoke Notify for every context. + watcher_handle_.reset(); + } + + MojoResult AddEvent(base::WaitableEvent* event) { + auto result = user_events_.insert(event); + if (result.second) + return MOJO_RESULT_OK; + return MOJO_RESULT_ALREADY_EXISTS; + } + + MojoResult RemoveEvent(base::WaitableEvent* event) { + auto it = user_events_.find(event); + if (it == user_events_.end()) + return MOJO_RESULT_NOT_FOUND; + user_events_.erase(it); + return MOJO_RESULT_OK; + } + + MojoResult AddHandle(Handle handle, MojoHandleSignals signals) { + DCHECK(watcher_handle_.is_valid()); + + scoped_refptr<Context> context = new Context(this, handle); + + { + base::AutoLock lock(lock_); + + if (handle_to_context_.count(handle)) + return MOJO_RESULT_ALREADY_EXISTS; + DCHECK(!contexts_.count(context->context_value())); + + handle_to_context_[handle] = context; + contexts_[context->context_value()] = context; + } + + // Balanced in State::Notify() with MOJO_RESULT_CANCELLED if + // MojoWatch() succeeds. Otherwise balanced immediately below. + context->AddRef(); + + // This can notify immediately if the watcher is already armed. Don't hold + // |lock_| while calling it. + MojoResult rv = MojoWatch(watcher_handle_.get().value(), handle.value(), + signals, context->context_value()); + if (rv == MOJO_RESULT_INVALID_ARGUMENT) { + base::AutoLock lock(lock_); + handle_to_context_.erase(handle); + contexts_.erase(context->context_value()); + + // Balanced above. + context->Release(); + return rv; + } + DCHECK_EQ(MOJO_RESULT_OK, rv); + + return rv; + } + + MojoResult RemoveHandle(Handle handle) { + DCHECK(watcher_handle_.is_valid()); + + scoped_refptr<Context> context; + { + base::AutoLock lock(lock_); + auto it = handle_to_context_.find(handle); + if (it == handle_to_context_.end()) + return MOJO_RESULT_NOT_FOUND; + + context = std::move(it->second); + handle_to_context_.erase(it); + + // Ensure that we never return this handle as a ready result again. Note + // that it's removal from |handle_to_context_| above ensures it will never + // be added back to this map. + ready_handles_.erase(handle); + } + + // NOTE: This may enter the notification callback immediately, so don't hold + // |lock_| while calling it. + MojoResult rv = MojoCancelWatch(watcher_handle_.get().value(), + context->context_value()); + + // We don't really care whether or not this succeeds. In either case, the + // context was or will imminently be cancelled and moved from |contexts_| + // to |cancelled_contexts_|. + DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_NOT_FOUND); + + { + // Always clear |cancelled_contexts_| in case it's accumulated any more + // entries since the last time we ran. + base::AutoLock lock(lock_); + cancelled_contexts_.clear(); + } + + return rv; + } + + void Wait(base::WaitableEvent** ready_event, + size_t* num_ready_handles, + Handle* ready_handles, + MojoResult* ready_results, + MojoHandleSignalsState* signals_states) { + DCHECK(watcher_handle_.is_valid()); + DCHECK(num_ready_handles); + DCHECK(ready_handles); + DCHECK(ready_results); + { + base::AutoLock lock(lock_); + if (ready_handles_.empty()) { + // No handles are currently in the ready set. Make sure the event is + // reset and try to arm the watcher. + handle_event_.Reset(); + + DCHECK_LE(*num_ready_handles, std::numeric_limits<uint32_t>::max()); + uint32_t num_ready_contexts = static_cast<uint32_t>(*num_ready_handles); + + base::StackVector<uintptr_t, 4> ready_contexts; + ready_contexts.container().resize(num_ready_contexts); + base::StackVector<MojoHandleSignalsState, 4> ready_states; + MojoHandleSignalsState* out_states = signals_states; + if (!out_states) { + // If the caller didn't provide a buffer for signal states, we provide + // our own locally. MojoArmWatcher() requires one if we want to handle + // arming failure properly. + ready_states.container().resize(num_ready_contexts); + out_states = ready_states.container().data(); + } + MojoResult rv = MojoArmWatcher( + watcher_handle_.get().value(), &num_ready_contexts, + ready_contexts.container().data(), ready_results, out_states); + + if (rv == MOJO_RESULT_FAILED_PRECONDITION) { + // Simulate the handles becoming ready. We do this in lieu of + // returning the results immediately so as to avoid potentially + // starving user events. i.e., we always want to call WaitMany() + // below. + handle_event_.Signal(); + for (size_t i = 0; i < num_ready_contexts; ++i) { + auto it = contexts_.find(ready_contexts.container()[i]); + DCHECK(it != contexts_.end()); + ready_handles_[it->second->handle()] = {ready_results[i], + out_states[i]}; + } + } else if (rv == MOJO_RESULT_NOT_FOUND) { + // Nothing to watch. If there are no user events, always signal to + // avoid deadlock. + if (user_events_.empty()) + handle_event_.Signal(); + } else { + // Watcher must be armed now. No need to manually signal. + DCHECK_EQ(MOJO_RESULT_OK, rv); + } + } + } + + // Build a local contiguous array of events to wait on. These are rotated + // across Wait() calls to avoid starvation, by virtue of the fact that + // WaitMany guarantees left-to-right priority when multiple events are + // signaled. + + base::StackVector<base::WaitableEvent*, 4> events; + events.container().resize(user_events_.size() + 1); + if (waitable_index_shift_ > user_events_.size()) + waitable_index_shift_ = 0; + + size_t dest_index = waitable_index_shift_++; + events.container()[dest_index] = &handle_event_; + for (auto* e : user_events_) { + dest_index = (dest_index + 1) % events.container().size(); + events.container()[dest_index] = e; + } + + size_t index = base::WaitableEvent::WaitMany(events.container().data(), + events.container().size()); + base::AutoLock lock(lock_); + + // Pop as many handles as we can out of the ready set and return them. Note + // that we do this regardless of which event signaled, as there may be + // ready handles in any case and they may be interesting to the caller. + *num_ready_handles = std::min(*num_ready_handles, ready_handles_.size()); + for (size_t i = 0; i < *num_ready_handles; ++i) { + auto it = ready_handles_.begin(); + ready_handles[i] = it->first; + ready_results[i] = it->second.result; + if (signals_states) + signals_states[i] = it->second.signals_state; + ready_handles_.erase(it); + } + + // If the caller cares, let them know which user event unblocked us, if any. + if (ready_event) { + if (events.container()[index] == &handle_event_) + *ready_event = nullptr; + else + *ready_event = events.container()[index]; + } + } + + private: + friend class base::RefCountedThreadSafe<State>; + + class Context : public base::RefCountedThreadSafe<Context> { + public: + Context(scoped_refptr<State> state, Handle handle) + : state_(state), handle_(handle) {} + + Handle handle() const { return handle_; } + + uintptr_t context_value() const { + return reinterpret_cast<uintptr_t>(this); + } + + static void OnNotification(uintptr_t context, + MojoResult result, + MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags) { + reinterpret_cast<Context*>(context)->Notify(result, signals_state); + } + + private: + friend class base::RefCountedThreadSafe<Context>; + + ~Context() {} + + void Notify(MojoResult result, MojoHandleSignalsState signals_state) { + state_->Notify(handle_, result, signals_state, this); + } + + const scoped_refptr<State> state_; + const Handle handle_; + + DISALLOW_COPY_AND_ASSIGN(Context); + }; + + ~State() {} + + void Notify(Handle handle, + MojoResult result, + MojoHandleSignalsState signals_state, + Context* context) { + base::AutoLock lock(lock_); + + // This could be a cancellation notification following an explicit + // RemoveHandle(), in which case we really don't care and don't want to + // add it to the ready set. Only update and signal if that's not the case. + if (!handle_to_context_.count(handle)) { + DCHECK_EQ(MOJO_RESULT_CANCELLED, result); + } else { + ready_handles_[handle] = {result, signals_state}; + handle_event_.Signal(); + } + + // Whether it's an implicit or explicit cancellation, erase from |contexts_| + // and append to |cancelled_contexts_|. + if (result == MOJO_RESULT_CANCELLED) { + contexts_.erase(context->context_value()); + handle_to_context_.erase(handle); + + // NOTE: We retain a context ref in |cancelled_contexts_| to ensure that + // this Context's heap address is not reused too soon. For example, it + // would otherwise be possible for the user to call AddHandle() from the + // WaitSet's thread immediately after this notification has fired on + // another thread, potentially reusing the same heap address for the newly + // added Context; and then they may call RemoveHandle() for this handle + // (not knowing its context has just been implicitly cancelled) and + // cause the new Context to be incorrectly removed from |contexts_|. + // + // This vector is cleared on the WaitSet's own thread every time + // RemoveHandle is called. + cancelled_contexts_.emplace_back(make_scoped_refptr(context)); + + // Balanced in State::AddHandle(). + context->Release(); + } + } + + struct ReadyState { + ReadyState() = default; + ReadyState(MojoResult result, MojoHandleSignalsState signals_state) + : result(result), signals_state(signals_state) {} + ~ReadyState() = default; + + MojoResult result = MOJO_RESULT_UNKNOWN; + MojoHandleSignalsState signals_state = {0, 0}; + }; + + // Not guarded by lock. Must only be accessed from the WaitSet's owning + // thread. + ScopedWatcherHandle watcher_handle_; + + base::Lock lock_; + std::map<uintptr_t, scoped_refptr<Context>> contexts_; + std::map<Handle, scoped_refptr<Context>> handle_to_context_; + std::map<Handle, ReadyState> ready_handles_; + std::vector<scoped_refptr<Context>> cancelled_contexts_; + std::set<base::WaitableEvent*> user_events_; + + // Event signaled any time a handle notification is received. + base::WaitableEvent handle_event_; + + // Offset by which to rotate the current set of waitable objects. This is used + // to guard against event starvation, as base::WaitableEvent::WaitMany gives + // preference to events in left-to-right order. + size_t waitable_index_shift_ = 0; + + DISALLOW_COPY_AND_ASSIGN(State); +}; + +WaitSet::WaitSet() : state_(new State) {} + +WaitSet::~WaitSet() { + state_->ShutDown(); +} + +MojoResult WaitSet::AddEvent(base::WaitableEvent* event) { + return state_->AddEvent(event); +} + +MojoResult WaitSet::RemoveEvent(base::WaitableEvent* event) { + return state_->RemoveEvent(event); +} + +MojoResult WaitSet::AddHandle(Handle handle, MojoHandleSignals signals) { + return state_->AddHandle(handle, signals); +} + +MojoResult WaitSet::RemoveHandle(Handle handle) { + return state_->RemoveHandle(handle); +} + +void WaitSet::Wait(base::WaitableEvent** ready_event, + size_t* num_ready_handles, + Handle* ready_handles, + MojoResult* ready_results, + MojoHandleSignalsState* signals_states) { + state_->Wait(ready_event, num_ready_handles, ready_handles, ready_results, + signals_states); +} + +} // namespace mojo diff --git a/mojo/public/cpp/system/wait_set.h b/mojo/public/cpp/system/wait_set.h new file mode 100644 index 0000000..5047a86 --- /dev/null +++ b/mojo/public/cpp/system/wait_set.h @@ -0,0 +1,124 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_WAIT_SET_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_WAIT_SET_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/system_export.h" + +namespace base { +class WaitableEvent; +} + +namespace mojo { + +// WaitSet provides an efficient means of blocking a thread on any number of +// events and Mojo handle state changes. +// +// Unlike WaitMany(), which incurs some extra setup cost for every call, a +// WaitSet maintains some persistent accounting of the handles added or removed +// from the set. A blocking wait operation (see the Wait() method below) can +// then be performed multiple times for the same set of events and handles with +// minimal additional setup per call. +// +// WaitSet is NOT thread-safe, so naturally handles and events may not be added +// to or removed from the set while waiting. +class MOJO_CPP_SYSTEM_EXPORT WaitSet { + public: + WaitSet(); + ~WaitSet(); + + // Adds |event| to the set of events to wait on. If successful, any future + // Wait() on this WaitSet will wake up if the event is signaled. + // + // |event| is not owned. + // + // Return values: + // |MOJO_RESULT_OK| if |event| has been successfully added. + // |MOJO_RESULT_ALREADY_EXISTS| if |event| is already in this WaitSet. + MojoResult AddEvent(base::WaitableEvent* event); + + // Removes |event| from the set of events to wait on. + // + // Return values: + // |MOJO_RESULT_OK| if |event| has been successfully added. + // |MOJO_RESULT_NOT_FOUND| if |event| was not in the set. + MojoResult RemoveEvent(base::WaitableEvent* event); + + // Adds |handle| to the set of handles to wait on. If successful, any future + // Wait() on this WaitSet will wake up in the event that one or more signals + // in |signals| becomes satisfied on |handle| or all of them become + // permanently unsatisfiable. + // + // Return values: + // |MOJO_RESULT_OK| if |handle| has been successfully added. + // |MOJO_RESULT_ALREADY_EXISTS| if |handle| is already in this WaitSet. + // |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle. + MojoResult AddHandle(Handle handle, MojoHandleSignals signals); + + // Removes |handle| from the set of handles to wait on. Future calls to + // Wait() will be unaffected by the state of this handle. + // + // Return values: + // |MOJO_RESULT_OK| if |handle| has been successfully removed. + // |MOJO_RESULT_NOT_FOUND| if |handle| was not in the set. + MojoResult RemoveHandle(Handle handle); + + // Waits on the current set of handles, waking up when one more of them meets + // the signaling conditions which were specified when they were added via + // AddHandle() above. + // + // |*num_ready_handles| on input must specify the number of entries available + // for output storage in |ready_handles| and |ready_result| (which must both + // be non-null). If |signals_states| is non-null it must also point to enough + // storage for |*num_ready_handles| MojoHandleSignalsState structures. + // + // Upon return, |*num_ready_handles| will contain the total number of handles + // whose information is stored in the given output buffers. + // + // If |ready_event| is non-null and the Wait() was unblocked by a user event + // signaling, the address of the event which signaled will be placed in + // |*ready_event|. Note that this is not necessarily exclusive to one or more + // handles also being ready. If |ready_event| is non-null and no user event + // was signaled for this Wait(), |*ready_event| will be null upon return. + // + // Every entry in |ready_handles| on output corresponds to one of the handles + // whose signaling state termianted the Wait() operation. Every corresponding + // entry in |ready_results| indicates the status of a ready handle according + // to the following result codes: + // |MOJO_RESULT_OK| one or more signals for the handle has been satisfied. + // |MOJO_RESULT_FAILED_PRECONDITION| all of the signals for the handle have + // become permanently unsatisfiable. + // |MOJO_RESULT_CANCELLED| if the handle has been closed from another + // thread. NOTE: It is important to recognize that this means the + // corresponding value in |ready_handles| is either invalid, or valid + // but referring to a different handle (i.e. has already been reused) by + // the time Wait() returns. The handle in question is automatically + // removed from the WaitSet. + void Wait(base::WaitableEvent** ready_event, + size_t* num_ready_handles, + Handle* ready_handles, + MojoResult* ready_results, + MojoHandleSignalsState* signals_states = nullptr); + + private: + class State; + + // Thread-safe state associated with this WaitSet. Used to aggregate + // notifications from watched handles. + scoped_refptr<State> state_; + + DISALLOW_COPY_AND_ASSIGN(WaitSet); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_WAIT_SET_H_ diff --git a/mojo/public/cpp/system/watcher.cc b/mojo/public/cpp/system/watcher.cc index 55dcf40..0c62ba8 100644 --- a/mojo/public/cpp/system/watcher.cc +++ b/mojo/public/cpp/system/watcher.cc @@ -4,114 +4,17 @@ #include "mojo/public/cpp/system/watcher.h" -#include "base/bind.h" -#include "base/location.h" -#include "base/macros.h" -#include "base/trace_event/heap_profiler.h" #include "mojo/public/c/system/functions.h" namespace mojo { -Watcher::Watcher(const tracked_objects::Location& from_here, - scoped_refptr<base::SingleThreadTaskRunner> runner) - : task_runner_(std::move(runner)), - is_default_task_runner_(task_runner_ == - base::ThreadTaskRunnerHandle::Get()), - heap_profiler_tag_(from_here.file_name()), - weak_factory_(this) { - DCHECK(task_runner_->BelongsToCurrentThread()); - weak_self_ = weak_factory_.GetWeakPtr(); -} - -Watcher::~Watcher() { - if(IsWatching()) - Cancel(); -} - -bool Watcher::IsWatching() const { - DCHECK(thread_checker_.CalledOnValidThread()); - return handle_.is_valid(); -} - -MojoResult Watcher::Start(Handle handle, - MojoHandleSignals signals, - const ReadyCallback& callback) { - DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(!IsWatching()); - DCHECK(!callback.is_null()); - - callback_ = callback; - handle_ = handle; - MojoResult result = MojoWatch(handle_.value(), signals, - &Watcher::CallOnHandleReady, - reinterpret_cast<uintptr_t>(this)); - if (result != MOJO_RESULT_OK) { - handle_.set_value(kInvalidHandleValue); - callback_.Reset(); - DCHECK(result == MOJO_RESULT_FAILED_PRECONDITION || - result == MOJO_RESULT_INVALID_ARGUMENT); - return result; - } - - return MOJO_RESULT_OK; -} - -void Watcher::Cancel() { - DCHECK(thread_checker_.CalledOnValidThread()); - - // The watch may have already been cancelled if the handle was closed. - if (!handle_.is_valid()) - return; - - MojoResult result = - MojoCancelWatch(handle_.value(), reinterpret_cast<uintptr_t>(this)); - // |result| may be MOJO_RESULT_INVALID_ARGUMENT if |handle_| has closed, but - // OnHandleReady has not yet been called. - DCHECK(result == MOJO_RESULT_INVALID_ARGUMENT || result == MOJO_RESULT_OK); - handle_.set_value(kInvalidHandleValue); - callback_.Reset(); -} - -void Watcher::OnHandleReady(MojoResult result) { - DCHECK(thread_checker_.CalledOnValidThread()); - - ReadyCallback callback = callback_; - if (result == MOJO_RESULT_CANCELLED) { - handle_.set_value(kInvalidHandleValue); - callback_.Reset(); - } - - // NOTE: It's legal for |callback| to delete |this|. - if (!callback.is_null()) { - TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION event(heap_profiler_tag_); - callback.Run(result); - } -} - -// static -void Watcher::CallOnHandleReady(uintptr_t context, - MojoResult result, - MojoHandleSignalsState signals_state, - MojoWatchNotificationFlags flags) { - // NOTE: It is safe to assume the Watcher still exists because this callback - // will never be run after the Watcher's destructor. - // - // TODO: Maybe we should also expose |signals_state| through the Watcher API. - // Current HandleWatcher users have no need for it, so it's omitted here. - Watcher* watcher = reinterpret_cast<Watcher*>(context); - - if ((flags & MOJO_WATCH_NOTIFICATION_FLAG_FROM_SYSTEM) && - watcher->task_runner_->RunsTasksOnCurrentThread() && - watcher->is_default_task_runner_) { - // System notifications will trigger from the task runner passed to - // mojo::edk::InitIPCSupport(). In Chrome this happens to always be the - // default task runner for the IO thread. - watcher->OnHandleReady(result); - } else { - watcher->task_runner_->PostTask( - FROM_HERE, - base::Bind(&Watcher::OnHandleReady, watcher->weak_self_, result)); - } +MojoResult CreateWatcher(MojoWatcherCallback callback, + ScopedWatcherHandle* watcher_handle) { + MojoHandle handle; + MojoResult rv = MojoCreateWatcher(callback, &handle); + if (rv == MOJO_RESULT_OK) + watcher_handle->reset(WatcherHandle(handle)); + return rv; } } // namespace mojo diff --git a/mojo/public/cpp/system/watcher.h b/mojo/public/cpp/system/watcher.h index 236788b..d0a2578 100644 --- a/mojo/public/cpp/system/watcher.h +++ b/mojo/public/cpp/system/watcher.h @@ -5,120 +5,32 @@ #ifndef MOJO_PUBLIC_CPP_SYSTEM_WATCHER_H_ #define MOJO_PUBLIC_CPP_SYSTEM_WATCHER_H_ -#include "base/callback.h" -#include "base/macros.h" -#include "base/memory/ref_counted.h" -#include "base/memory/weak_ptr.h" -#include "base/single_thread_task_runner.h" -#include "base/threading/thread_checker.h" -#include "base/threading/thread_task_runner_handle.h" #include "mojo/public/c/system/types.h" +#include "mojo/public/c/system/watcher.h" #include "mojo/public/cpp/system/handle.h" #include "mojo/public/cpp/system/system_export.h" namespace mojo { -// A Watcher watches a single Mojo handle for signal state changes. -// -// NOTE: Watchers may only be used on threads which have a running MessageLoop. -class MOJO_CPP_SYSTEM_EXPORT Watcher { +// A strongly-typed representation of a |MojoHandle| for a watcher. +class WatcherHandle : public Handle { public: - // A callback to be called any time a watched handle changes state in some - // interesting way. The |result| argument indicates one of the following - // conditions depending on its value: - // - // |MOJO_RESULT_OK|: One or more of the signals being watched is satisfied. - // - // |MOJO_RESULT_FAILED_PRECONDITION|: None of the signals being watched can - // ever be satisfied again. - // - // |MOJO_RESULT_CANCELLED|: The handle has been closed and the watch has - // been cancelled implicitly. - using ReadyCallback = base::Callback<void(MojoResult result)>; + WatcherHandle() = default; + explicit WatcherHandle(MojoHandle value) : Handle(value) {} - Watcher(const tracked_objects::Location& from_here, - scoped_refptr<base::SingleThreadTaskRunner> runner = - base::ThreadTaskRunnerHandle::Get()); - - // NOTE: This destructor automatically calls |Cancel()| if the Watcher is - // still active. - ~Watcher(); - - // Indicates if the Watcher is currently watching a handle. - bool IsWatching() const; - - // Starts watching |handle|. A Watcher may only watch one handle at a time, - // but it is safe to call this more than once as long as the previous watch - // has been cancelled (i.e. |is_watching()| returns |false|.) - // - // If no signals in |signals| can ever be satisfied for |handle|, this returns - // |MOJO_RESULT_FAILED_PRECONDITION|. - // - // If |handle| is not a valid watchable (message or data pipe) handle, this - // returns |MOJO_RESULT_INVALID_ARGUMENT|. - // - // Otherwise |MOJO_RESULT_OK| is returned and the handle will be watched until - // closure or cancellation. - // - // Once the watch is started, |callback| may be called at any time on the - // current thread until |Cancel()| is called or the handle is closed. - // - // Destroying the Watcher implicitly calls |Cancel()|. - MojoResult Start(Handle handle, - MojoHandleSignals signals, - const ReadyCallback& callback); - - // Cancels the current watch. Once this returns, the callback previously - // passed to |Start()| will never be called again for this Watcher. - void Cancel(); - - Handle handle() const { return handle_; } - ReadyCallback ready_callback() const { return callback_; } - - // Sets the tag used by the heap profiler. - // |tag| must be a const string literal. - void set_heap_profiler_tag(const char* heap_profiler_tag) { - heap_profiler_tag_ = heap_profiler_tag; - } - - private: - void OnHandleReady(MojoResult result); - - static void CallOnHandleReady(uintptr_t context, - MojoResult result, - MojoHandleSignalsState signals_state, - MojoWatchNotificationFlags flags); - - base::ThreadChecker thread_checker_; - - // The TaskRunner of this Watcher's owning thread. This field is safe to - // access from any thread. - const scoped_refptr<base::SingleThreadTaskRunner> task_runner_; - // Whether |task_runner_| is the same as base::ThreadTaskRunnerHandle::Get() - // for the thread. - const bool is_default_task_runner_; - - // A persistent weak reference to this Watcher which can be passed to the - // Dispatcher any time this object should be signalled. Safe to access (but - // not to dereference!) from any thread. - base::WeakPtr<Watcher> weak_self_; - - // Fields below must only be accessed on the Watcher's owning thread. - - // The handle currently under watch. Not owned. - Handle handle_; - - // The callback to call when the handle is signaled. - ReadyCallback callback_; + // Copying and assignment allowed. +}; - // Tag used to ID memory allocations that originated from notifications in - // this watcher. - const char* heap_profiler_tag_ = nullptr; +static_assert(sizeof(WatcherHandle) == sizeof(Handle), + "Bad size for C++ WatcherHandle"); - base::WeakPtrFactory<Watcher> weak_factory_; +typedef ScopedHandleBase<WatcherHandle> ScopedWatcherHandle; +static_assert(sizeof(ScopedWatcherHandle) == sizeof(WatcherHandle), + "Bad size for C++ ScopedWatcherHandle"); - DISALLOW_COPY_AND_ASSIGN(Watcher); -}; +MOJO_CPP_SYSTEM_EXPORT MojoResult +CreateWatcher(MojoWatcherCallback callback, + ScopedWatcherHandle* watcher_handle); } // namespace mojo diff --git a/mojo/public/cpp/test_support/lib/test_utils.cc b/mojo/public/cpp/test_support/lib/test_utils.cc index 92288c4..7fe6f02 100644 --- a/mojo/public/cpp/test_support/lib/test_utils.cc +++ b/mojo/public/cpp/test_support/lib/test_utils.cc @@ -8,6 +8,7 @@ #include <stdint.h> #include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/wait.h" #include "mojo/public/cpp/test_support/test_support.h" namespace mojo { @@ -41,8 +42,7 @@ bool ReadTextMessage(const MessagePipeHandle& handle, std::string* text) { assert(false); // Looping endlessly!? return false; } - rv = Wait(handle, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE, - nullptr); + rv = Wait(handle, MOJO_HANDLE_SIGNAL_READABLE); if (rv != MOJO_RESULT_OK) return false; did_wait = true; diff --git a/mojo/public/interfaces/bindings/BUILD.gn b/mojo/public/interfaces/bindings/BUILD.gn index eab75c1..c2cadcd 100644 --- a/mojo/public/interfaces/bindings/BUILD.gn +++ b/mojo/public/interfaces/bindings/BUILD.gn @@ -15,3 +15,15 @@ mojom("bindings") { export_define = "MOJO_CPP_BINDINGS_IMPLEMENTATION" export_header = "mojo/public/cpp/bindings/bindings_export.h" } + +# TODO(yzshen): Remove this target and use the one above once +# |use_new_js_bindings| becomes true by default. +mojom("new_bindings") { + visibility = [] + sources = [ + "new_bindings/interface_control_messages.mojom", + "new_bindings/pipe_control_messages.mojom", + ] + + use_new_js_bindings = true +} diff --git a/mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom b/mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom new file mode 100644 index 0000000..e03ffd6 --- /dev/null +++ b/mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom @@ -0,0 +1,67 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[JavaPackage="org.chromium.mojo.bindings.interfacecontrol"] +module mojo.interface_control2; + +// For each user-defined interface, some control functions are provided by the +// interface endpoints at both sides. + +//////////////////////////////////////////////////////////////////////////////// +// Run@0xFFFFFFFF(RunInput input) => (RunOutput? output); +// +// This control function runs the input command. If the command is not +// supported, |output| is set to null; otherwise |output| stores the result, +// whose type depends on the input. + +const uint32 kRunMessageId = 0xFFFFFFFF; + +struct RunMessageParams { + RunInput input; +}; +union RunInput { + QueryVersion query_version; + FlushForTesting flush_for_testing; +}; + +struct RunResponseMessageParams { + RunOutput? output; +}; +union RunOutput { + QueryVersionResult query_version_result; +}; + +// Queries the max supported version of the user-defined interface. +// Sent by the interface client side. +struct QueryVersion { +}; +struct QueryVersionResult { + uint32 version; +}; + +// Sent by either side of the interface. +struct FlushForTesting { +}; + +//////////////////////////////////////////////////////////////////////////////// +// RunOrClosePipe@0xFFFFFFFE(RunOrClosePipeInput input); +// +// This control function runs the input command. If the operation fails or the +// command is not supported, the message pipe is closed. + +const uint32 kRunOrClosePipeMessageId = 0xFFFFFFFE; + +struct RunOrClosePipeMessageParams { + RunOrClosePipeInput input; +}; +union RunOrClosePipeInput { + RequireVersion require_version; +}; + +// If the specified version of the user-defined interface is not supported, the +// function fails and the pipe is closed. +// Sent by the interface client side. +struct RequireVersion { + uint32 version; +}; diff --git a/mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom b/mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom new file mode 100644 index 0000000..69975fc --- /dev/null +++ b/mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom @@ -0,0 +1,46 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[JavaPackage="org.chromium.mojo.bindings.pipecontrol"] +module mojo.pipe_control2; + +// For each message pipe running user-defined interfaces, some control +// functions are provided and used by the routers at both ends of the pipe, so +// that they can coordinate to manage interface endpoints. +// All these control messages will have the interface ID field in the message +// header set to invalid. + +//////////////////////////////////////////////////////////////////////////////// +// RunOrClosePipe@0xFFFFFFFE(RunOrClosePipeInput input); +// +// This control function runs the input command. If the operation fails or the +// command is not supported, the message pipe is closed. + +const uint32 kRunOrClosePipeMessageId = 0xFFFFFFFE; + +struct RunOrClosePipeMessageParams { + RunOrClosePipeInput input; +}; + +union RunOrClosePipeInput { + PeerAssociatedEndpointClosedEvent peer_associated_endpoint_closed_event; +}; + +// A user-defined reason about why the interface is disconnected. +struct DisconnectReason { + uint32 custom_reason; + string description; +}; + +// An event to notify that an interface endpoint set up at the message sender +// side has been closed. +// +// This event is omitted if the endpoint belongs to the master interface and +// there is no disconnect reason specified. +struct PeerAssociatedEndpointClosedEvent { + // The interface ID. + uint32 id; + DisconnectReason? disconnect_reason; +}; + diff --git a/mojo/public/interfaces/bindings/tests/BUILD.gn b/mojo/public/interfaces/bindings/tests/BUILD.gn index 9b10db0..e496eb6 100644 --- a/mojo/public/interfaces/bindings/tests/BUILD.gn +++ b/mojo/public/interfaces/bindings/tests/BUILD.gn @@ -23,9 +23,11 @@ mojom("test_interfaces") { "test_native_types.mojom", "test_structs.mojom", "test_sync_methods.mojom", + "test_unions.mojom", "validation_test_interfaces.mojom", ] public_deps = [ + ":echo", ":test_mojom_import", ":test_mojom_import2", ] @@ -84,6 +86,31 @@ mojom("test_exported_import") { } } +# Used to test that it is okay to call mojom::Foo::Serialize()/Deserialize() +# even if the mojom target is linked into another component. +# +# We don't use |test_export_component| for this test because +# //mojo/public/cpp/bindings/tests depends on both |test_export_component| and +# |test_exported_import| and therefore actually get the shared cpp sources of +# test_export.mojom from |test_exported_import|. +component("test_export_component2") { + testonly = true + public_deps = [ + ":test_export2", + ] +} + +mojom("test_export2") { + testonly = true + sources = [ + "test_export2.mojom", + ] + export_class_attribute = "MOJO_TEST_EXPORT" + export_define = "MOJO_TEST_IMPLEMENTATION=1" + export_header = "mojo/public/cpp/bindings/tests/mojo_test_export.h" + visibility = [ ":test_export_component2" ] +} + mojom("test_mojom_import") { testonly = true sources = [ @@ -123,13 +150,6 @@ mojom("test_struct_traits_interfaces") { ] } -mojom("test_interfaces_experimental") { - testonly = true - sources = [ - "test_unions.mojom", - ] -} - mojom("test_associated_interfaces") { # These files are not included in the test_interfaces target because # associated interfaces are not supported by all bindings languages yet. @@ -173,3 +193,12 @@ mojom("test_no_sources") { ":test_interfaces", ] } + +mojom("echo") { + testonly = true + sources = [ + "echo.mojom", + "echo_import.mojom", + ] + use_new_js_bindings = true +} diff --git a/mojo/public/interfaces/bindings/tests/echo.mojom b/mojo/public/interfaces/bindings/tests/echo.mojom new file mode 100644 index 0000000..56c6063 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/echo.mojom @@ -0,0 +1,12 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module test.echo.mojom; + +import "echo_import.mojom"; + +interface Echo { + EchoPoint(test.echo_import.mojom.Point point) + => (test.echo_import.mojom.Point result); +}; diff --git a/mojo/public/interfaces/bindings/tests/echo_import.mojom b/mojo/public/interfaces/bindings/tests/echo_import.mojom new file mode 100644 index 0000000..a024ce2 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/echo_import.mojom @@ -0,0 +1,10 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module test.echo_import.mojom; + +struct Point { + int32 x; + int32 y; +}; diff --git a/mojo/public/interfaces/bindings/tests/test_export2.mojom b/mojo/public/interfaces/bindings/tests/test_export2.mojom new file mode 100644 index 0000000..011395c --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/test_export2.mojom @@ -0,0 +1,10 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo.test.test_export2; + +struct StringPair { + string s1; + string s2; +}; diff --git a/mojo/public/java/bindings/README.md b/mojo/public/java/bindings/README.md new file mode 100644 index 0000000..821a230 --- /dev/null +++ b/mojo/public/java/bindings/README.md @@ -0,0 +1,12 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo Java Bindings API +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview + +This document provides a brief guide to API usage with example code snippets. +For a detailed API references please consult the class definitions in +[this directory](https://cs.chromium.org/chromium/src/mojo/public/java/bindings/src/org/chromium/mojo/bindings/) + +TODO: Make the contents of this document less non-existent. diff --git a/mojo/public/java/system/README.md b/mojo/public/java/system/README.md new file mode 100644 index 0000000..3213e4c --- /dev/null +++ b/mojo/public/java/system/README.md @@ -0,0 +1,25 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo Java System API +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview + +This document provides a brief guide to Java Mojo bindings usage with example +code snippets. + +For a detailed API references please consult the class definitions in +[this directory](https://cs.chromium.org/chromium/src/mojo/public/java/system/src/org/chromium/mojo/system/). + +*TODO: Make the contents of this document less non-existent.* + +## Message Pipes + +## Data Pipes + +## Shared Buffers + +## Native Platform Handles (File Descriptors, Windows Handles, *etc.*) + +## Watchers + diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Core.java b/mojo/public/java/system/src/org/chromium/mojo/system/Core.java index e5c6d08..40e4be3 100644 --- a/mojo/public/java/system/src/org/chromium/mojo/system/Core.java +++ b/mojo/public/java/system/src/org/chromium/mojo/system/Core.java @@ -4,8 +4,6 @@ package org.chromium.mojo.system; -import java.util.List; - /** * Core mojo interface giving access to the base operations. See |src/mojo/public/c/system/core.h| * for the underlying api. @@ -129,142 +127,6 @@ public interface Core { } /** - * Result for the |wait| method. - */ - public static class WaitResult { - /** - * The result of the wait method. - * <p> - * |MojoResult.OK| if some signal in |signals| was satisfied (or is already satisfied). - * <p> - * |MojoResult.DEADLINE_EXCEEDED| if the deadline has passed without any of the signals - * being satisfied. - * <p> - * |MojoResult.CANCELLED| if |handle| is closed concurrently by another thread. - * <p> - * |MojoResult.FAILED_PRECONDITION| if it is or becomes impossible that any flag in - * |signals| will ever be satisfied (for example, if the other endpoint is closed). - */ - private int mMojoResult; - - /** - * The signaling state of handles. - */ - private HandleSignalsState mHandleSignalsState; - - /** - * Returns the mojoResult. - */ - public int getMojoResult() { - return mMojoResult; - } - - /** - * @param mojoResult the mojoResult to set - */ - public void setMojoResult(int mojoResult) { - mMojoResult = mojoResult; - } - - /** - * Returns the handleSignalsState. - */ - public HandleSignalsState getHandleSignalsState() { - return mHandleSignalsState; - } - - /** - * @param handleSignalsState the handleSignalsState to set - */ - public void setHandleSignalsState(HandleSignalsState handleSignalsState) { - mHandleSignalsState = handleSignalsState; - } - } - - /** - * Waits on the given |handle| until the state indicated by |signals| is satisfied or until - * |deadline| has passed. - * - * @return a |WaitResult|. - */ - public WaitResult wait(Handle handle, HandleSignals signals, long deadline); - - /** - * Result for the |waitMany| method. - */ - public static class WaitManyResult { - - /** - * See |wait| for the different possible values. - */ - private int mMojoResult; - - /** - * If |mojoResult| is |MojoResult.OK|, |handleIndex| is the index of the handle for which - * some flag was satisfied (or is already satisfied). If |mojoResult| is - * |MojoResult.CANCELLED| or |MojoResult.FAILED_PRECONDITION|, |handleIndex| is the index of - * the handle for which the issue occurred. - */ - private int mHandleIndex; - - /** - * The signaling state of handles. Will not be set if |mojoResult| is - * |MOJO_RESULT_INVALID_ARGUMENT| or |MOJO_RESULT_RESOURCE_EXHAUSTED| - */ - private List<HandleSignalsState> mSignalStates; - - /** - * Returns the mojoResult. - */ - public int getMojoResult() { - return mMojoResult; - } - - /** - * @param mojoResult the mojoResult to set - */ - public void setMojoResult(int mojoResult) { - mMojoResult = mojoResult; - } - - /** - * Returns the handleIndex. - */ - public int getHandleIndex() { - return mHandleIndex; - } - - /** - * @param handleIndex the handleIndex to set - */ - public void setHandleIndex(int handleIndex) { - mHandleIndex = handleIndex; - } - - /** - * Returns the signalStates. - */ - public List<HandleSignalsState> getSignalStates() { - return mSignalStates; - } - - /** - * @param signalStates the signalStates to set - */ - public void setSignalStates(List<HandleSignalsState> signalStates) { - mSignalStates = signalStates; - } - } - - /** - * Waits on handle in |handles| for at least one of them to satisfy the associated - * |HandleSignals|, or until |deadline| has passed. - * - * @returns a |WaitManyResult|. - */ - public WaitManyResult waitMany(List<Pair<Handle, HandleSignals>> handles, long deadline); - - /** * Creates a message pipe, which is a bidirectional communication channel for framed data (i.e., * messages), with the given options. Messages can contain plain data and/or Mojo handles. * diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java b/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java index 6181669..903f36d 100644 --- a/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java +++ b/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java @@ -4,7 +4,7 @@ package org.chromium.mojo.system; -import org.chromium.mojo.system.Core.WaitResult; +import org.chromium.mojo.system.Core.HandleSignalsState; import java.io.Closeable; @@ -25,9 +25,9 @@ public interface Handle extends Closeable { public void close(); /** - * @see Core#wait(Handle, Core.HandleSignals, long) + * @return the last known signaling state of the handle. */ - public WaitResult wait(Core.HandleSignals signals, long deadline); + public HandleSignalsState querySignalsState(); /** * @return whether the handle is valid. A handle is valid until it has been explicitly closed or diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java index 9c20fdd..f8b99c6 100644 --- a/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java +++ b/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java @@ -4,8 +4,7 @@ package org.chromium.mojo.system; -import org.chromium.mojo.system.Core.HandleSignals; -import org.chromium.mojo.system.Core.WaitResult; +import org.chromium.mojo.system.Core.HandleSignalsState; import org.chromium.mojo.system.DataPipe.ConsumerHandle; import org.chromium.mojo.system.DataPipe.ProducerHandle; @@ -38,10 +37,10 @@ public class InvalidHandle implements UntypedHandle, MessagePipeHandle, Consumer } /** - * @see Handle#wait(Core.HandleSignals, long) + * @see Handle#querySignalsState() */ @Override - public WaitResult wait(HandleSignals signals, long deadline) { + public HandleSignalsState querySignalsState() { throw new MojoException(MojoResult.INVALID_ARGUMENT); } diff --git a/mojo/public/js/BUILD.gn b/mojo/public/js/BUILD.gn index 0fae4b4..5ed57a1 100644 --- a/mojo/public/js/BUILD.gn +++ b/mojo/public/js/BUILD.gn @@ -14,6 +14,7 @@ source_set("js") { group("bindings") { data = [ "$interfaces_bindings_gen_dir/interface_control_messages.mojom.js", + "$interfaces_bindings_gen_dir/pipe_control_messages.mojom.js", "bindings.js", "buffer.js", "codec.js", @@ -22,6 +23,10 @@ group("bindings") { "interface_types.js", "lib/control_message_handler.js", "lib/control_message_proxy.js", + "lib/interface_endpoint_client.js", + "lib/interface_endpoint_handle.js", + "lib/pipe_control_message_handler.js", + "lib/pipe_control_message_proxy.js", "router.js", "support.js", "threading.js", @@ -30,22 +35,54 @@ group("bindings") { ] deps = [ + ":new_bindings", "//mojo/public/interfaces/bindings:bindings__generator", ] } +action("new_bindings") { + new_bindings_js_files = [ + # This must be the first file in the list, because it initializes global + # variable |mojoBindings| that the others need to refer to. + "new_bindings/base.js", + + "$interfaces_bindings_gen_dir/new_bindings/interface_control_messages.mojom.js", + "new_bindings/bindings.js", + "new_bindings/buffer.js", + "new_bindings/codec.js", + "new_bindings/connector.js", + "new_bindings/interface_types.js", + "new_bindings/lib/control_message_handler.js", + "new_bindings/lib/control_message_proxy.js", + "new_bindings/router.js", + "new_bindings/unicode.js", + "new_bindings/validator.js", + ] + compiled_file = "$target_gen_dir/mojo_bindings.js" + + # TODO(yzshen): Eventually we would like to use Closure Compiler to minify the + # bindings instead of simply concatenating the files. + script = "//v8/tools/concatenate-files.py" + + sources = new_bindings_js_files + outputs = [ + compiled_file, + ] + + args = rebase_path(new_bindings_js_files) + args += [ rebase_path(compiled_file) ] + + deps = [ + "//mojo/public/interfaces/bindings:new_bindings__generator", + ] +} + group("tests") { testonly = true data = [ "//mojo/public/interfaces/bindings/tests/data/validation/", - "tests/codec_unittest.js", - "tests/connection_unittest.js", "tests/core_unittest.js", - "tests/interface_ptr_unittest.js", - "tests/sample_service_unittest.js", - "tests/struct_unittest.js", - "tests/union_unittest.js", "tests/validation_test_input_parser.js", "tests/validation_unittest.js", ] diff --git a/mojo/public/js/README.md b/mojo/public/js/README.md new file mode 100644 index 0000000..b6eafe9 --- /dev/null +++ b/mojo/public/js/README.md @@ -0,0 +1,7 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo JavaScript System and Bindings APIs +This document is a subset of the [Mojo documentation](/mojo). + +**NOTE:** The JavaScript APIs are currently in flux and will stabilize soon. +More info forthcoming ASAP! + +TODO: Make the contents of this document less non-existent. diff --git a/mojo/public/js/bindings.js b/mojo/public/js/bindings.js index f3e40d2..a944e2f 100644 --- a/mojo/public/js/bindings.js +++ b/mojo/public/js/bindings.js @@ -4,10 +4,12 @@ define("mojo/public/js/bindings", [ "mojo/public/js/core", - "mojo/public/js/lib/control_message_proxy", "mojo/public/js/interface_types", + "mojo/public/js/lib/interface_endpoint_client", "mojo/public/js/router", -], function(core, controlMessageProxy, types, router) { +], function(core, types, interfaceEndpointClient, router) { + + var InterfaceEndpointClient = interfaceEndpointClient.InterfaceEndpointClient; // --------------------------------------------------------------------------- @@ -27,12 +29,13 @@ define("mojo/public/js/bindings", [ this.interfaceType_ = interfaceType; this.router_ = null; + this.interfaceEndpointClient_ = null; this.proxy_ = null; - // |router_| is lazily initialized. |handle_| is valid between bind() and - // the initialization of |router_|. + // |router_| and |interfaceEndpointClient_| are lazily initialized. + // |handle_| is valid between bind() and + // the initialization of |router_| and |interfaceEndpointClient_|. this.handle_ = null; - this.controlMessageProxy_ = null; if (ptrInfoOrHandle) this.bind(ptrInfoOrHandle); @@ -57,6 +60,10 @@ define("mojo/public/js/bindings", [ // immediately. InterfacePtrController.prototype.reset = function() { this.version = 0; + if (this.interfaceEndpointClient_) { + this.interfaceEndpointClient_.close(); + this.interfaceEndpointClient_ = null; + } if (this.router_) { this.router_.close(); this.router_ = null; @@ -69,13 +76,20 @@ define("mojo/public/js/bindings", [ } }; - InterfacePtrController.prototype.setConnectionErrorHandler - = function(callback) { + InterfacePtrController.prototype.resetWithReason = function(reason) { + this.configureProxyIfNecessary_(); + this.interfaceEndpointClient_.close(reason); + this.interfaceEndpointClient_ = null; + this.reset(); + }; + + InterfacePtrController.prototype.setConnectionErrorHandler = function( + callback) { if (!this.isBound()) throw new Error("Cannot set connection error handler if not bound."); this.configureProxyIfNecessary_(); - this.router_.setErrorHandler(callback); + this.interfaceEndpointClient_.setConnectionErrorHandler(callback); }; InterfacePtrController.prototype.passInterface = function() { @@ -100,9 +114,9 @@ define("mojo/public/js/bindings", [ return this.proxy_; }; - InterfacePtrController.prototype.enableTestingMode = function() { + InterfacePtrController.prototype.waitForNextMessageForTesting = function() { this.configureProxyIfNecessary_(); - return this.router_.enableTestingMode(); + this.router_.waitForNextMessageForTesting(); }; InterfacePtrController.prototype.configureProxyIfNecessary_ = function() { @@ -111,12 +125,15 @@ define("mojo/public/js/bindings", [ this.router_ = new router.Router(this.handle_); this.handle_ = null; - this.router_ .setPayloadValidators([this.interfaceType_.validateResponse]); - this.controlMessageProxy_ = new - controlMessageProxy.ControlMessageProxy(this.router_); + this.interfaceEndpointClient_ = new InterfaceEndpointClient( + this.router_.createLocalEndpointHandle(types.kMasterInterfaceId), + this.router_); - this.proxy_ = new this.interfaceType_.proxyClass(this.router_); + this.interfaceEndpointClient_ .setPayloadValidators([ + this.interfaceType_.validateResponse]); + this.proxy_ = new this.interfaceType_.proxyClass( + this.interfaceEndpointClient_); }; InterfacePtrController.prototype.queryVersion = function() { @@ -126,7 +143,7 @@ define("mojo/public/js/bindings", [ } this.configureProxyIfNecessary_(); - return this.controlMessageProxy_.queryVersion().then( + return this.interfaceEndpointClient_.queryVersion().then( onQueryVersion.bind(this)); }; @@ -137,7 +154,7 @@ define("mojo/public/js/bindings", [ return; } this.version = version; - this.controlMessageProxy_.requireVersion(version); + this.interfaceEndpointClient_.requireVersion(version); }; // --------------------------------------------------------------------------- @@ -159,6 +176,7 @@ define("mojo/public/js/bindings", [ this.interfaceType_ = interfaceType; this.impl_ = impl; this.router_ = null; + this.interfaceEndpointClient_ = null; this.stub_ = null; if (requestOrHandle) @@ -174,7 +192,7 @@ define("mojo/public/js/bindings", [ // TODO(yzshen): Set the version of the interface pointer. this.bind(makeRequest(ptr)); return ptr; - } + }; Binding.prototype.bind = function(requestOrHandle) { this.close(); @@ -184,26 +202,45 @@ define("mojo/public/js/bindings", [ if (!core.isHandle(handle)) return; + this.router_ = new router.Router(handle); + this.stub_ = new this.interfaceType_.stubClass(this.impl_); - this.router_ = new router.Router(handle, this.interfaceType_.kVersion); - this.router_.setIncomingReceiver(this.stub_); - this.router_ .setPayloadValidators([this.interfaceType_.validateRequest]); + this.interfaceEndpointClient_ = new InterfaceEndpointClient( + this.router_.createLocalEndpointHandle(types.kMasterInterfaceId), + this.router_, this.interfaceType_.kVersion); + this.interfaceEndpointClient_.setIncomingReceiver(this.stub_); + this.interfaceEndpointClient_ .setPayloadValidators([ + this.interfaceType_.validateRequest]); }; Binding.prototype.close = function() { if (!this.isBound()) return; + if (this.interfaceEndpointClient_) { + this.interfaceEndpointClient_.close(); + this.interfaceEndpointClient_ = null; + } + this.router_.close(); this.router_ = null; this.stub_ = null; }; + Binding.prototype.closeWithReason = function(reason) { + if (this.interfaceEndpointClient_) { + this.interfaceEndpointClient_.close(reason); + this.interfaceEndpointClient_ = null; + } + this.close(); + }; + Binding.prototype.setConnectionErrorHandler = function(callback) { - if (!this.isBound()) + if (!this.isBound()) { throw new Error("Cannot set connection error handler if not bound."); - this.router_.setErrorHandler(callback); + } + this.interfaceEndpointClient_.setConnectionErrorHandler(callback); }; Binding.prototype.unbind = function() { @@ -216,8 +253,8 @@ define("mojo/public/js/bindings", [ return result; }; - Binding.prototype.enableTestingMode = function() { - return this.router_.enableTestingMode(); + Binding.prototype.waitForNextMessageForTesting = function() { + this.router_.waitForNextMessageForTesting(); }; // --------------------------------------------------------------------------- diff --git a/mojo/public/js/codec.js b/mojo/public/js/codec.js index ff5d31a..ce58a8c 100644 --- a/mojo/public/js/codec.js +++ b/mojo/public/js/codec.js @@ -453,6 +453,10 @@ define("mojo/public/js/codec", [ return this.buffer.getUint32(kMessageFlagsOffset); }; + Message.prototype.getInterfaceId = function() { + return this.buffer.getUint32(kMessageInterfaceIdOffset); + }; + Message.prototype.isResponse = function() { return (this.getFlags() & kMessageIsResponse) != 0; }; @@ -466,6 +470,10 @@ define("mojo/public/js/codec", [ this.buffer.setUint64(kMessageRequestIDOffset, requestID); }; + Message.prototype.setInterfaceId = function(interfaceId) { + this.buffer.setUint32(kMessageInterfaceIdOffset, interfaceId); + }; + // MessageBuilder ----------------------------------------------------------- @@ -537,10 +545,6 @@ define("mojo/public/js/codec", [ this.payloadSize = message.buffer.byteLength - messageHeaderSize; var version = this.decoder.readUint32(); var interface_id = this.decoder.readUint32(); - if (interface_id != 0) { - throw new Error("Receiving non-zero interface ID. Associated interfaces " + - "are not yet supported."); - } this.messageName = this.decoder.readUint32(); this.flags = this.decoder.readUint32(); // Skip the padding. diff --git a/mojo/public/js/connector.js b/mojo/public/js/connector.js index ee16be8..012e3c7 100644 --- a/mojo/public/js/connector.js +++ b/mojo/public/js/connector.js @@ -78,13 +78,8 @@ define("mojo/public/js/connector", [ this.errorHandler_ = handler; }; - Connector.prototype.encounteredError = function() { - return this.error_; - }; - Connector.prototype.waitForNextMessageForTesting = function() { - var wait = core.wait(this.handle_, core.HANDLE_SIGNAL_READABLE, - core.DEADLINE_INDEFINITE); + var wait = core.wait(this.handle_, core.HANDLE_SIGNAL_READABLE); this.readMore_(wait.result); }; @@ -97,9 +92,12 @@ define("mojo/public/js/connector", [ if (read.result == core.RESULT_SHOULD_WAIT) return; if (read.result != core.RESULT_OK) { + // TODO(wangjimmy): Add a handleError method to swap the handle to be + // closed with a dummy handle in the case when + // read.result != MOJO_RESULT_FAILED_PRECONDITION this.error_ = true; if (this.errorHandler_) - this.errorHandler_.onError(read.result); + this.errorHandler_.onError(); return; } var messageBuffer = new buffer.Buffer(read.buffer); diff --git a/mojo/public/js/constants.cc b/mojo/public/js/constants.cc index 58cf274..a0ce7d4 100644 --- a/mojo/public/js/constants.cc +++ b/mojo/public/js/constants.cc @@ -16,7 +16,17 @@ const char kControlMessageProxyModuleName[] = "mojo/public/js/lib/control_message_proxy"; const char kInterfaceControlMessagesMojom[] = "mojo/public/interfaces/bindings/interface_control_messages.mojom"; +const char kInterfaceEndpointClientModuleName[] = + "mojo/public/js/lib/interface_endpoint_client"; +const char kInterfaceEndpointHandleModuleName[] = + "mojo/public/js/lib/interface_endpoint_handle"; const char kInterfaceTypesModuleName[] = "mojo/public/js/interface_types"; +const char kPipeControlMessageHandlerModuleName[] = + "mojo/public/js/lib/pipe_control_message_handler"; +const char kPipeControlMessageProxyModuleName[] = + "mojo/public/js/lib/pipe_control_message_proxy"; +const char kPipeControlMessagesMojom[] = + "mojo/public/interfaces/bindings/pipe_control_messages.mojom"; const char kRouterModuleName[] = "mojo/public/js/router"; const char kUnicodeModuleName[] = "mojo/public/js/unicode"; const char kValidatorModuleName[] = "mojo/public/js/validator"; diff --git a/mojo/public/js/constants.h b/mojo/public/js/constants.h index 9d32d20..f561d73 100644 --- a/mojo/public/js/constants.h +++ b/mojo/public/js/constants.h @@ -15,7 +15,12 @@ extern const char kConnectorModuleName[]; extern const char kControlMessageHandlerModuleName[]; extern const char kControlMessageProxyModuleName[]; extern const char kInterfaceControlMessagesMojom[]; +extern const char kInterfaceEndpointClientModuleName[]; +extern const char kInterfaceEndpointHandleModuleName[]; extern const char kInterfaceTypesModuleName[]; +extern const char kPipeControlMessageHandlerModuleName[]; +extern const char kPipeControlMessageProxyModuleName[]; +extern const char kPipeControlMessagesMojom[]; extern const char kRouterModuleName[]; extern const char kUnicodeModuleName[]; extern const char kValidatorModuleName[]; diff --git a/mojo/public/js/core.js b/mojo/public/js/core.js index ef480ee..b2c4ee2 100644 --- a/mojo/public/js/core.js +++ b/mojo/public/js/core.js @@ -41,12 +41,6 @@ var RESULT_BUSY; var RESULT_SHOULD_WAIT; /** - * MojoDeadline {number}: Used to specify deadlines (timeouts), in microseconds. - * See core.h for more information. - */ -var DEADLINE_INDEFINITE; - -/** * MojoHandleSignals: Used to specify signals that can be waited on for a handle *(and which can be triggered), e.g., the ability to read or write to * the handle. @@ -142,29 +136,26 @@ var MAP_BUFFER_FLAG_NONE; function close(handle) { [native code] } /** - * Waits on the given handle until a signal indicated by |signals| is - * satisfied or until |deadline| is passed. See MojoWait for more information. + * Queries the last known signaling state of |handle|. * - * @param {MojoHandle} handle Handle to wait on. - * @param {MojoHandleSignals} signals Specifies the condition to wait for. - * @param {MojoDeadline} deadline Stops waiting if this is reached. - * @return {MojoResult} Result code. + * @param {MojoHandle} handle Handle to query. + * @return {object} An object of the form { + * result, // MOJO_RESULT_OK or MOJO_RESULT_INVALID_ARGUMENT + * satisfiedSignals, // MojoHandleSignals (see above) + * satisfiableSignals, // MojoHandleSignals + * } */ -function wait(handle, signals, deadline) { [native code] } +function queryHandleSignalsState(handle) { [native code] } /** - * Waits on |handles[0]|, ..., |handles[handles.length-1]| for at least one of - * them to satisfy the state indicated by |flags[0]|, ..., - * |flags[handles.length-1]|, respectively, or until |deadline| has passed. - * See MojoWaitMany for more information. + * Waits on the given handle until a signal indicated by |signals| is + * satisfied or an error occurs. * - * @param {Array.MojoHandle} handles Handles to wait on. - * @param {Array.MojoHandleSignals} signals Specifies the condition to wait for, - * for each corresponding handle. Must be the same length as |handles|. - * @param {MojoDeadline} deadline Stops waiting if this is reached. + * @param {MojoHandle} handle Handle to wait on. + * @param {MojoHandleSignals} signals Specifies the condition to wait for. * @return {MojoResult} Result code. */ -function waitMany(handles, signals, deadline) { [native code] } +function wait(handle, signals) { [native code] } /** * Creates a message pipe. This function always succeeds. diff --git a/mojo/public/js/interface_types.js b/mojo/public/js/interface_types.js index 01ea2d1..e8ed37a 100644 --- a/mojo/public/js/interface_types.js +++ b/mojo/public/js/interface_types.js @@ -6,6 +6,11 @@ define("mojo/public/js/interface_types", [ "mojo/public/js/core", ], function(core) { + // Constants ---------------------------------------------------------------- + var kInterfaceIdNamespaceMask = 0x80000000; + var kMasterInterfaceId = 0x00000000; + var kInvalidInterfaceId = 0xFFFFFFFF; + // --------------------------------------------------------------------------- function InterfacePtrInfo(handle, version) { @@ -44,9 +49,22 @@ define("mojo/public/js/interface_types", [ this.handle = null; }; + function isMasterInterfaceId(interfaceId) { + return interfaceId === kMasterInterfaceId; + } + + function isValidInterfaceId(interfaceId) { + return interfaceId !== kInvalidInterfaceId; + } + var exports = {}; exports.InterfacePtrInfo = InterfacePtrInfo; exports.InterfaceRequest = InterfaceRequest; + exports.isMasterInterfaceId = isMasterInterfaceId; + exports.isValidInterfaceId = isValidInterfaceId; + exports.kInvalidInterfaceId = kInvalidInterfaceId; + exports.kMasterInterfaceId = kMasterInterfaceId; + exports.kInterfaceIdNamespaceMask = kInterfaceIdNamespaceMask; return exports; }); diff --git a/mojo/public/js/lib/control_message_handler.js b/mojo/public/js/lib/control_message_handler.js index 81d9002..5da306e 100644 --- a/mojo/public/js/lib/control_message_handler.js +++ b/mojo/public/js/lib/control_message_handler.js @@ -3,10 +3,10 @@ // found in the LICENSE file. define("mojo/public/js/lib/control_message_handler", [ - "mojo/public/js/codec", "mojo/public/interfaces/bindings/interface_control_messages.mojom", + "mojo/public/js/codec", "mojo/public/js/validator", -], function(codec, controlMessages, validator) { +], function(controlMessages, codec, validator) { var Validator = validator.Validator; @@ -89,18 +89,18 @@ define("mojo/public/js/lib/control_message_handler", [ } function ControlMessageHandler(interface_version) { - this.interface_version = interface_version; + this.interface_version_ = interface_version; } ControlMessageHandler.prototype.accept = function(message) { validateControlRequestWithoutResponse(message); - return runOrClosePipe(message, this.interface_version); + return runOrClosePipe(message, this.interface_version_); }; ControlMessageHandler.prototype.acceptWithResponder = function(message, responder) { validateControlRequestWithResponse(message); - return run(message, responder, this.interface_version); + return run(message, responder, this.interface_version_); }; var exports = {}; diff --git a/mojo/public/js/lib/control_message_proxy.js b/mojo/public/js/lib/control_message_proxy.js index d6c0734..b6f1d3c 100644 --- a/mojo/public/js/lib/control_message_proxy.js +++ b/mojo/public/js/lib/control_message_proxy.js @@ -10,14 +10,18 @@ define("mojo/public/js/lib/control_message_proxy", [ var Validator = validator.Validator; - function sendRunOrClosePipeMessage(receiver, runOrClosePipeMessageParams) { + function constructRunOrClosePipeMessage(runOrClosePipeInput) { + var runOrClosePipeMessageParams = new + controlMessages.RunOrClosePipeMessageParams(); + runOrClosePipeMessageParams.input = runOrClosePipeInput; + var messageName = controlMessages.kRunOrClosePipeMessageId; var payloadSize = controlMessages.RunOrClosePipeMessageParams.encodedSize; var builder = new codec.MessageBuilder(messageName, payloadSize); builder.encodeStruct(controlMessages.RunOrClosePipeMessageParams, runOrClosePipeMessageParams); var message = builder.finish(); - receiver.accept(message); + return message; } function validateControlResponse(message) { @@ -71,7 +75,7 @@ define("mojo/public/js/lib/control_message_proxy", [ } function ControlMessageProxy(receiver) { - this.receiver = receiver; + this.receiver_ = receiver; } ControlMessageProxy.prototype.queryVersion = function() { @@ -79,20 +83,18 @@ define("mojo/public/js/lib/control_message_proxy", [ runMessageParams.input = new controlMessages.RunInput(); runMessageParams.input.query_version = new controlMessages.QueryVersion(); - return sendRunMessage(this.receiver, runMessageParams).then(function( + return sendRunMessage(this.receiver_, runMessageParams).then(function( runResponseMessageParams) { return runResponseMessageParams.output.query_version_result.version; }); }; ControlMessageProxy.prototype.requireVersion = function(version) { - var runOrClosePipeMessageParams = new - controlMessages.RunOrClosePipeMessageParams(); - runOrClosePipeMessageParams.input = new - controlMessages.RunOrClosePipeInput(); - runOrClosePipeMessageParams.input.require_version = new - controlMessages.RequireVersion({'version': version}); - sendRunOrClosePipeMessage(this.receiver, runOrClosePipeMessageParams); + var runOrClosePipeInput = new controlMessages.RunOrClosePipeInput(); + runOrClosePipeInput.require_version = new controlMessages.RequireVersion({ + 'version': version}); + var message = constructRunOrClosePipeMessage(runOrClosePipeInput); + this.receiver_.accept(message); }; var exports = {}; diff --git a/mojo/public/js/lib/interface_endpoint_client.js b/mojo/public/js/lib/interface_endpoint_client.js new file mode 100644 index 0000000..631c52e --- /dev/null +++ b/mojo/public/js/lib/interface_endpoint_client.js @@ -0,0 +1,232 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +define("mojo/public/js/lib/interface_endpoint_client", [ + "console", + "mojo/public/js/codec", + "mojo/public/js/lib/control_message_handler", + "mojo/public/js/lib/control_message_proxy", + "mojo/public/js/lib/interface_endpoint_handle", + "mojo/public/js/validator", + "timer", +], function(console, + codec, + controlMessageHandler, + controlMessageProxy, + interfaceEndpointHandle, + validator, + timer) { + + var ControlMessageHandler = controlMessageHandler.ControlMessageHandler; + var ControlMessageProxy = controlMessageProxy.ControlMessageProxy; + var MessageReader = codec.MessageReader; + var Validator = validator.Validator; + var InterfaceEndpointHandle = interfaceEndpointHandle.InterfaceEndpointHandle; + + function InterfaceEndpointClient(interfaceEndpointHandle, receiver, + interfaceVersion) { + this.controller_ = null; + this.encounteredError_ = false; + this.handle_ = interfaceEndpointHandle; + this.incomingReceiver_ = receiver; + + if (interfaceVersion !== undefined) { + this.controlMessageHandler_ = new ControlMessageHandler( + interfaceVersion); + } else { + this.controlMessageProxy_ = new ControlMessageProxy(this); + } + + this.nextRequestID_ = 0; + this.completers_ = new Map(); + this.payloadValidators_ = []; + this.connectionErrorHandler_ = null; + + if (interfaceEndpointHandle.pendingAssociation()) { + interfaceEndpointHandle.setAssociationEventHandler( + this.onAssociationEvent.bind(this)); + } else { + this.initControllerIfNecessary_(); + } + } + + InterfaceEndpointClient.prototype.initControllerIfNecessary_ = function() { + if (this.controller_ || this.handle_.pendingAssociation()) { + return; + } + + this.controller_ = this.handle_.groupController().attachEndpointClient( + this.handle_, this); + }; + + InterfaceEndpointClient.prototype.onAssociationEvent = function( + associationEvent) { + if (associationEvent === + InterfaceEndpointHandle.AssociationEvent.ASSOCIATED) { + this.initControllerIfNecessary_(); + } else if (associationEvent === + InterfaceEndpointHandle.AssociationEvent + .PEER_CLOSED_BEFORE_ASSOCIATION) { + timer.createOneShot(0, this.notifyError.bind(this, + this.handle_.disconnectReason())); + } + }; + + InterfaceEndpointClient.prototype.passHandle = function() { + if (!this.handle_.isValid()) { + return new InterfaceEndpointHandle(); + } + + // Used to clear the previously set callback. + this.handle_.setAssociationEventHandler(undefined); + + if (this.controller_) { + this.controller_ = null; + this.handle_.groupController().detachEndpointClient(this.handle_); + } + var handle = this.handle_; + this.handle_ = null; + return handle; + }; + + InterfaceEndpointClient.prototype.close = function(reason) { + var handle = this.passHandle(); + handle.reset(reason); + }; + + InterfaceEndpointClient.prototype.accept = function(message) { + if (this.encounteredError_) { + return false; + } + + this.initControllerIfNecessary_(); + return this.controller_.sendMessage(message); + }; + + InterfaceEndpointClient.prototype.acceptAndExpectResponse = function( + message) { + if (this.encounteredError_) { + return Promise.reject(); + } + + this.initControllerIfNecessary_(); + + // Reserve 0 in case we want it to convey special meaning in the future. + var requestID = this.nextRequestID_++; + if (requestID === 0) + requestID = this.nextRequestID_++; + + message.setRequestID(requestID); + var result = this.controller_.sendMessage(message); + if (!result) + return Promise.reject(Error("Connection error")); + + var completer = {}; + this.completers_.set(requestID, completer); + return new Promise(function(resolve, reject) { + completer.resolve = resolve; + completer.reject = reject; + }); + }; + + InterfaceEndpointClient.prototype.setPayloadValidators = function( + payloadValidators) { + this.payloadValidators_ = payloadValidators; + }; + + InterfaceEndpointClient.prototype.setIncomingReceiver = function(receiver) { + this.incomingReceiver_ = receiver; + }; + + InterfaceEndpointClient.prototype.setConnectionErrorHandler = function( + handler) { + this.connectionErrorHandler_ = handler; + }; + + InterfaceEndpointClient.prototype.handleIncomingMessage_ = function( + message) { + var noError = validator.validationError.NONE; + var messageValidator = new Validator(message); + var err = noError; + for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i) + err = this.payloadValidators_[i](messageValidator); + + if (err == noError) { + return this.handleValidIncomingMessage_(message); + } else { + validator.reportValidationError(err); + return false; + } + }; + + InterfaceEndpointClient.prototype.handleValidIncomingMessage_ = function( + message) { + if (validator.isTestingMode()) { + return true; + } + + if (this.encounteredError_) { + return false; + } + + var ok = false; + + if (message.expectsResponse()) { + if (controlMessageHandler.isControlMessage(message) && + this.controlMessageHandler_) { + ok = this.controlMessageHandler_.acceptWithResponder(message, this); + } else if (this.incomingReceiver_) { + ok = this.incomingReceiver_.acceptWithResponder(message, this); + } + } else if (message.isResponse()) { + var reader = new MessageReader(message); + var requestID = reader.requestID; + var completer = this.completers_.get(requestID); + if (completer) { + this.completers_.delete(requestID); + completer.resolve(message); + ok = true; + } else { + console.log("Unexpected response with request ID: " + requestID); + } + } else { + if (controlMessageHandler.isControlMessage(message) && + this.controlMessageHandler_) { + ok = this.controlMessageHandler_.accept(message); + } else if (this.incomingReceiver_) { + ok = this.incomingReceiver_.accept(message); + } + } + return ok; + }; + + InterfaceEndpointClient.prototype.notifyError = function(reason) { + if (this.encounteredError_) { + return; + } + this.encounteredError_ = true; + + this.completers_.forEach(function(value) { + value.reject(); + }); + this.completers_.clear(); // Drop any responders. + + if (this.connectionErrorHandler_) { + this.connectionErrorHandler_(reason); + } + }; + + InterfaceEndpointClient.prototype.queryVersion = function() { + return this.controlMessageProxy_.queryVersion(); + }; + + InterfaceEndpointClient.prototype.requireVersion = function(version) { + this.controlMessageProxy_.requireVersion(version); + }; + + var exports = {}; + exports.InterfaceEndpointClient = InterfaceEndpointClient; + + return exports; +}); diff --git a/mojo/public/js/lib/interface_endpoint_handle.js b/mojo/public/js/lib/interface_endpoint_handle.js new file mode 100644 index 0000000..f48b89b --- /dev/null +++ b/mojo/public/js/lib/interface_endpoint_handle.js @@ -0,0 +1,158 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +define("mojo/public/js/lib/interface_endpoint_handle", [ + "mojo/public/js/interface_types", + "timer", +], function(types, timer) { + + var AssociationEvent = { + // The interface has been associated with a message pipe. + ASSOCIATED: 'associated', + // The peer of this object has been closed before association. + PEER_CLOSED_BEFORE_ASSOCIATION: 'peer_closed_before_association' + }; + + function State(interfaceId, associatedGroupController) { + if (interfaceId === undefined) { + interfaceId = types.kInvalidInterfaceId; + } + + this.interfaceId = interfaceId; + this.associatedGroupController = associatedGroupController; + this.pendingAssociation = false; + this.disconnectReason = null; + this.peerState_ = null; + this.associationEventHandler_ = null; + } + + State.prototype.initPendingState = function(peer) { + this.pendingAssociation = true; + this.peerState_ = peer; + }; + + State.prototype.isValid = function() { + return this.pendingAssociation || + types.isValidInterfaceId(this.interfaceId); + }; + + State.prototype.close = function(disconnectReason) { + var cachedGroupController; + var cachedPeerState; + var cachedId = types.kInvalidInterfaceId; + + if (!this.pendingAssociation) { + if (types.isValidInterfaceId(this.interfaceId)) { + cachedGroupController = this.associatedGroupController; + this.associatedGroupController = null; + cachedId = this.interfaceId; + this.interfaceId = types.kInvalidInterfaceId; + } + } else { + this.pendingAssociation = false; + cachedPeerState = this.peerState_; + this.peerState_ = null; + } + + if (cachedGroupController) { + cachedGroupController.closeEndpointHandle(cachedId, + disconnectReason); + } else if (cachedPeerState) { + cachedPeerState.onPeerClosedBeforeAssociation(disconnectReason); + } + }; + + State.prototype.runAssociationEventHandler = function(associationEvent) { + if (this.associationEventHandler_) { + var handler = this.associationEventHandler_; + this.associationEventHandler_ = null; + handler(associationEvent); + } + }; + + State.prototype.setAssociationEventHandler = function(handler) { + if (!this.pendingAssociation && + !types.isValidInterfaceId(this.interfaceId)) { + return; + } + + if (!handler) { + this.associationEventHandler_ = null; + return; + } + + this.associationEventHandler_ = handler; + if (!this.pendingAssociation) { + timer.createOneShot(0, this.runAssociationEventHandler.bind(this, + AssociationEvent.ASSOCIATED)); + } else if (!this.peerState_) { + timer.createOneShot(0, this.runAssociationEventHandler.bind(this, + AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION)); + } + }; + + State.prototype.onAssociated = function(interfaceId, + associatedGroupController) { + if (!this.pendingAssociation) { + return; + } + + this.pendingAssociation = false; + this.peerState_ = null; + this.interfaceId = interfaceId; + this.associatedGroupController = associatedGroupController; + this.runAssociationEventHandler(AssociationEvent.ASSOCIATED); + }; + + State.prototype.onPeerClosedBeforeAssociation = function(disconnectReason) { + if (!this.pendingAssociation) { + return; + } + + this.peerState_ = null; + this.disconnectReason = disconnectReason; + + this.runAssociationEventHandler( + AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION); + }; + + function InterfaceEndpointHandle(interfaceId, associatedGroupController) { + this.state_ = new State(interfaceId, associatedGroupController); + } + + InterfaceEndpointHandle.prototype.isValid = function() { + return this.state_.isValid(); + }; + + InterfaceEndpointHandle.prototype.pendingAssociation = function() { + return this.state_.pendingAssociation; + }; + + InterfaceEndpointHandle.prototype.id = function() { + return this.state_.interfaceId; + }; + + InterfaceEndpointHandle.prototype.groupController = function() { + return this.state_.associatedGroupController; + }; + + InterfaceEndpointHandle.prototype.disconnectReason = function() { + return this.state_.disconnectReason; + }; + + InterfaceEndpointHandle.prototype.setAssociationEventHandler = function( + handler) { + this.state_.setAssociationEventHandler(handler); + }; + + InterfaceEndpointHandle.prototype.reset = function(reason) { + this.state_.close(reason); + this.state_ = new State(); + }; + + var exports = {}; + exports.InterfaceEndpointHandle = InterfaceEndpointHandle; + + return exports; +}); diff --git a/mojo/public/js/lib/pipe_control_message_handler.js b/mojo/public/js/lib/pipe_control_message_handler.js new file mode 100644 index 0000000..2eb45d1 --- /dev/null +++ b/mojo/public/js/lib/pipe_control_message_handler.js @@ -0,0 +1,61 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +define("mojo/public/js/lib/pipe_control_message_handler", [ + "mojo/public/interfaces/bindings/pipe_control_messages.mojom", + "mojo/public/js/codec", + "mojo/public/js/interface_types", + "mojo/public/js/validator", +], function(pipeControlMessages, codec, types, validator) { + + var Validator = validator.Validator; + + function validateControlRequestWithoutResponse(message) { + var messageValidator = new Validator(message); + var error = messageValidator.validateMessageIsRequestWithoutResponse(); + if (error != validator.validationError.NONE) { + throw error; + } + + if (message.getName() != pipeControlMessages.kRunOrClosePipeMessageId) { + throw new Error("Control message name is not kRunOrClosePipeMessageId"); + } + + // Validate payload. + error = pipeControlMessages.RunOrClosePipeMessageParams.validate( + messageValidator, message.getHeaderNumBytes()); + if (error != validator.validationError.NONE) { + throw error; + } + } + + function runOrClosePipe(message, delegate) { + var reader = new codec.MessageReader(message); + var runOrClosePipeMessageParams = reader.decodeStruct( + pipeControlMessages.RunOrClosePipeMessageParams); + var event = runOrClosePipeMessageParams.input + .peer_associated_endpoint_closed_event; + return delegate.onPeerAssociatedEndpointClosed(event.id, + event.disconnect_reason); + } + + function isPipeControlMessage(message) { + return !types.isValidInterfaceId(message.getInterfaceId()); + } + + function PipeControlMessageHandler(delegate) { + this.delegate_ = delegate; + } + + PipeControlMessageHandler.prototype.accept = function(message) { + validateControlRequestWithoutResponse(message); + return runOrClosePipe(message, this.delegate_); + }; + + var exports = {}; + exports.PipeControlMessageHandler = PipeControlMessageHandler; + exports.isPipeControlMessage = isPipeControlMessage; + + return exports; +}); diff --git a/mojo/public/js/lib/pipe_control_message_proxy.js b/mojo/public/js/lib/pipe_control_message_proxy.js new file mode 100644 index 0000000..4b8e7a2 --- /dev/null +++ b/mojo/public/js/lib/pipe_control_message_proxy.js @@ -0,0 +1,56 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +define("mojo/public/js/lib/pipe_control_message_proxy", [ + "mojo/public/interfaces/bindings/pipe_control_messages.mojom", + "mojo/public/js/codec", + "mojo/public/js/interface_types", +], function(pipeControlMessages, codec, types) { + + function constructRunOrClosePipeMessage(runOrClosePipeInput) { + var runOrClosePipeMessageParams = new + pipeControlMessages.RunOrClosePipeMessageParams(); + runOrClosePipeMessageParams.input = runOrClosePipeInput; + + var messageName = pipeControlMessages.kRunOrClosePipeMessageId; + var payloadSize = + pipeControlMessages.RunOrClosePipeMessageParams.encodedSize; + + var builder = new codec.MessageBuilder(messageName, payloadSize); + builder.encodeStruct(pipeControlMessages.RunOrClosePipeMessageParams, + runOrClosePipeMessageParams); + var message = builder.finish(); + message.setInterfaceId(types.kInvalidInterfaceId); + return message; + } + + function PipeControlMessageProxy(receiver) { + this.receiver_ = receiver; + } + + PipeControlMessageProxy.prototype.notifyPeerEndpointClosed = function( + interfaceId, reason) { + var message = this.constructPeerEndpointClosedMessage(interfaceId, reason); + this.receiver_.accept(message); + }; + + PipeControlMessageProxy.prototype.constructPeerEndpointClosedMessage = + function(interfaceId, reason) { + var event = new pipeControlMessages.PeerAssociatedEndpointClosedEvent(); + event.id = interfaceId; + if (reason) { + event.disconnect_reason = new pipeControlMessages.DisconnectReason({ + custom_reason: reason.custom_reason, + description: reason.description}); + } + var runOrClosePipeInput = new pipeControlMessages.RunOrClosePipeInput(); + runOrClosePipeInput.peer_associated_endpoint_closed_event = event; + return constructRunOrClosePipeMessage(runOrClosePipeInput); + }; + + var exports = {}; + exports.PipeControlMessageProxy = PipeControlMessageProxy; + + return exports; +}); diff --git a/mojo/public/js/new_bindings/base.js b/mojo/public/js/new_bindings/base.js new file mode 100644 index 0000000..db72d48 --- /dev/null +++ b/mojo/public/js/new_bindings/base.js @@ -0,0 +1,111 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +if (mojo && mojo.internal) { + throw new Error('The Mojo bindings library has been initialized.'); +} + +var mojo = mojo || {}; +mojo.internal = {}; +mojo.internal.global = this; +mojo.config = { + // Whether to automatically load mojom dependencies. + // For example, if foo.mojom imports bar.mojom, |autoLoadMojomDeps| set to + // true means that loading foo.mojom.js will insert a <script> tag to load + // bar.mojom.js, if it hasn't been loaded. + // + // The URL of bar.mojom.js is determined by the relative path of bar.mojom + // (relative to the position of foo.mojom at build time) and the URL of + // foo.mojom.js. For exmple, if at build time the two mojom files are + // located at: + // a/b/c/foo.mojom + // a/b/d/bar.mojom + // and the URL of foo.mojom.js is: + // http://example.org/scripts/b/c/foo.mojom.js + // then the URL of bar.mojom.js will be: + // http://example.org/scripts/b/d/bar.mojom.js + // + // If you would like bar.mojom.js to live at a different location, you need + // to turn off |autoLoadMojomDeps| before loading foo.mojom.js, and manually + // load bar.mojom.js yourself. Similarly, you need to turn off the option if + // you merge bar.mojom.js and foo.mojom.js into a single file. + // + // Performance tip: Avoid loading the same mojom.js file multiple times. + // Assume that |autoLoadMojomDeps| is set to true: + // <!-- No duplicate loading; recommended. --> + // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script> + // + // <!-- No duplicate loading, although unnecessary. --> + // <script src="http://example.org/scripts/b/d/bar.mojom.js"></script> + // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script> + // + // <!-- Load bar.mojom.js twice; should be avoided. --> + // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script> + // <script src="http://example.org/scripts/b/d/bar.mojom.js"></script> + autoLoadMojomDeps: true +}; + +(function() { + var internal = mojo.internal; + + var LoadState = { + PENDING_LOAD: 1, + LOADED: 2 + }; + + var mojomRegistry = new Map(); + + function exposeNamespace(namespace) { + var current = internal.global; + var parts = namespace.split('.'); + + for (var part; parts.length && (part = parts.shift());) { + if (!current[part]) { + current[part] = {}; + } + current = current[part]; + } + + return current; + } + + function isMojomPendingLoad(id) { + return mojomRegistry.get(id) === LoadState.PENDING_LOAD; + } + + function isMojomLoaded(id) { + return mojomRegistry.get(id) === LoadState.LOADED; + } + + function markMojomPendingLoad(id) { + if (isMojomLoaded(id)) { + throw new Error('The following mojom file has been loaded: ' + id); + } + + mojomRegistry.set(id, LoadState.PENDING_LOAD); + } + + function markMojomLoaded(id) { + mojomRegistry.set(id, LoadState.LOADED); + } + + function loadMojomIfNecessary(id, url) { + if (mojomRegistry.has(id)) { + return; + } + + markMojomPendingLoad(id); + internal.global.document.write('<script type="text/javascript" src="' + + url + '"></script>'); + } + + internal.exposeNamespace = exposeNamespace; + internal.isMojomPendingLoad = isMojomPendingLoad; + internal.isMojomLoaded = isMojomLoaded; + internal.markMojomPendingLoad = markMojomPendingLoad; + internal.markMojomLoaded = markMojomLoaded; + internal.loadMojomIfNecessary = loadMojomIfNecessary; +})(); diff --git a/mojo/public/js/new_bindings/bindings.js b/mojo/public/js/new_bindings/bindings.js index f3e40d2..5b3b66e 100644 --- a/mojo/public/js/new_bindings/bindings.js +++ b/mojo/public/js/new_bindings/bindings.js @@ -2,19 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -define("mojo/public/js/bindings", [ - "mojo/public/js/core", - "mojo/public/js/lib/control_message_proxy", - "mojo/public/js/interface_types", - "mojo/public/js/router", -], function(core, controlMessageProxy, types, router) { - +(function() { + var internal = mojo.internal; // --------------------------------------------------------------------------- function makeRequest(interfacePtr) { - var pipe = core.createMessagePipe(); - interfacePtr.ptr.bind(new types.InterfacePtrInfo(pipe.handle0, 0)); - return new types.InterfaceRequest(pipe.handle1); + var pipe = Mojo.createMessagePipe(); + interfacePtr.ptr.bind(new mojo.InterfacePtrInfo(pipe.handle0, 0)); + return new mojo.InterfaceRequest(pipe.handle1); } // --------------------------------------------------------------------------- @@ -41,7 +36,7 @@ define("mojo/public/js/bindings", [ InterfacePtrController.prototype.bind = function(ptrInfoOrHandle) { this.reset(); - if (ptrInfoOrHandle instanceof types.InterfacePtrInfo) { + if (ptrInfoOrHandle instanceof mojo.InterfacePtrInfo) { this.version = ptrInfoOrHandle.version; this.handle_ = ptrInfoOrHandle.handle; } else { @@ -64,7 +59,7 @@ define("mojo/public/js/bindings", [ this.proxy_ = null; } if (this.handle_) { - core.close(this.handle_); + this.handle_.close(); this.handle_ = null; } }; @@ -82,12 +77,12 @@ define("mojo/public/js/bindings", [ var result; if (this.router_) { // TODO(yzshen): Fix Router interface to support extracting handle. - result = new types.InterfacePtrInfo( + result = new mojo.InterfacePtrInfo( this.router_.connector_.handle_, this.version); this.router_.connector_.handle_ = null; } else { // This also handles the case when this object is not bound. - result = new types.InterfacePtrInfo(this.handle_, this.version); + result = new mojo.InterfacePtrInfo(this.handle_, this.version); this.handle_ = null; } @@ -109,12 +104,11 @@ define("mojo/public/js/bindings", [ if (!this.handle_) return; - this.router_ = new router.Router(this.handle_); + this.router_ = new internal.Router(this.handle_); this.handle_ = null; this.router_ .setPayloadValidators([this.interfaceType_.validateResponse]); - this.controlMessageProxy_ = new - controlMessageProxy.ControlMessageProxy(this.router_); + this.controlMessageProxy_ = new internal.ControlMessageProxy(this.router_); this.proxy_ = new this.interfaceType_.proxyClass(this.router_); }; @@ -179,13 +173,13 @@ define("mojo/public/js/bindings", [ Binding.prototype.bind = function(requestOrHandle) { this.close(); - var handle = requestOrHandle instanceof types.InterfaceRequest ? + var handle = requestOrHandle instanceof mojo.InterfaceRequest ? requestOrHandle.handle : requestOrHandle; - if (!core.isHandle(handle)) + if (!(handle instanceof MojoHandle)) return; this.stub_ = new this.interfaceType_.stubClass(this.impl_); - this.router_ = new router.Router(handle, this.interfaceType_.kVersion); + this.router_ = new internal.Router(handle, this.interfaceType_.kVersion); this.router_.setIncomingReceiver(this.stub_); this.router_ .setPayloadValidators([this.interfaceType_.validateRequest]); }; @@ -208,9 +202,9 @@ define("mojo/public/js/bindings", [ Binding.prototype.unbind = function() { if (!this.isBound()) - return new types.InterfaceRequest(null); + return new mojo.InterfaceRequest(null); - var result = new types.InterfaceRequest(this.router_.connector_.handle_); + var result = new mojo.InterfaceRequest(this.router_.connector_.handle_); this.router_.connector_.handle_ = null; this.close(); return result; @@ -273,13 +267,9 @@ define("mojo/public/js/bindings", [ this.errorHandler_(); }; - var exports = {}; - exports.InterfacePtrInfo = types.InterfacePtrInfo; - exports.InterfaceRequest = types.InterfaceRequest; - exports.makeRequest = makeRequest; - exports.InterfacePtrController = InterfacePtrController; - exports.Binding = Binding; - exports.BindingSet = BindingSet; - return exports; -}); + mojo.makeRequest = makeRequest; + mojo.Binding = Binding; + mojo.BindingSet = BindingSet; + mojo.InterfacePtrController = InterfacePtrController; +})(); diff --git a/mojo/public/js/new_bindings/buffer.js b/mojo/public/js/new_bindings/buffer.js index e35f695..c44058b 100644 --- a/mojo/public/js/new_bindings/buffer.js +++ b/mojo/public/js/new_bindings/buffer.js @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -define("mojo/public/js/buffer", function() { +(function() { + var internal = mojo.internal; var kHostIsLittleEndian = (function () { var endianArrayBuffer = new ArrayBuffer(2); @@ -150,7 +151,5 @@ define("mojo/public/js/buffer", function() { this.dataView.setFloat64(offset, value, kHostIsLittleEndian); } - var exports = {}; - exports.Buffer = Buffer; - return exports; -}); + internal.Buffer = Buffer; +})(); diff --git a/mojo/public/js/new_bindings/codec.js b/mojo/public/js/new_bindings/codec.js index ff5d31a..339fc16 100644 --- a/mojo/public/js/new_bindings/codec.js +++ b/mojo/public/js/new_bindings/codec.js @@ -2,11 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -define("mojo/public/js/codec", [ - "mojo/public/js/buffer", - "mojo/public/js/interface_types", - "mojo/public/js/unicode", -], function(buffer, types, unicode) { +(function() { + var internal = mojo.internal; var kErrorUnsigned = "Passing negative value to unsigned"; var kErrorArray = "Passing non Array for array type"; @@ -138,7 +135,7 @@ define("mojo/public/js/codec", [ var numberOfElements = this.readUint32(); var base = this.next; this.next += numberOfElements; - return unicode.decodeUtf8String( + return internal.decodeUtf8String( new Uint8Array(this.buffer.arrayBuffer, base, numberOfElements)); }; @@ -314,7 +311,7 @@ define("mojo/public/js/codec", [ Encoder.prototype.encodeString = function(val) { var base = this.next + kArrayHeaderSize; - var numberOfElements = unicode.encodeUtf8String( + var numberOfElements = internal.encodeUtf8String( val, new Uint8Array(this.buffer.arrayBuffer, base)); var numberOfBytes = kArrayHeaderSize + numberOfElements; this.writeUint32(numberOfBytes); @@ -389,7 +386,7 @@ define("mojo/public/js/codec", [ if (typeof(val) !== "string") { throw new Error(kErrorString); } - var encodedSize = kArrayHeaderSize + unicode.utf8Length(val); + var encodedSize = kArrayHeaderSize + internal.utf8Length(val); var encoder = this.createAndEncodeEncoder(encodedSize); encoder.encodeString(val); }; @@ -473,7 +470,7 @@ define("mojo/public/js/codec", [ // Currently, we don't compute the payload size correctly ahead of time. // Instead, we resize the buffer at the end. var numberOfBytes = kMessageHeaderSize + payloadSize; - this.buffer = new buffer.Buffer(numberOfBytes); + this.buffer = new internal.Buffer(numberOfBytes); this.handles = []; var encoder = this.createEncoder(kMessageHeaderSize); encoder.writeUint32(kMessageHeaderSize); @@ -511,7 +508,7 @@ define("mojo/public/js/codec", [ // Currently, we don't compute the payload size correctly ahead of time. // Instead, we resize the buffer at the end. var numberOfBytes = kMessageWithRequestIDHeaderSize + payloadSize; - this.buffer = new buffer.Buffer(numberOfBytes); + this.buffer = new internal.Buffer(numberOfBytes); this.handles = []; var encoder = this.createEncoder(kMessageWithRequestIDHeaderSize); encoder.writeUint32(kMessageWithRequestIDHeaderSize); @@ -814,7 +811,7 @@ define("mojo/public/js/codec", [ Interface.prototype.encodedSize = 8; Interface.prototype.decode = function(decoder) { - var interfacePtrInfo = new types.InterfacePtrInfo( + var interfacePtrInfo = new mojo.InterfacePtrInfo( decoder.decodeHandle(), decoder.readUint32()); var interfacePtr = new this.cls(); interfacePtr.ptr.bind(interfacePtrInfo); @@ -823,7 +820,7 @@ define("mojo/public/js/codec", [ Interface.prototype.encode = function(encoder, val) { var interfacePtrInfo = - val ? val.ptr.passInterface() : new types.InterfacePtrInfo(null, 0); + val ? val.ptr.passInterface() : new mojo.InterfacePtrInfo(null, 0); encoder.encodeHandle(interfacePtrInfo.handle); encoder.writeUint32(interfacePtrInfo.version); }; @@ -840,7 +837,7 @@ define("mojo/public/js/codec", [ InterfaceRequest.encodedSize = 4; InterfaceRequest.decode = function(decoder) { - return new types.InterfaceRequest(decoder.decodeHandle()); + return new mojo.InterfaceRequest(decoder.decodeHandle()); }; InterfaceRequest.encode = function(encoder, val) { @@ -877,46 +874,44 @@ define("mojo/public/js/codec", [ NullableMapOf.prototype = Object.create(MapOf.prototype); - var exports = {}; - exports.align = align; - exports.isAligned = isAligned; - exports.Message = Message; - exports.MessageBuilder = MessageBuilder; - exports.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder; - exports.MessageReader = MessageReader; - exports.kArrayHeaderSize = kArrayHeaderSize; - exports.kMapStructPayloadSize = kMapStructPayloadSize; - exports.kStructHeaderSize = kStructHeaderSize; - exports.kEncodedInvalidHandleValue = kEncodedInvalidHandleValue; - exports.kMessageHeaderSize = kMessageHeaderSize; - exports.kMessageWithRequestIDHeaderSize = kMessageWithRequestIDHeaderSize; - exports.kMessageExpectsResponse = kMessageExpectsResponse; - exports.kMessageIsResponse = kMessageIsResponse; - exports.Int8 = Int8; - exports.Uint8 = Uint8; - exports.Int16 = Int16; - exports.Uint16 = Uint16; - exports.Int32 = Int32; - exports.Uint32 = Uint32; - exports.Int64 = Int64; - exports.Uint64 = Uint64; - exports.Float = Float; - exports.Double = Double; - exports.String = String; - exports.Enum = Enum; - exports.NullableString = NullableString; - exports.PointerTo = PointerTo; - exports.NullablePointerTo = NullablePointerTo; - exports.ArrayOf = ArrayOf; - exports.NullableArrayOf = NullableArrayOf; - exports.PackedBool = PackedBool; - exports.Handle = Handle; - exports.NullableHandle = NullableHandle; - exports.Interface = Interface; - exports.NullableInterface = NullableInterface; - exports.InterfaceRequest = InterfaceRequest; - exports.NullableInterfaceRequest = NullableInterfaceRequest; - exports.MapOf = MapOf; - exports.NullableMapOf = NullableMapOf; - return exports; -}); + internal.align = align; + internal.isAligned = isAligned; + internal.Message = Message; + internal.MessageBuilder = MessageBuilder; + internal.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder; + internal.MessageReader = MessageReader; + internal.kArrayHeaderSize = kArrayHeaderSize; + internal.kMapStructPayloadSize = kMapStructPayloadSize; + internal.kStructHeaderSize = kStructHeaderSize; + internal.kEncodedInvalidHandleValue = kEncodedInvalidHandleValue; + internal.kMessageHeaderSize = kMessageHeaderSize; + internal.kMessageWithRequestIDHeaderSize = kMessageWithRequestIDHeaderSize; + internal.kMessageExpectsResponse = kMessageExpectsResponse; + internal.kMessageIsResponse = kMessageIsResponse; + internal.Int8 = Int8; + internal.Uint8 = Uint8; + internal.Int16 = Int16; + internal.Uint16 = Uint16; + internal.Int32 = Int32; + internal.Uint32 = Uint32; + internal.Int64 = Int64; + internal.Uint64 = Uint64; + internal.Float = Float; + internal.Double = Double; + internal.String = String; + internal.Enum = Enum; + internal.NullableString = NullableString; + internal.PointerTo = PointerTo; + internal.NullablePointerTo = NullablePointerTo; + internal.ArrayOf = ArrayOf; + internal.NullableArrayOf = NullableArrayOf; + internal.PackedBool = PackedBool; + internal.Handle = Handle; + internal.NullableHandle = NullableHandle; + internal.Interface = Interface; + internal.NullableInterface = NullableInterface; + internal.InterfaceRequest = InterfaceRequest; + internal.NullableInterfaceRequest = NullableInterfaceRequest; + internal.MapOf = MapOf; + internal.NullableMapOf = NullableMapOf; +})(); diff --git a/mojo/public/js/new_bindings/connector.js b/mojo/public/js/new_bindings/connector.js index ee16be8..7fa4822 100644 --- a/mojo/public/js/new_bindings/connector.js +++ b/mojo/public/js/new_bindings/connector.js @@ -2,15 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -define("mojo/public/js/connector", [ - "mojo/public/js/buffer", - "mojo/public/js/codec", - "mojo/public/js/core", - "mojo/public/js/support", -], function(buffer, codec, core, support) { +(function() { + var internal = mojo.internal; function Connector(handle) { - if (!core.isHandle(handle)) + if (!(handle instanceof MojoHandle)) throw new Error("Connector: not a handle " + handle); this.handle_ = handle; this.dropWrites_ = false; @@ -20,19 +16,18 @@ define("mojo/public/js/connector", [ this.errorHandler_ = null; if (handle) { - this.readWatcher_ = support.watch(handle, - core.HANDLE_SIGNAL_READABLE, - this.readMore_.bind(this)); + this.readWatcher_ = handle.watch({readable: true}, + this.readMore_.bind(this)); } } Connector.prototype.close = function() { if (this.readWatcher_) { - support.cancelWatch(this.readWatcher_); + this.readWatcher_.cancel(); this.readWatcher_ = null; } if (this.handle_ != null) { - core.close(this.handle_); + this.handle_.close(); this.handle_ = null; } }; @@ -44,17 +39,15 @@ define("mojo/public/js/connector", [ if (this.dropWrites_) return true; - var result = core.writeMessage(this.handle_, - new Uint8Array(message.buffer.arrayBuffer), - message.handles, - core.WRITE_MESSAGE_FLAG_NONE); + var result = this.handle_.writeMessage( + new Uint8Array(message.buffer.arrayBuffer), message.handles); switch (result) { - case core.RESULT_OK: + case Mojo.RESULT_OK: // The handles were successfully transferred, so we don't own them // anymore. message.handles = []; break; - case core.RESULT_FAILED_PRECONDITION: + case Mojo.RESULT_FAILED_PRECONDITION: // There's no point in continuing to write to this pipe since the other // end is gone. Avoid writing any future messages. Hide write failures // from the caller since we'd like them to continue consuming any @@ -83,33 +76,29 @@ define("mojo/public/js/connector", [ }; Connector.prototype.waitForNextMessageForTesting = function() { - var wait = core.wait(this.handle_, core.HANDLE_SIGNAL_READABLE, - core.DEADLINE_INDEFINITE); - this.readMore_(wait.result); + // TODO(yzshen): Change the tests that use this method. + throw new Error("Not supported!"); }; Connector.prototype.readMore_ = function(result) { for (;;) { - var read = core.readMessage(this.handle_, - core.READ_MESSAGE_FLAG_NONE); + var read = this.handle_.readMessage(); if (this.handle_ == null) // The connector has been closed. return; - if (read.result == core.RESULT_SHOULD_WAIT) + if (read.result == Mojo.RESULT_SHOULD_WAIT) return; - if (read.result != core.RESULT_OK) { + if (read.result != Mojo.RESULT_OK) { this.error_ = true; if (this.errorHandler_) this.errorHandler_.onError(read.result); return; } - var messageBuffer = new buffer.Buffer(read.buffer); - var message = new codec.Message(messageBuffer, read.handles); + var messageBuffer = new internal.Buffer(read.buffer); + var message = new internal.Message(messageBuffer, read.handles); if (this.incomingReceiver_) this.incomingReceiver_.accept(message); } }; - var exports = {}; - exports.Connector = Connector; - return exports; -}); + internal.Connector = Connector; +})(); diff --git a/mojo/public/js/new_bindings/interface_types.js b/mojo/public/js/new_bindings/interface_types.js index 01ea2d1..c52f6c7 100644 --- a/mojo/public/js/new_bindings/interface_types.js +++ b/mojo/public/js/new_bindings/interface_types.js @@ -2,10 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -define("mojo/public/js/interface_types", [ - "mojo/public/js/core", -], function(core) { - +(function() { // --------------------------------------------------------------------------- function InterfacePtrInfo(handle, version) { @@ -14,14 +11,14 @@ define("mojo/public/js/interface_types", [ } InterfacePtrInfo.prototype.isValid = function() { - return core.isHandle(this.handle); + return this.handle instanceof MojoHandle; }; InterfacePtrInfo.prototype.close = function() { if (!this.isValid()) return; - core.close(this.handle); + this.handle.close(); this.handle = null; this.version = 0; }; @@ -33,20 +30,17 @@ define("mojo/public/js/interface_types", [ } InterfaceRequest.prototype.isValid = function() { - return core.isHandle(this.handle); + return this.handle instanceof MojoHandle; }; InterfaceRequest.prototype.close = function() { if (!this.isValid()) return; - core.close(this.handle); + this.handle.close(); this.handle = null; }; - var exports = {}; - exports.InterfacePtrInfo = InterfacePtrInfo; - exports.InterfaceRequest = InterfaceRequest; - - return exports; -}); + mojo.InterfacePtrInfo = InterfacePtrInfo; + mojo.InterfaceRequest = InterfaceRequest; +})(); diff --git a/mojo/public/js/new_bindings/lib/control_message_handler.js b/mojo/public/js/new_bindings/lib/control_message_handler.js index 81d9002..3f122fb 100644 --- a/mojo/public/js/new_bindings/lib/control_message_handler.js +++ b/mojo/public/js/new_bindings/lib/control_message_handler.js @@ -2,90 +2,88 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -define("mojo/public/js/lib/control_message_handler", [ - "mojo/public/js/codec", - "mojo/public/interfaces/bindings/interface_control_messages.mojom", - "mojo/public/js/validator", -], function(codec, controlMessages, validator) { - - var Validator = validator.Validator; +(function() { + var internal = mojo.internal; function validateControlRequestWithResponse(message) { - var messageValidator = new Validator(message); + var messageValidator = new internal.Validator(message); var error = messageValidator.validateMessageIsRequestExpectingResponse(); - if (error !== validator.validationError.NONE) { + if (error !== internal.validationError.NONE) { throw error; } - if (message.getName() != controlMessages.kRunMessageId) { + if (message.getName() != mojo.interface_control2.kRunMessageId) { throw new Error("Control message name is not kRunMessageId"); } // Validate payload. - error = controlMessages.RunMessageParams.validate(messageValidator, + error = mojo.interface_control2.RunMessageParams.validate(messageValidator, message.getHeaderNumBytes()); - if (error != validator.validationError.NONE) { + if (error != internal.validationError.NONE) { throw error; } } function validateControlRequestWithoutResponse(message) { - var messageValidator = new Validator(message); + var messageValidator = new internal.Validator(message); var error = messageValidator.validateMessageIsRequestWithoutResponse(); - if (error != validator.validationError.NONE) { + if (error != internal.validationError.NONE) { throw error; } - if (message.getName() != controlMessages.kRunOrClosePipeMessageId) { + if (message.getName() != mojo.interface_control2.kRunOrClosePipeMessageId) { throw new Error("Control message name is not kRunOrClosePipeMessageId"); } // Validate payload. - error = controlMessages.RunOrClosePipeMessageParams.validate( + error = mojo.interface_control2.RunOrClosePipeMessageParams.validate( messageValidator, message.getHeaderNumBytes()); - if (error != validator.validationError.NONE) { + if (error != internal.validationError.NONE) { throw error; } } function runOrClosePipe(message, interface_version) { - var reader = new codec.MessageReader(message); + var reader = new internal.MessageReader(message); var runOrClosePipeMessageParams = reader.decodeStruct( - controlMessages.RunOrClosePipeMessageParams); + mojo.interface_control2.RunOrClosePipeMessageParams); return interface_version >= runOrClosePipeMessageParams.input.require_version.version; } function run(message, responder, interface_version) { - var reader = new codec.MessageReader(message); + var reader = new internal.MessageReader(message); var runMessageParams = - reader.decodeStruct(controlMessages.RunMessageParams); + reader.decodeStruct(mojo.interface_control2.RunMessageParams); var runOutput = null; if (runMessageParams.input.query_version) { - runOutput = new controlMessages.RunOutput(); + runOutput = new mojo.interface_control2.RunOutput(); runOutput.query_version_result = new - controlMessages.QueryVersionResult({'version': interface_version}); + mojo.interface_control2.QueryVersionResult( + {'version': interface_version}); } var runResponseMessageParams = new - controlMessages.RunResponseMessageParams(); + mojo.interface_control2.RunResponseMessageParams(); runResponseMessageParams.output = runOutput; - var messageName = controlMessages.kRunMessageId; - var payloadSize = controlMessages.RunResponseMessageParams.encodedSize; + var messageName = mojo.interface_control2.kRunMessageId; + var payloadSize = + mojo.interface_control2.RunResponseMessageParams.encodedSize; var requestID = reader.requestID; - var builder = new codec.MessageWithRequestIDBuilder(messageName, - payloadSize, codec.kMessageIsResponse, requestID); - builder.encodeStruct(controlMessages.RunResponseMessageParams, + var builder = new internal.MessageWithRequestIDBuilder(messageName, + payloadSize, internal.kMessageIsResponse, requestID); + builder.encodeStruct(mojo.interface_control2.RunResponseMessageParams, runResponseMessageParams); responder.accept(builder.finish()); return true; } - function isControlMessage(message) { - return message.getName() == controlMessages.kRunMessageId || - message.getName() == controlMessages.kRunOrClosePipeMessageId; + function isInterfaceControlMessage(message) { + return message.getName() == mojo.interface_control2.kRunMessageId || + message.getName() == + mojo.interface_control2.kRunOrClosePipeMessageId; } function ControlMessageHandler(interface_version) { @@ -103,9 +101,6 @@ define("mojo/public/js/lib/control_message_handler", [ return run(message, responder, this.interface_version); }; - var exports = {}; - exports.ControlMessageHandler = ControlMessageHandler; - exports.isControlMessage = isControlMessage; - - return exports; -}); + internal.ControlMessageHandler = ControlMessageHandler; + internal.isInterfaceControlMessage = isInterfaceControlMessage; +})(); diff --git a/mojo/public/js/new_bindings/lib/control_message_proxy.js b/mojo/public/js/new_bindings/lib/control_message_proxy.js index d6c0734..1d57557 100644 --- a/mojo/public/js/new_bindings/lib/control_message_proxy.js +++ b/mojo/public/js/new_bindings/lib/control_message_proxy.js @@ -2,39 +2,35 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -define("mojo/public/js/lib/control_message_proxy", [ - "mojo/public/interfaces/bindings/interface_control_messages.mojom", - "mojo/public/js/codec", - "mojo/public/js/validator", -], function(controlMessages, codec, validator) { - - var Validator = validator.Validator; +(function() { + var internal = mojo.internal; function sendRunOrClosePipeMessage(receiver, runOrClosePipeMessageParams) { - var messageName = controlMessages.kRunOrClosePipeMessageId; - var payloadSize = controlMessages.RunOrClosePipeMessageParams.encodedSize; - var builder = new codec.MessageBuilder(messageName, payloadSize); - builder.encodeStruct(controlMessages.RunOrClosePipeMessageParams, + var messageName = mojo.interface_control2.kRunOrClosePipeMessageId; + var payloadSize = + mojo.interface_control2.RunOrClosePipeMessageParams.encodedSize; + var builder = new internal.MessageBuilder(messageName, payloadSize); + builder.encodeStruct(mojo.interface_control2.RunOrClosePipeMessageParams, runOrClosePipeMessageParams); var message = builder.finish(); receiver.accept(message); } function validateControlResponse(message) { - var messageValidator = new Validator(message); + var messageValidator = new internal.Validator(message); var error = messageValidator.validateMessageIsResponse(); - if (error != validator.validationError.NONE) { + if (error != internal.validationError.NONE) { throw error; } - if (message.getName() != controlMessages.kRunMessageId) { + if (message.getName() != mojo.interface_control2.kRunMessageId) { throw new Error("Control message name is not kRunMessageId"); } // Validate payload. - error = controlMessages.RunResponseMessageParams.validate( + error = mojo.interface_control2.RunResponseMessageParams.validate( messageValidator, message.getHeaderNumBytes()); - if (error != validator.validationError.NONE) { + if (error != internal.validationError.NONE) { throw error; } } @@ -42,9 +38,9 @@ define("mojo/public/js/lib/control_message_proxy", [ function acceptRunResponse(message) { validateControlResponse(message); - var reader = new codec.MessageReader(message); + var reader = new internal.MessageReader(message); var runResponseMessageParams = reader.decodeStruct( - controlMessages.RunResponseMessageParams); + mojo.interface_control2.RunResponseMessageParams); return Promise.resolve(runResponseMessageParams); } @@ -59,12 +55,13 @@ define("mojo/public/js/lib/control_message_proxy", [ * @return {Promise} that resolves to a RunResponseMessageParams. */ function sendRunMessage(receiver, runMessageParams) { - var messageName = controlMessages.kRunMessageId; - var payloadSize = controlMessages.RunMessageParams.encodedSize; + var messageName = mojo.interface_control2.kRunMessageId; + var payloadSize = mojo.interface_control2.RunMessageParams.encodedSize; // |requestID| is set to 0, but is later properly set by Router. - var builder = new codec.MessageWithRequestIDBuilder(messageName, - payloadSize, codec.kMessageExpectsResponse, 0); - builder.encodeStruct(controlMessages.RunMessageParams, runMessageParams); + var builder = new internal.MessageWithRequestIDBuilder(messageName, + payloadSize, internal.kMessageExpectsResponse, 0); + builder.encodeStruct(mojo.interface_control2.RunMessageParams, + runMessageParams); var message = builder.finish(); return receiver.acceptAndExpectResponse(message).then(acceptRunResponse); @@ -75,9 +72,10 @@ define("mojo/public/js/lib/control_message_proxy", [ } ControlMessageProxy.prototype.queryVersion = function() { - var runMessageParams = new controlMessages.RunMessageParams(); - runMessageParams.input = new controlMessages.RunInput(); - runMessageParams.input.query_version = new controlMessages.QueryVersion(); + var runMessageParams = new mojo.interface_control2.RunMessageParams(); + runMessageParams.input = new mojo.interface_control2.RunInput(); + runMessageParams.input.query_version = + new mojo.interface_control2.QueryVersion(); return sendRunMessage(this.receiver, runMessageParams).then(function( runResponseMessageParams) { @@ -87,16 +85,13 @@ define("mojo/public/js/lib/control_message_proxy", [ ControlMessageProxy.prototype.requireVersion = function(version) { var runOrClosePipeMessageParams = new - controlMessages.RunOrClosePipeMessageParams(); + mojo.interface_control2.RunOrClosePipeMessageParams(); runOrClosePipeMessageParams.input = new - controlMessages.RunOrClosePipeInput(); + mojo.interface_control2.RunOrClosePipeInput(); runOrClosePipeMessageParams.input.require_version = new - controlMessages.RequireVersion({'version': version}); + mojo.interface_control2.RequireVersion({'version': version}); sendRunOrClosePipeMessage(this.receiver, runOrClosePipeMessageParams); }; - var exports = {}; - exports.ControlMessageProxy = ControlMessageProxy; - - return exports; -}); + internal.ControlMessageProxy = ControlMessageProxy; +})(); diff --git a/mojo/public/js/new_bindings/router.js b/mojo/public/js/new_bindings/router.js index e94c5eb..1272407 100644 --- a/mojo/public/js/new_bindings/router.js +++ b/mojo/public/js/new_bindings/router.js @@ -2,25 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -define("mojo/public/js/router", [ - "console", - "mojo/public/js/codec", - "mojo/public/js/core", - "mojo/public/js/connector", - "mojo/public/js/lib/control_message_handler", - "mojo/public/js/validator", -], function(console, codec, core, connector, controlMessageHandler, validator) { - - var Connector = connector.Connector; - var MessageReader = codec.MessageReader; - var Validator = validator.Validator; - var ControlMessageHandler = controlMessageHandler.ControlMessageHandler; +(function() { + var internal = mojo.internal; function Router(handle, interface_version, connectorFactory) { - if (!core.isHandle(handle)) + if (!(handle instanceof MojoHandle)) throw new Error("Router constructor: Not a handle"); if (connectorFactory === undefined) - connectorFactory = Connector; + connectorFactory = internal.Connector; this.connector_ = new connectorFactory(handle); this.incomingReceiver_ = null; this.errorHandler_ = null; @@ -31,7 +20,7 @@ define("mojo/public/js/router", [ if (interface_version !== undefined) { this.controlMessageHandler_ = new - ControlMessageHandler(interface_version); + internal.ControlMessageHandler(interface_version); } this.connector_.setIncomingReceiver({ @@ -97,8 +86,8 @@ define("mojo/public/js/router", [ }; Router.prototype.handleIncomingMessage_ = function(message) { - var noError = validator.validationError.NONE; - var messageValidator = new Validator(message); + var noError = internal.validationError.NONE; + var messageValidator = new internal.Validator(message); var err = messageValidator.validateMessageHeader(); for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i) err = this.payloadValidators_[i](messageValidator); @@ -114,7 +103,7 @@ define("mojo/public/js/router", [ return; if (message.expectsResponse()) { - if (controlMessageHandler.isControlMessage(message)) { + if (internal.isInterfaceControlMessage(message)) { if (this.controlMessageHandler_) { this.controlMessageHandler_.acceptWithResponder(message, this); } else { @@ -128,7 +117,7 @@ define("mojo/public/js/router", [ this.close(); } } else if (message.isResponse()) { - var reader = new MessageReader(message); + var reader = new internal.MessageReader(message); var requestID = reader.requestID; var completer = this.completers_.get(requestID); if (completer) { @@ -138,7 +127,7 @@ define("mojo/public/js/router", [ console.log("Unexpected response with request ID: " + requestID); } } else { - if (controlMessageHandler.isControlMessage(message)) { + if (internal.isInterfaceControlMessage(message)) { if (this.controlMessageHandler_) { var ok = this.controlMessageHandler_.accept(message); if (ok) return; @@ -156,7 +145,7 @@ define("mojo/public/js/router", [ // TODO(yzshen): This should also trigger connection error handler. // Consider making accept() return a boolean and let the connector deal // with this, as the C++ code does. - console.log("Invalid message: " + validator.validationError[error]); + console.log("Invalid message: " + internal.validationError[error]); this.close(); return; @@ -197,7 +186,5 @@ define("mojo/public/js/router", [ this.invalidMessageHandler_(error); }; - var exports = {}; - exports.Router = Router; - return exports; -}); + internal.Router = Router; +})(); diff --git a/mojo/public/js/new_bindings/unicode.js b/mojo/public/js/new_bindings/unicode.js index be2ba0e..6ed8839 100644 --- a/mojo/public/js/new_bindings/unicode.js +++ b/mojo/public/js/new_bindings/unicode.js @@ -7,7 +7,9 @@ * stored in ArrayBuffers. There is much room for optimization in this code if * it proves necessary. */ -define("mojo/public/js/unicode", function() { +(function() { + var internal = mojo.internal; + /** * Decodes the UTF8 string from the given buffer. * @param {ArrayBufferView} buffer The buffer containing UTF8 string data. @@ -43,9 +45,7 @@ define("mojo/public/js/unicode", function() { return utf8String.length; } - var exports = {}; - exports.decodeUtf8String = decodeUtf8String; - exports.encodeUtf8String = encodeUtf8String; - exports.utf8Length = utf8Length; - return exports; -}); + internal.decodeUtf8String = decodeUtf8String; + internal.encodeUtf8String = encodeUtf8String; + internal.utf8Length = utf8Length; +})(); diff --git a/mojo/public/js/new_bindings/validator.js b/mojo/public/js/new_bindings/validator.js index fee742d..610112b 100644 --- a/mojo/public/js/new_bindings/validator.js +++ b/mojo/public/js/new_bindings/validator.js @@ -2,9 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -define("mojo/public/js/validator", [ - "mojo/public/js/codec", -], function(codec) { +(function() { + var internal = mojo.internal; var validationError = { NONE: 'VALIDATION_ERROR_NONE', @@ -30,32 +29,33 @@ define("mojo/public/js/validator", [ var NULL_MOJO_POINTER = "NULL_MOJO_POINTER"; function isEnumClass(cls) { - return cls instanceof codec.Enum; + return cls instanceof internal.Enum; } function isStringClass(cls) { - return cls === codec.String || cls === codec.NullableString; + return cls === internal.String || cls === internal.NullableString; } function isHandleClass(cls) { - return cls === codec.Handle || cls === codec.NullableHandle; + return cls === internal.Handle || cls === internal.NullableHandle; } function isInterfaceClass(cls) { - return cls instanceof codec.Interface; + return cls instanceof internal.Interface; } function isInterfaceRequestClass(cls) { - return cls === codec.InterfaceRequest || - cls === codec.NullableInterfaceRequest; + return cls === internal.InterfaceRequest || + cls === internal.NullableInterfaceRequest; } function isNullable(type) { - return type === codec.NullableString || type === codec.NullableHandle || - type === codec.NullableInterface || - type === codec.NullableInterfaceRequest || - type instanceof codec.NullableArrayOf || - type instanceof codec.NullablePointerTo; + return type === internal.NullableString || + type === internal.NullableHandle || + type === internal.NullableInterface || + type === internal.NullableInterfaceRequest || + type instanceof internal.NullableArrayOf || + type instanceof internal.NullablePointerTo; } function Validator(message) { @@ -98,7 +98,7 @@ define("mojo/public/js/validator", [ }; Validator.prototype.claimHandle = function(index) { - if (index === codec.kEncodedInvalidHandleValue) + if (index === internal.kEncodedInvalidHandleValue) return true; if (index < this.handleIndex || index >= this.handleIndexLimit) @@ -119,7 +119,7 @@ define("mojo/public/js/validator", [ Validator.prototype.validateHandle = function(offset, nullable) { var index = this.message.buffer.getUint32(offset); - if (index === codec.kEncodedInvalidHandleValue) + if (index === internal.kEncodedInvalidHandleValue) return nullable ? validationError.NONE : validationError.UNEXPECTED_INVALID_HANDLE; @@ -138,10 +138,10 @@ define("mojo/public/js/validator", [ }; Validator.prototype.validateStructHeader = function(offset, minNumBytes) { - if (!codec.isAligned(offset)) + if (!internal.isAligned(offset)) return validationError.MISALIGNED_OBJECT; - if (!this.isValidRange(offset, codec.kStructHeaderSize)) + if (!this.isValidRange(offset, internal.kStructHeaderSize)) return validationError.ILLEGAL_MEMORY_RANGE; var numBytes = this.message.buffer.getUint32(offset); @@ -182,7 +182,7 @@ define("mojo/public/js/validator", [ Validator.prototype.validateMessageHeader = function() { - var err = this.validateStructHeader(0, codec.kMessageHeaderSize); + var err = this.validateStructHeader(0, internal.kMessageHeaderSize); if (err != validationError.NONE) return err; @@ -190,11 +190,11 @@ define("mojo/public/js/validator", [ var version = this.message.getHeaderVersion(); var validVersionAndNumBytes = - (version == 0 && numBytes == codec.kMessageHeaderSize) || + (version == 0 && numBytes == internal.kMessageHeaderSize) || (version == 1 && - numBytes == codec.kMessageWithRequestIDHeaderSize) || + numBytes == internal.kMessageWithRequestIDHeaderSize) || (version > 1 && - numBytes >= codec.kMessageWithRequestIDHeaderSize); + numBytes >= internal.kMessageWithRequestIDHeaderSize); if (!validVersionAndNumBytes) return validationError.UNEXPECTED_STRUCT_HEADER; @@ -322,13 +322,14 @@ define("mojo/public/js/validator", [ return mapIsNullable ? validationError.NONE : validationError.UNEXPECTED_NULL_POINTER; - var mapEncodedSize = codec.kStructHeaderSize + codec.kMapStructPayloadSize; + var mapEncodedSize = internal.kStructHeaderSize + + internal.kMapStructPayloadSize; var err = this.validateStructHeader(structOffset, mapEncodedSize); if (err !== validationError.NONE) return err; // Validate the keys array. - var keysArrayPointerOffset = structOffset + codec.kStructHeaderSize; + var keysArrayPointerOffset = structOffset + internal.kStructHeaderSize; err = this.validateArrayPointer( keysArrayPointerOffset, keyClass.encodedSize, keyClass, false, [0], 0); if (err !== validationError.NONE) @@ -337,7 +338,7 @@ define("mojo/public/js/validator", [ // Validate the values array. var valuesArrayPointerOffset = keysArrayPointerOffset + 8; var valuesArrayDimensions = [0]; // Validate the actual length below. - if (valueClass instanceof codec.ArrayOf) + if (valueClass instanceof internal.ArrayOf) valuesArrayDimensions = valuesArrayDimensions.concat(valueClass.dimensions()); var err = this.validateArrayPointer(valuesArrayPointerOffset, @@ -360,7 +361,7 @@ define("mojo/public/js/validator", [ Validator.prototype.validateStringPointer = function(offset, nullable) { return this.validateArrayPointer( - offset, codec.Uint8.encodedSize, codec.Uint8, nullable, [0], 0); + offset, internal.Uint8.encodedSize, internal.Uint8, nullable, [0], 0); }; // Similar to Array_Data<T>::Validate() @@ -369,10 +370,10 @@ define("mojo/public/js/validator", [ Validator.prototype.validateArray = function (offset, elementSize, elementType, expectedDimensionSizes, currentDimension) { - if (!codec.isAligned(offset)) + if (!internal.isAligned(offset)) return validationError.MISALIGNED_OBJECT; - if (!this.isValidRange(offset, codec.kArrayHeaderSize)) + if (!this.isValidRange(offset, internal.kArrayHeaderSize)) return validationError.ILLEGAL_MEMORY_RANGE; var numBytes = this.message.buffer.getUint32(offset); @@ -380,10 +381,10 @@ define("mojo/public/js/validator", [ // Note: this computation is "safe" because elementSize <= 8 and // numElements is a uint32. - var elementsTotalSize = (elementType === codec.PackedBool) ? + var elementsTotalSize = (elementType === internal.PackedBool) ? Math.ceil(numElements / 8) : (elementSize * numElements); - if (numBytes < codec.kArrayHeaderSize + elementsTotalSize) + if (numBytes < internal.kArrayHeaderSize + elementsTotalSize) return validationError.UNEXPECTED_ARRAY_HEADER; if (expectedDimensionSizes[currentDimension] != 0 && @@ -396,7 +397,7 @@ define("mojo/public/js/validator", [ // Validate the array's elements if they are pointers or handles. - var elementsOffset = offset + codec.kArrayHeaderSize; + var elementsOffset = offset + internal.kArrayHeaderSize; var nullable = isNullable(elementType); if (isHandleClass(elementType)) @@ -409,11 +410,11 @@ define("mojo/public/js/validator", [ elementsOffset, numElements, nullable); if (isStringClass(elementType)) return this.validateArrayElements( - elementsOffset, numElements, codec.Uint8, nullable, [0], 0); - if (elementType instanceof codec.PointerTo) + elementsOffset, numElements, internal.Uint8, nullable, [0], 0); + if (elementType instanceof internal.PointerTo) return this.validateStructElements( elementsOffset, numElements, elementType.cls, nullable); - if (elementType instanceof codec.ArrayOf) + if (elementType instanceof internal.ArrayOf) return this.validateArrayElements( elementsOffset, numElements, elementType.cls, nullable, expectedDimensionSizes, currentDimension + 1); @@ -430,7 +431,7 @@ define("mojo/public/js/validator", [ Validator.prototype.validateHandleElements = function(offset, numElements, nullable) { - var elementSize = codec.Handle.encodedSize; + var elementSize = internal.Handle.encodedSize; for (var i = 0; i < numElements; i++) { var elementOffset = offset + i * elementSize; var err = this.validateHandle(elementOffset, nullable); @@ -442,7 +443,7 @@ define("mojo/public/js/validator", [ Validator.prototype.validateInterfaceElements = function(offset, numElements, nullable) { - var elementSize = codec.Interface.prototype.encodedSize; + var elementSize = internal.Interface.prototype.encodedSize; for (var i = 0; i < numElements; i++) { var elementOffset = offset + i * elementSize; var err = this.validateInterface(elementOffset, nullable); @@ -454,7 +455,7 @@ define("mojo/public/js/validator", [ Validator.prototype.validateInterfaceRequestElements = function(offset, numElements, nullable) { - var elementSize = codec.InterfaceRequest.encodedSize; + var elementSize = internal.InterfaceRequest.encodedSize; for (var i = 0; i < numElements; i++) { var elementOffset = offset + i * elementSize; var err = this.validateInterfaceRequest(elementOffset, nullable); @@ -468,7 +469,7 @@ define("mojo/public/js/validator", [ Validator.prototype.validateArrayElements = function(offset, numElements, elementClass, nullable, expectedDimensionSizes, currentDimension) { - var elementSize = codec.PointerTo.prototype.encodedSize; + var elementSize = internal.PointerTo.prototype.encodedSize; for (var i = 0; i < numElements; i++) { var elementOffset = offset + i * elementSize; var err = this.validateArrayPointer( @@ -482,7 +483,7 @@ define("mojo/public/js/validator", [ Validator.prototype.validateStructElements = function(offset, numElements, structClass, nullable) { - var elementSize = codec.PointerTo.prototype.encodedSize; + var elementSize = internal.PointerTo.prototype.encodedSize; for (var i = 0; i < numElements; i++) { var elementOffset = offset + i * elementSize; var err = @@ -495,7 +496,7 @@ define("mojo/public/js/validator", [ Validator.prototype.validateEnumElements = function(offset, numElements, enumClass) { - var elementSize = codec.Enum.prototype.encodedSize; + var elementSize = internal.Enum.prototype.encodedSize; for (var i = 0; i < numElements; i++) { var elementOffset = offset + i * elementSize; var err = this.validateEnum(elementOffset, enumClass); @@ -505,8 +506,6 @@ define("mojo/public/js/validator", [ return validationError.NONE; }; - var exports = {}; - exports.validationError = validationError; - exports.Validator = Validator; - return exports; -}); + internal.validationError = validationError; + internal.Validator = Validator; +})(); diff --git a/mojo/public/js/router.js b/mojo/public/js/router.js index e94c5eb..89d9a2f 100644 --- a/mojo/public/js/router.js +++ b/mojo/public/js/router.js @@ -3,198 +3,264 @@ // found in the LICENSE file. define("mojo/public/js/router", [ - "console", - "mojo/public/js/codec", - "mojo/public/js/core", "mojo/public/js/connector", - "mojo/public/js/lib/control_message_handler", + "mojo/public/js/core", + "mojo/public/js/interface_types", + "mojo/public/js/lib/interface_endpoint_handle", + "mojo/public/js/lib/pipe_control_message_handler", + "mojo/public/js/lib/pipe_control_message_proxy", "mojo/public/js/validator", -], function(console, codec, core, connector, controlMessageHandler, validator) { + "timer", +], function(connector, core, types, interfaceEndpointHandle, + controlMessageHandler, controlMessageProxy, validator, timer) { var Connector = connector.Connector; - var MessageReader = codec.MessageReader; + var PipeControlMessageHandler = + controlMessageHandler.PipeControlMessageHandler; + var PipeControlMessageProxy = controlMessageProxy.PipeControlMessageProxy; var Validator = validator.Validator; - var ControlMessageHandler = controlMessageHandler.ControlMessageHandler; + var InterfaceEndpointHandle = interfaceEndpointHandle.InterfaceEndpointHandle; - function Router(handle, interface_version, connectorFactory) { - if (!core.isHandle(handle)) - throw new Error("Router constructor: Not a handle"); - if (connectorFactory === undefined) - connectorFactory = Connector; - this.connector_ = new connectorFactory(handle); - this.incomingReceiver_ = null; - this.errorHandler_ = null; - this.nextRequestID_ = 0; - this.completers_ = new Map(); - this.payloadValidators_ = []; - this.testingController_ = null; + /** + * The state of |endpoint|. If both the endpoint and its peer have been + * closed, removes it from |endpoints_|. + * @enum {string} + */ + var EndpointStateUpdateType = { + ENDPOINT_CLOSED: 'endpoint_closed', + PEER_ENDPOINT_CLOSED: 'peer_endpoint_closed' + }; - if (interface_version !== undefined) { - this.controlMessageHandler_ = new - ControlMessageHandler(interface_version); + function check(condition, output) { + if (!condition) { + // testharness.js does not rethrow errors so the error stack needs to be + // included as a string in the error we throw for debugging layout tests. + throw new Error((new Error()).stack); } + } + + function InterfaceEndpoint(router, interfaceId) { + this.router_ = router; + this.id = interfaceId; + this.closed = false; + this.peerClosed = false; + this.handleCreated = false; + this.disconnectReason = null; + this.client = null; + } + + InterfaceEndpoint.prototype.sendMessage = function(message) { + message.setInterfaceId(this.id); + return this.router_.connector_.accept(message); + }; + + function Router(handle, setInterfaceIdNamespaceBit) { + if (!core.isHandle(handle)) { + throw new Error("Router constructor: Not a handle"); + } + if (setInterfaceIdNamespaceBit === undefined) { + setInterfaceIdNamespaceBit = false; + } + + this.connector_ = new Connector(handle); this.connector_.setIncomingReceiver({ - accept: this.handleIncomingMessage_.bind(this), + accept: this.accept.bind(this), }); this.connector_.setErrorHandler({ - onError: this.handleConnectionError_.bind(this), + onError: this.onPipeConnectionError.bind(this), }); + + this.setInterfaceIdNamespaceBit_ = setInterfaceIdNamespaceBit; + this.controlMessageHandler_ = new PipeControlMessageHandler(this); + this.controlMessageProxy_ = new PipeControlMessageProxy(this.connector_); + this.nextInterfaceIdValue = 1; + this.encounteredError_ = false; + this.endpoints_ = new Map(); } - Router.prototype.close = function() { - this.completers_.clear(); // Drop any responders. - this.connector_.close(); - this.testingController_ = null; - }; + Router.prototype.attachEndpointClient = function( + interfaceEndpointHandle, interfaceEndpointClient) { + check(types.isValidInterfaceId(interfaceEndpointHandle.id())); + check(interfaceEndpointClient); - Router.prototype.accept = function(message) { - this.connector_.accept(message); - }; + var endpoint = this.endpoints_.get(interfaceEndpointHandle.id()); + check(endpoint); + check(!endpoint.client); + check(!endpoint.closed); + endpoint.client = interfaceEndpointClient; - Router.prototype.reject = function(message) { - // TODO(mpcomplete): no way to trasmit errors over a Connection. - }; + if (endpoint.peerClosed) { + timer.createOneShot(0, + endpoint.client.notifyError.bind(endpoint.client)); + } - Router.prototype.acceptAndExpectResponse = function(message) { - // Reserve 0 in case we want it to convey special meaning in the future. - var requestID = this.nextRequestID_++; - if (requestID == 0) - requestID = this.nextRequestID_++; - - message.setRequestID(requestID); - var result = this.connector_.accept(message); - if (!result) - return Promise.reject(Error("Connection error")); - - var completer = {}; - this.completers_.set(requestID, completer); - return new Promise(function(resolve, reject) { - completer.resolve = resolve; - completer.reject = reject; - }); + return endpoint; }; - Router.prototype.setIncomingReceiver = function(receiver) { - this.incomingReceiver_ = receiver; - }; + Router.prototype.detachEndpointClient = function( + interfaceEndpointHandle) { + check(types.isValidInterfaceId(interfaceEndpointHandle.id())); + var endpoint = this.endpoints_.get(interfaceEndpointHandle.id()); + check(endpoint); + check(endpoint.client); + check(!endpoint.closed); - Router.prototype.setPayloadValidators = function(payloadValidators) { - this.payloadValidators_ = payloadValidators; + endpoint.client = null; }; - Router.prototype.setErrorHandler = function(handler) { - this.errorHandler_ = handler; - }; + Router.prototype.createLocalEndpointHandle = function( + interfaceId) { + if (!types.isValidInterfaceId(interfaceId)) { + return new InterfaceEndpointHandle(); + } - Router.prototype.encounteredError = function() { - return this.connector_.encounteredError(); - }; + var endpoint = this.endpoints_.get(interfaceId); - Router.prototype.enableTestingMode = function() { - this.testingController_ = new RouterTestingController(this.connector_); - return this.testingController_; - }; + if (!endpoint) { + endpoint = new InterfaceEndpoint(this, interfaceId); + this.endpoints_.set(interfaceId, endpoint); - Router.prototype.handleIncomingMessage_ = function(message) { - var noError = validator.validationError.NONE; - var messageValidator = new Validator(message); - var err = messageValidator.validateMessageHeader(); - for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i) - err = this.payloadValidators_[i](messageValidator); + check(!endpoint.handleCreated); - if (err == noError) - this.handleValidIncomingMessage_(message); - else - this.handleInvalidIncomingMessage_(message, err); + if (this.encounteredError_) { + this.updateEndpointStateMayRemove(endpoint, + EndpointStateUpdateType.PEER_ENDPOINT_CLOSED); + } + } else { + // If the endpoint already exist, it is because we have received a + // notification that the peer endpoint has closed. + check(!endpoint.closed); + check(endpoint.peerClosed); + + if (endpoint.handleCreated) { + return new InterfaceEndpointHandle(); + } + } + + endpoint.handleCreated = true; + return new InterfaceEndpointHandle(interfaceId, this); }; - Router.prototype.handleValidIncomingMessage_ = function(message) { - if (this.testingController_) - return; + Router.prototype.accept = function(message) { + var messageValidator = new Validator(message); + var err = messageValidator.validateMessageHeader(); - if (message.expectsResponse()) { - if (controlMessageHandler.isControlMessage(message)) { - if (this.controlMessageHandler_) { - this.controlMessageHandler_.acceptWithResponder(message, this); - } else { - this.close(); - } - } else if (this.incomingReceiver_) { - this.incomingReceiver_.acceptWithResponder(message, this); - } else { - // If we receive a request expecting a response when the client is not - // listening, then we have no choice but to tear down the pipe. - this.close(); - } - } else if (message.isResponse()) { - var reader = new MessageReader(message); - var requestID = reader.requestID; - var completer = this.completers_.get(requestID); - if (completer) { - this.completers_.delete(requestID); - completer.resolve(message); - } else { - console.log("Unexpected response with request ID: " + requestID); - } + var ok = false; + if (err !== validator.validationError.NONE) { + validator.reportValidationError(err); + } else if (controlMessageHandler.isPipeControlMessage(message)) { + ok = this.controlMessageHandler_.accept(message); } else { - if (controlMessageHandler.isControlMessage(message)) { - if (this.controlMessageHandler_) { - var ok = this.controlMessageHandler_.accept(message); - if (ok) return; - } - this.close(); - } else if (this.incomingReceiver_) { - this.incomingReceiver_.accept(message); + var interfaceId = message.getInterfaceId(); + var endpoint = this.endpoints_.get(interfaceId); + if (!endpoint || endpoint.closed) { + return true; + } + + if (!endpoint.client) { + // We need to wait until a client is attached in order to dispatch + // further messages. + return false; } + ok = endpoint.client.handleIncomingMessage_(message); + } + + if (!ok) { + this.handleInvalidIncomingMessage_(); } + return ok; }; - Router.prototype.handleInvalidIncomingMessage_ = function(message, error) { - if (!this.testingController_) { + Router.prototype.close = function() { + this.connector_.close(); + // Closing the message pipe won't trigger connection error handler. + // Explicitly call onPipeConnectionError() so that associated endpoints + // will get notified. + this.onPipeConnectionError(); + }; + + Router.prototype.waitForNextMessageForTesting = function() { + this.connector_.waitForNextMessageForTesting(); + }; + + Router.prototype.handleInvalidIncomingMessage_ = function(message) { + if (!validator.isTestingMode()) { // TODO(yzshen): Consider notifying the embedder. // TODO(yzshen): This should also trigger connection error handler. // Consider making accept() return a boolean and let the connector deal // with this, as the C++ code does. - console.log("Invalid message: " + validator.validationError[error]); - this.close(); return; } - - this.testingController_.onInvalidIncomingMessage(error); }; - Router.prototype.handleConnectionError_ = function(result) { - this.completers_.forEach(function(value) { - value.reject(result); - }); - if (this.errorHandler_) - this.errorHandler_(); - this.close(); - }; + Router.prototype.onPeerAssociatedEndpointClosed = function(interfaceId, + reason) { + check(!types.isMasterInterfaceId(interfaceId) || reason); - // The RouterTestingController is used in unit tests. It defeats valid message - // handling and delgates invalid message handling. + var endpoint = this.endpoints_.get(interfaceId); + if (!endpoint) { + endpoint = new InterfaceEndpoint(this, interfaceId); + this.endpoints_.set(interfaceId, endpoint); + } - function RouterTestingController(connector) { - this.connector_ = connector; - this.invalidMessageHandler_ = null; - } + if (reason) { + endpoint.disconnectReason = reason; + } - RouterTestingController.prototype.waitForNextMessage = function() { - this.connector_.waitForNextMessageForTesting(); + if (!endpoint.peerClosed) { + if (endpoint.client) { + timer.createOneShot(0, + endpoint.client.notifyError.bind(endpoint.client, reason)); + } + this.updateEndpointStateMayRemove(endpoint, + EndpointStateUpdateType.PEER_ENDPOINT_CLOSED); + } + return true; }; - RouterTestingController.prototype.setInvalidIncomingMessageHandler = - function(callback) { - this.invalidMessageHandler_ = callback; + Router.prototype.onPipeConnectionError = function() { + this.encounteredError_ = true; + + for (var endpoint of this.endpoints_.values()) { + if (endpoint.client) { + timer.createOneShot(0, + endpoint.client.notifyError.bind(endpoint.client, + endpoint.disconnectReason)); + } + this.updateEndpointStateMayRemove(endpoint, + EndpointStateUpdateType.PEER_ENDPOINT_CLOSED); + } }; - RouterTestingController.prototype.onInvalidIncomingMessage = - function(error) { - if (this.invalidMessageHandler_) - this.invalidMessageHandler_(error); + Router.prototype.closeEndpointHandle = function(interfaceId, reason) { + if (!types.isValidInterfaceId(interfaceId)) { + return; + } + var endpoint = this.endpoints_.get(interfaceId); + check(endpoint); + check(!endpoint.client); + check(!endpoint.closed); + + this.updateEndpointStateMayRemove(endpoint, + EndpointStateUpdateType.ENDPOINT_CLOSED); + + if (!types.isMasterInterfaceId(interfaceId) || reason) { + this.controlMessageProxy_.notifyPeerEndpointClosed(interfaceId, reason); + } + }; + + Router.prototype.updateEndpointStateMayRemove = function(endpoint, + endpointStateUpdateType) { + if (endpointStateUpdateType === EndpointStateUpdateType.ENDPOINT_CLOSED) { + endpoint.closed = true; + } else { + endpoint.peerClosed = true; + } + if (endpoint.closed && endpoint.peerClosed) { + this.endpoints_.delete(endpoint.id); + } }; var exports = {}; diff --git a/mojo/public/js/tests/codec_unittest.js b/mojo/public/js/tests/codec_unittest.js deleted file mode 100644 index 5fa4076..0000000 --- a/mojo/public/js/tests/codec_unittest.js +++ /dev/null @@ -1,314 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -define([ - "gin/test/expect", - "mojo/public/js/codec", - "mojo/public/interfaces/bindings/tests/rect.mojom", - "mojo/public/interfaces/bindings/tests/sample_service.mojom", - "mojo/public/interfaces/bindings/tests/test_structs.mojom", - ], function(expect, codec, rect, sample, structs) { - testBar(); - testFoo(); - testNamedRegion(); - testSingleBooleanStruct(); - testTypes(); - testAlign(); - testUtf8(); - testTypedPointerValidation(); - this.result = "PASS"; - - function testBar() { - var bar = new sample.Bar(); - bar.alpha = 1; - bar.beta = 2; - bar.gamma = 3; - bar.type = 0x08070605; - bar.extraProperty = "banana"; - - var messageName = 42; - var payloadSize = sample.Bar.encodedSize; - - var builder = new codec.MessageBuilder(messageName, payloadSize); - builder.encodeStruct(sample.Bar, bar); - - var message = builder.finish(); - - var expectedMemory = new Uint8Array([ - 24, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 42, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - - 16, 0, 0, 0, - 0, 0, 0, 0, - - 1, 2, 3, 0, - 5, 6, 7, 8, - ]); - - var actualMemory = new Uint8Array(message.buffer.arrayBuffer); - expect(actualMemory).toEqual(expectedMemory); - - var reader = new codec.MessageReader(message); - - expect(reader.payloadSize).toBe(payloadSize); - expect(reader.messageName).toBe(messageName); - - var bar2 = reader.decodeStruct(sample.Bar); - - expect(bar2.alpha).toBe(bar.alpha); - expect(bar2.beta).toBe(bar.beta); - expect(bar2.gamma).toBe(bar.gamma); - expect("extraProperty" in bar2).toBeFalsy(); - } - - function testFoo() { - var foo = new sample.Foo(); - foo.x = 0x212B4D5; - foo.y = 0x16E93; - foo.a = 1; - foo.b = 0; - foo.c = 3; // This will get truncated to one bit. - foo.bar = new sample.Bar(); - foo.bar.alpha = 91; - foo.bar.beta = 82; - foo.bar.gamma = 73; - foo.data = [ - 4, 5, 6, 7, 8, - ]; - foo.extra_bars = [ - new sample.Bar(), new sample.Bar(), new sample.Bar(), - ]; - for (var i = 0; i < foo.extra_bars.length; ++i) { - foo.extra_bars[i].alpha = 1 * i; - foo.extra_bars[i].beta = 2 * i; - foo.extra_bars[i].gamma = 3 * i; - } - foo.name = "I am a banana"; - // This is supposed to be a handle, but we fake it with an integer. - foo.source = 23423782; - foo.array_of_array_of_bools = [ - [true], [false, true] - ]; - foo.array_of_bools = [ - true, false, true, false, true, false, true, true - ]; - - - var messageName = 31; - var payloadSize = 304; - - var builder = new codec.MessageBuilder(messageName, payloadSize); - builder.encodeStruct(sample.Foo, foo); - - var message = builder.finish(); - - var expectedMemory = new Uint8Array([ - /* 0: */ 24, 0, 0, 0, 0, 0, 0, 0, - /* 8: */ 0, 0, 0, 0, 31, 0, 0, 0, - /* 16: */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 24: */ 96, 0, 0, 0, 0, 0, 0, 0, - /* 32: */ 0xD5, 0xB4, 0x12, 0x02, 0x93, 0x6E, 0x01, 0, - /* 40: */ 5, 0, 0, 0, 0, 0, 0, 0, - /* 48: */ 72, 0, 0, 0, 0, 0, 0, 0, - ]); - // TODO(abarth): Test more of the message's raw memory. - var actualMemory = new Uint8Array(message.buffer.arrayBuffer, - 0, expectedMemory.length); - expect(actualMemory).toEqual(expectedMemory); - - var expectedHandles = [ - 23423782, - ]; - - expect(message.handles).toEqual(expectedHandles); - - var reader = new codec.MessageReader(message); - - expect(reader.payloadSize).toBe(payloadSize); - expect(reader.messageName).toBe(messageName); - - var foo2 = reader.decodeStruct(sample.Foo); - - expect(foo2.x).toBe(foo.x); - expect(foo2.y).toBe(foo.y); - - expect(foo2.a).toBe(foo.a & 1 ? true : false); - expect(foo2.b).toBe(foo.b & 1 ? true : false); - expect(foo2.c).toBe(foo.c & 1 ? true : false); - - expect(foo2.bar).toEqual(foo.bar); - expect(foo2.data).toEqual(foo.data); - - expect(foo2.extra_bars).toEqual(foo.extra_bars); - expect(foo2.name).toBe(foo.name); - expect(foo2.source).toEqual(foo.source); - - expect(foo2.array_of_bools).toEqual(foo.array_of_bools); - } - - function createRect(x, y, width, height) { - var r = new rect.Rect(); - r.x = x; - r.y = y; - r.width = width; - r.height = height; - return r; - } - - // Verify that the references to the imported Rect type in test_structs.mojom - // are generated correctly. - function testNamedRegion() { - var r = new structs.NamedRegion(); - r.name = "rectangle"; - r.rects = new Array(createRect(1, 2, 3, 4), createRect(10, 20, 30, 40)); - - var builder = new codec.MessageBuilder(1, structs.NamedRegion.encodedSize); - builder.encodeStruct(structs.NamedRegion, r); - var reader = new codec.MessageReader(builder.finish()); - var result = reader.decodeStruct(structs.NamedRegion); - - expect(result.name).toEqual("rectangle"); - expect(result.rects[0]).toEqual(createRect(1, 2, 3, 4)); - expect(result.rects[1]).toEqual(createRect(10, 20, 30, 40)); - } - - // Verify that a single boolean field in a struct is correctly decoded to - // boolean type. - function testSingleBooleanStruct() { - var single_bool = new structs.SingleBoolStruct(); - single_bool.value = true; - - var builder = new codec.MessageBuilder( - 1, structs.SingleBoolStruct.encodedSize); - builder.encodeStruct(structs.SingleBoolStruct, single_bool); - var reader = new codec.MessageReader(builder.finish()); - var result = reader.decodeStruct(structs.SingleBoolStruct); - - // Use toEqual() instead of toBeTruthy() to make sure the field type is - // actually boolean. - expect(result.value).toEqual(true); - } - - function testTypes() { - function encodeDecode(cls, input, expectedResult, encodedSize) { - var messageName = 42; - var payloadSize = encodedSize || cls.encodedSize; - - var builder = new codec.MessageBuilder(messageName, payloadSize); - builder.encodeStruct(cls, input) - var message = builder.finish(); - - var reader = new codec.MessageReader(message); - expect(reader.payloadSize).toBe(payloadSize); - expect(reader.messageName).toBe(messageName); - var result = reader.decodeStruct(cls); - expect(result).toEqual(expectedResult); - } - encodeDecode(codec.String, "banana", "banana", 24); - encodeDecode(codec.NullableString, null, null, 8); - encodeDecode(codec.Int8, -1, -1); - encodeDecode(codec.Int8, 0xff, -1); - encodeDecode(codec.Int16, -1, -1); - encodeDecode(codec.Int16, 0xff, 0xff); - encodeDecode(codec.Int16, 0xffff, -1); - encodeDecode(codec.Int32, -1, -1); - encodeDecode(codec.Int32, 0xffff, 0xffff); - encodeDecode(codec.Int32, 0xffffffff, -1); - encodeDecode(codec.Float, 1.0, 1.0); - encodeDecode(codec.Double, 1.0, 1.0); - } - - function testAlign() { - var aligned = [ - 0, // 0 - 8, // 1 - 8, // 2 - 8, // 3 - 8, // 4 - 8, // 5 - 8, // 6 - 8, // 7 - 8, // 8 - 16, // 9 - 16, // 10 - 16, // 11 - 16, // 12 - 16, // 13 - 16, // 14 - 16, // 15 - 16, // 16 - 24, // 17 - 24, // 18 - 24, // 19 - 24, // 20 - ]; - for (var i = 0; i < aligned.length; ++i) - expect(codec.align(i)).toBe(aligned[i]); - } - - function testUtf8() { - var str = "B\u03ba\u1f79"; // some UCS-2 codepoints - var messageName = 42; - var payloadSize = 24; - - var builder = new codec.MessageBuilder(messageName, payloadSize); - var encoder = builder.createEncoder(8); - encoder.encodeStringPointer(str); - var message = builder.finish(); - var expectedMemory = new Uint8Array([ - /* 0: */ 24, 0, 0, 0, 0, 0, 0, 0, - /* 8: */ 0, 0, 0, 0, 42, 0, 0, 0, - /* 16: */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 24: */ 8, 0, 0, 0, 0, 0, 0, 0, - /* 32: */ 14, 0, 0, 0, 6, 0, 0, 0, - /* 40: */ 0x42, 0xCE, 0xBA, 0xE1, 0xBD, 0xB9, 0, 0, - ]); - var actualMemory = new Uint8Array(message.buffer.arrayBuffer); - expect(actualMemory.length).toEqual(expectedMemory.length); - expect(actualMemory).toEqual(expectedMemory); - - var reader = new codec.MessageReader(message); - expect(reader.payloadSize).toBe(payloadSize); - expect(reader.messageName).toBe(messageName); - var str2 = reader.decoder.decodeStringPointer(); - expect(str2).toEqual(str); - } - - function testTypedPointerValidation() { - var encoder = new codec.MessageBuilder(42, 24).createEncoder(8); - function DummyClass() {}; - var testCases = [ - // method, args, invalid examples, valid examples - [encoder.encodeArrayPointer, [DummyClass], [75], - [[], null, undefined, new Uint8Array([])]], - [encoder.encodeStringPointer, [], [75, new String("foo")], - ["", "bar", null, undefined]], - [encoder.encodeMapPointer, [DummyClass, DummyClass], [75], - [new Map(), null, undefined]], - ]; - - testCases.forEach(function(test) { - var method = test[0]; - var baseArgs = test[1]; - var invalidExamples = test[2]; - var validExamples = test[3]; - - var encoder = new codec.MessageBuilder(42, 24).createEncoder(8); - invalidExamples.forEach(function(invalid) { - expect(function() { - method.apply(encoder, baseArgs.concat(invalid)); - }).toThrow(); - }); - - validExamples.forEach(function(valid) { - var encoder = new codec.MessageBuilder(42, 24).createEncoder(8); - method.apply(encoder, baseArgs.concat(valid)); - }); - }); - } -}); diff --git a/mojo/public/js/tests/connection_unittest.js b/mojo/public/js/tests/connection_unittest.js deleted file mode 100644 index feba87d..0000000 --- a/mojo/public/js/tests/connection_unittest.js +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -define([ - "gin/test/expect", - "mojo/public/js/bindings", - "mojo/public/js/core", - "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom", - "mojo/public/interfaces/bindings/tests/sample_service.mojom", - "mojo/public/js/threading", - "gc", -], function(expect, - bindings, - core, - sample_interfaces, - sample_service, - threading, - gc) { - testClientServer() - .then(testWriteToClosedPipe) - .then(testRequestResponse) - .then(function() { - this.result = "PASS"; - gc.collectGarbage(); // should not crash - threading.quit(); - }.bind(this)).catch(function(e) { - this.result = "FAIL: " + (e.stack || e); - threading.quit(); - }.bind(this)); - - function testClientServer() { - // ServiceImpl ------------------------------------------------------------ - - function ServiceImpl() { - } - - ServiceImpl.prototype.frobinate = function(foo, baz, port) { - expect(foo.name).toBe("Example name"); - expect(baz).toBe(sample_service.Service.BazOptions.REGULAR); - expect(port.ptr.isBound()).toBeTruthy(); - port.ptr.reset(); - - return Promise.resolve({result: 42}); - }; - - var service = new sample_service.ServicePtr(); - var serviceBinding = new bindings.Binding(sample_service.Service, - new ServiceImpl(), - bindings.makeRequest(service)); - var sourcePipe = core.createMessagePipe(); - var port = new sample_service.PortPtr(); - var portRequest = bindings.makeRequest(port); - - var foo = new sample_service.Foo(); - foo.bar = new sample_service.Bar(); - foo.name = "Example name"; - foo.source = sourcePipe.handle0; - var promise = service.frobinate( - foo, sample_service.Service.BazOptions.REGULAR, port) - .then(function(response) { - expect(response.result).toBe(42); - - service.ptr.reset(); - serviceBinding.close(); - - return Promise.resolve(); - }); - - // sourcePipe.handle1 hasn't been closed yet. - expect(core.close(sourcePipe.handle1)).toBe(core.RESULT_OK); - - // portRequest.handle hasn't been closed yet. - expect(core.close(portRequest.handle)).toBe(core.RESULT_OK); - - return promise; - } - - function testWriteToClosedPipe() { - var service = new sample_service.ServicePtr(); - // Discard the interface request. - bindings.makeRequest(service); - gc.collectGarbage(); - - var promise = service.frobinate( - null, sample_service.Service.BazOptions.REGULAR, null) - .then(function(response) { - return Promise.reject("Unexpected response"); - }).catch(function(e) { - // We should observe the closed pipe. - return Promise.resolve(); - }); - - return promise; - } - - function testRequestResponse() { - // ProviderImpl ------------------------------------------------------------ - - function ProviderImpl() { - } - - ProviderImpl.prototype.echoString = function(a) { - return Promise.resolve({a: a}); - }; - - ProviderImpl.prototype.echoStrings = function(a, b) { - return Promise.resolve({a: a, b: b}); - }; - - var provider = new sample_interfaces.ProviderPtr(); - var providerBinding = new bindings.Binding(sample_interfaces.Provider, - new ProviderImpl(), - bindings.makeRequest(provider)); - var promise = provider.echoString("hello").then(function(response) { - expect(response.a).toBe("hello"); - return provider.echoStrings("hello", "world"); - }).then(function(response) { - expect(response.a).toBe("hello"); - expect(response.b).toBe("world"); - // Mock a read failure, expect it to fail. - core.readMessage = function() { - return { result: core.RESULT_UNKNOWN }; - }; - return provider.echoString("goodbye"); - }).then(function() { - throw Error("Expected echoString to fail."); - }, function(error) { - expect(error.message).toBe("Connection error: " + core.RESULT_UNKNOWN); - - return Promise.resolve(); - }); - - return promise; - } -}); diff --git a/mojo/public/js/tests/core_unittest.js b/mojo/public/js/tests/core_unittest.js index 395ed05..86a997f 100644 --- a/mojo/public/js/tests/core_unittest.js +++ b/mojo/public/js/tests/core_unittest.js @@ -89,35 +89,15 @@ define([ } function testReadAndWriteMessage(pipe) { - var wait = core.waitMany([], [], 0); - expect(wait.result).toBe(core.RESULT_INVALID_ARGUMENT); - expect(wait.index).toBe(null); - expect(wait.signalsState).toBe(null); - - wait = core.wait(pipe.handle0, core.HANDLE_SIGNAL_READABLE, 0); - expect(wait.result).toBe(core.RESULT_DEADLINE_EXCEEDED); - expect(wait.signalsState.satisfiedSignals).toBe( - core.HANDLE_SIGNAL_WRITABLE); - expect(wait.signalsState.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL); + var state0 = core.queryHandleSignalsState(pipe.handle0); + expect(state0.result).toBe(core.RESULT_OK); + expect(state0.satisfiedSignals).toBe(core.HANDLE_SIGNAL_WRITABLE); + expect(state0.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL); - wait = core.waitMany( - [pipe.handle0, pipe.handle1], - [core.HANDLE_SIGNAL_READABLE,core.HANDLE_SIGNAL_READABLE], - 0); - expect(wait.result).toBe(core.RESULT_DEADLINE_EXCEEDED); - expect(wait.index).toBe(null); - expect(wait.signalsState[0].satisfiedSignals).toBe( - core.HANDLE_SIGNAL_WRITABLE); - expect(wait.signalsState[0].satisfiableSignals).toBe(HANDLE_SIGNAL_ALL); - expect(wait.signalsState[1].satisfiedSignals).toBe( - core.HANDLE_SIGNAL_WRITABLE); - expect(wait.signalsState[1].satisfiableSignals).toBe(HANDLE_SIGNAL_ALL); - - wait = core.wait(pipe.handle0, core.HANDLE_SIGNAL_WRITABLE, 0); - expect(wait.result).toBe(core.RESULT_OK); - expect(wait.signalsState.satisfiedSignals).toBe( - core.HANDLE_SIGNAL_WRITABLE); - expect(wait.signalsState.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL); + var state1 = core.queryHandleSignalsState(pipe.handle1); + expect(state1.result).toBe(core.RESULT_OK); + expect(state1.satisfiedSignals).toBe(core.HANDLE_SIGNAL_WRITABLE); + expect(state1.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL); var senderData = new Uint8Array(42); for (var i = 0; i < senderData.length; ++i) { @@ -130,14 +110,12 @@ define([ expect(result).toBe(core.RESULT_OK); - wait = core.wait(pipe.handle0, core.HANDLE_SIGNAL_WRITABLE, 0); - expect(wait.result).toBe(core.RESULT_OK); - expect(wait.signalsState.satisfiedSignals).toBe( - core.HANDLE_SIGNAL_WRITABLE); - expect(wait.signalsState.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL); + state0 = core.queryHandleSignalsState(pipe.handle0); + expect(state0.result).toBe(core.RESULT_OK); + expect(state0.satisfiedSignals).toBe(core.HANDLE_SIGNAL_WRITABLE); + expect(state0.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL); - wait = core.wait(pipe.handle1, core.HANDLE_SIGNAL_READABLE, - core.DEADLINE_INDEFINITE); + var wait = core.wait(pipe.handle1, core.HANDLE_SIGNAL_READABLE); expect(wait.result).toBe(core.RESULT_OK); expect(wait.signalsState.satisfiedSignals).toBe(HANDLE_SIGNAL_READWRITABLE); expect(wait.signalsState.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL); @@ -166,8 +144,7 @@ define([ expect(write.result).toBe(core.RESULT_OK); expect(write.numBytes).toBe(42); - var wait = core.wait(pipe.consumerHandle, core.HANDLE_SIGNAL_READABLE, - core.DEADLINE_INDEFINITE); + var wait = core.wait(pipe.consumerHandle, core.HANDLE_SIGNAL_READABLE); expect(wait.result).toBe(core.RESULT_OK); var peeked = core.readData( pipe.consumerHandle, diff --git a/mojo/public/js/tests/interface_ptr_unittest.js b/mojo/public/js/tests/interface_ptr_unittest.js deleted file mode 100644 index 6203154..0000000 --- a/mojo/public/js/tests/interface_ptr_unittest.js +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -define([ - "gin/test/expect", - "mojo/public/js/bindings", - "mojo/public/js/core", - "mojo/public/interfaces/bindings/tests/math_calculator.mojom", - "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom", - "mojo/public/js/threading", - "gc", -], function(expect, - bindings, - core, - math, - sampleInterfaces, - threading, - gc) { - testIsBound() - .then(testEndToEnd) - .then(testReusable) - .then(testConnectionError) - .then(testPassInterface) - .then(testBindRawHandle) - .then(testQueryVersion) - .then(testRequireVersion) - .then(function() { - this.result = "PASS"; - gc.collectGarbage(); // should not crash - threading.quit(); - }.bind(this)).catch(function(e) { - this.result = "FAIL: " + (e.stack || e); - threading.quit(); - }.bind(this)); - - function CalculatorImpl() { - this.total = 0; - } - - CalculatorImpl.prototype.clear = function() { - this.total = 0; - return Promise.resolve({value: this.total}); - }; - - CalculatorImpl.prototype.add = function(value) { - this.total += value; - return Promise.resolve({value: this.total}); - }; - - CalculatorImpl.prototype.multiply = function(value) { - this.total *= value; - return Promise.resolve({value: this.total}); - }; - - function IntegerAccessorImpl() { - this.integer = 0; - } - - IntegerAccessorImpl.prototype.getInteger = function() { - return Promise.resolve({data: this.integer}); - }; - - IntegerAccessorImpl.prototype.setInteger = function(value) { - this.integer = value; - }; - - function testIsBound() { - var calc = new math.CalculatorPtr(); - expect(calc.ptr.isBound()).toBeFalsy(); - - var request = bindings.makeRequest(calc); - expect(calc.ptr.isBound()).toBeTruthy(); - - calc.ptr.reset(); - expect(calc.ptr.isBound()).toBeFalsy(); - - return Promise.resolve(); - } - - function testEndToEnd() { - var calc = new math.CalculatorPtr(); - var calcBinding = new bindings.Binding(math.Calculator, - new CalculatorImpl(), - bindings.makeRequest(calc)); - - var promise = calc.add(2).then(function(response) { - expect(response.value).toBe(2); - return calc.multiply(5); - }).then(function(response) { - expect(response.value).toBe(10); - return calc.clear(); - }).then(function(response) { - expect(response.value).toBe(0); - return Promise.resolve(); - }); - - return promise; - } - - function testReusable() { - var calc = new math.CalculatorPtr(); - var calcImpl1 = new CalculatorImpl(); - var calcBinding1 = new bindings.Binding(math.Calculator, - calcImpl1, - bindings.makeRequest(calc)); - var calcImpl2 = new CalculatorImpl(); - var calcBinding2 = new bindings.Binding(math.Calculator, calcImpl2); - - var promise = calc.add(2).then(function(response) { - expect(response.value).toBe(2); - calcBinding2.bind(bindings.makeRequest(calc)); - return calc.add(2); - }).then(function(response) { - expect(response.value).toBe(2); - expect(calcImpl1.total).toBe(2); - expect(calcImpl2.total).toBe(2); - return Promise.resolve(); - }); - - return promise; - } - - function testConnectionError() { - var calc = new math.CalculatorPtr(); - var calcBinding = new bindings.Binding(math.Calculator, - new CalculatorImpl(), - bindings.makeRequest(calc)); - - var promise = new Promise(function(resolve, reject) { - calc.ptr.setConnectionErrorHandler(function() { - resolve(); - }); - calcBinding.close(); - }); - - return promise; - } - - function testPassInterface() { - var calc = new math.CalculatorPtr(); - var newCalc = null; - var calcBinding = new bindings.Binding(math.Calculator, - new CalculatorImpl(), - bindings.makeRequest(calc)); - - var promise = calc.add(2).then(function(response) { - expect(response.value).toBe(2); - newCalc = new math.CalculatorPtr(); - newCalc.ptr.bind(calc.ptr.passInterface()); - expect(calc.ptr.isBound()).toBeFalsy(); - return newCalc.add(2); - }).then(function(response) { - expect(response.value).toBe(4); - return Promise.resolve(); - }); - - return promise; - } - - function testBindRawHandle() { - var pipe = core.createMessagePipe(); - var calc = new math.CalculatorPtr(pipe.handle0); - var newCalc = null; - var calcBinding = new bindings.Binding(math.Calculator, - new CalculatorImpl(), - pipe.handle1); - - var promise = calc.add(2).then(function(response) { - expect(response.value).toBe(2); - return Promise.resolve(); - }); - - return promise; - } - - function testQueryVersion() { - var integerAccessorPtr = new sampleInterfaces.IntegerAccessorPtr(); - - var integerAccessorBinding = new bindings.Binding( - sampleInterfaces.IntegerAccessor, - new IntegerAccessorImpl(), - bindings.makeRequest(integerAccessorPtr)); - expect(integerAccessorPtr.ptr.version).toBe(0); - - return integerAccessorPtr.ptr.queryVersion().then(function(version) { - expect(version).toBe(3); - expect(integerAccessorPtr.ptr.version).toBe(3); - }); - } - - function testRequireVersion() { - var integerAccessorImpl = new IntegerAccessorImpl(); - var integerAccessorPtr = new sampleInterfaces.IntegerAccessorPtr(); - var integerAccessorBinding = new bindings.Binding( - sampleInterfaces.IntegerAccessor, - integerAccessorImpl, - bindings.makeRequest(integerAccessorPtr)); - - // Inital version is 0. - expect(integerAccessorPtr.ptr.version).toBe(0); - - function requireVersion1() { - integerAccessorPtr.ptr.requireVersion(1); - expect(integerAccessorPtr.ptr.version).toBe(1); - integerAccessorPtr.setInteger(123, sampleInterfaces.Enum.VALUE); - return integerAccessorPtr.getInteger().then(function(responseParams) { - expect(responseParams.data).toBe(123); - }); - } - - function requireVersion3() { - integerAccessorPtr.ptr.requireVersion(3); - expect(integerAccessorPtr.ptr.version).toBe(3); - integerAccessorPtr.setInteger(456, sampleInterfaces.Enum.VALUE); - return integerAccessorPtr.getInteger().then(function(responseParams) { - expect(responseParams.data).toBe(456); - }); - } - - // Require a version that is not supported by the impl side. - function requireVersion4() { - integerAccessorPtr.ptr.requireVersion(4); - expect(integerAccessorPtr.ptr.version).toBe(4); - integerAccessorPtr.setInteger(789, sampleInterfaces.Enum.VALUE); - - var promise = new Promise(function(resolve, reject) { - integerAccessorPtr.ptr.setConnectionErrorHandler(function() { - // The call to setInteger() after requireVersion(4) is ignored. - expect(integerAccessorImpl.integer).toBe(456); - resolve(); - }); - }); - - return promise; - } - - return requireVersion1().then(requireVersion3).then(requireVersion4); - } -}); diff --git a/mojo/public/js/tests/sample_service_unittest.js b/mojo/public/js/tests/sample_service_unittest.js deleted file mode 100644 index b9a2845..0000000 --- a/mojo/public/js/tests/sample_service_unittest.js +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -define([ - "gin/test/expect", - "mojo/public/interfaces/bindings/tests/sample_service.mojom", - "mojo/public/interfaces/bindings/tests/sample_import.mojom", - "mojo/public/interfaces/bindings/tests/sample_import2.mojom", - "mojo/public/js/bindings", - "mojo/public/js/core", - "mojo/public/js/threading", - ], function(expect, sample, imported, imported2, bindings, core, threading) { - testDefaultValues() - .then(testSampleService) - .then(function() { - this.result = "PASS"; - threading.quit(); - }.bind(this)).catch(function(e) { - this.result = "FAIL: " + (e.stack || e); - threading.quit(); - }.bind(this)); - - // Checks that values are set to the defaults if we don't override them. - function testDefaultValues() { - var bar = new sample.Bar(); - expect(bar.alpha).toBe(255); - expect(bar.type).toBe(sample.Bar.Type.VERTICAL); - - var foo = new sample.Foo(); - expect(foo.name).toBe("Fooby"); - expect(foo.a).toBeTruthy(); - expect(foo.data).toBeNull(); - - var defaults = new sample.DefaultsTest(); - expect(defaults.a0).toBe(-12); - expect(defaults.a1).toBe(sample.kTwelve); - expect(defaults.a2).toBe(1234); - expect(defaults.a3).toBe(34567); - expect(defaults.a4).toBe(123456); - expect(defaults.a5).toBe(3456789012); - expect(defaults.a6).toBe(-111111111111); - // JS doesn't have a 64 bit integer type so this is just checking that the - // expected and actual values have the same closest double value. - expect(defaults.a7).toBe(9999999999999999999); - expect(defaults.a8).toBe(0x12345); - expect(defaults.a9).toBe(-0x12345); - expect(defaults.a10).toBe(1234); - expect(defaults.a11).toBe(true); - expect(defaults.a12).toBe(false); - expect(defaults.a13).toBe(123.25); - expect(defaults.a14).toBe(1234567890.123); - expect(defaults.a15).toBe(1E10); - expect(defaults.a16).toBe(-1.2E+20); - expect(defaults.a17).toBe(1.23E-20); - expect(defaults.a20).toBe(sample.Bar.Type.BOTH); - expect(defaults.a21).toBeNull(); - expect(defaults.a22).toBeTruthy(); - expect(defaults.a22.shape).toBe(imported.Shape.RECTANGLE); - expect(defaults.a22.color).toBe(imported2.Color.BLACK); - expect(defaults.a21).toBeNull(); - expect(defaults.a23).toBe(0xFFFFFFFFFFFFFFFF); - expect(defaults.a24).toBe(0x123456789); - expect(defaults.a25).toBe(-0x123456789); - - return Promise.resolve(); - } - - function testSampleService() { - function ServiceImpl() { - } - - ServiceImpl.prototype.frobinate = function(foo, baz, port) { - checkFoo(foo); - expect(baz).toBe(sample.Service.BazOptions.EXTRA); - expect(port.ptr.isBound()).toBeTruthy(); - return Promise.resolve({result: 1234}); - }; - - var foo = makeFoo(); - checkFoo(foo); - - var service = new sample.ServicePtr(); - var request = bindings.makeRequest(service); - var serviceBinding = new bindings.Binding( - sample.Service, new ServiceImpl(), request); - - var port = new sample.PortPtr(); - bindings.makeRequest(port); - var promise = service.frobinate( - foo, sample.Service.BazOptions.EXTRA, port) - .then(function(response) { - expect(response.result).toBe(1234); - - return Promise.resolve(); - }); - - return promise; - } - - function makeFoo() { - var bar = new sample.Bar(); - bar.alpha = 20; - bar.beta = 40; - bar.gamma = 60; - bar.type = sample.Bar.Type.VERTICAL; - - var extra_bars = new Array(3); - for (var i = 0; i < extra_bars.length; ++i) { - var base = i * 100; - var type = i % 2 ? - sample.Bar.Type.VERTICAL : sample.Bar.Type.HORIZONTAL; - extra_bars[i] = new sample.Bar(); - extra_bars[i].alpha = base; - extra_bars[i].beta = base + 20; - extra_bars[i].gamma = base + 40; - extra_bars[i].type = type; - } - - var data = new Array(10); - for (var i = 0; i < data.length; ++i) { - data[i] = data.length - i; - } - - var foo = new sample.Foo(); - foo.name = "foopy"; - foo.x = 1; - foo.y = 2; - foo.a = false; - foo.b = true; - foo.c = false; - foo.bar = bar; - foo.extra_bars = extra_bars; - foo.data = data; - - // TODO(yzshen): currently setting it to null will cause connection error, - // even if the field is defined as nullable. crbug.com/575753 - foo.source = core.createMessagePipe().handle0; - - return foo; - } - - // Checks that the given |Foo| is identical to the one made by |makeFoo()|. - function checkFoo(foo) { - expect(foo.name).toBe("foopy"); - expect(foo.x).toBe(1); - expect(foo.y).toBe(2); - expect(foo.a).toBeFalsy(); - expect(foo.b).toBeTruthy(); - expect(foo.c).toBeFalsy(); - expect(foo.bar.alpha).toBe(20); - expect(foo.bar.beta).toBe(40); - expect(foo.bar.gamma).toBe(60); - expect(foo.bar.type).toBe(sample.Bar.Type.VERTICAL); - - expect(foo.extra_bars.length).toBe(3); - for (var i = 0; i < foo.extra_bars.length; ++i) { - var base = i * 100; - var type = i % 2 ? - sample.Bar.Type.VERTICAL : sample.Bar.Type.HORIZONTAL; - expect(foo.extra_bars[i].alpha).toBe(base); - expect(foo.extra_bars[i].beta).toBe(base + 20); - expect(foo.extra_bars[i].gamma).toBe(base + 40); - expect(foo.extra_bars[i].type).toBe(type); - } - - expect(foo.data.length).toBe(10); - for (var i = 0; i < foo.data.length; ++i) - expect(foo.data[i]).toBe(foo.data.length - i); - - expect(core.isHandle(foo.source)).toBeTruthy(); - } -}); diff --git a/mojo/public/js/tests/struct_unittest.js b/mojo/public/js/tests/struct_unittest.js deleted file mode 100644 index 691d51b..0000000 --- a/mojo/public/js/tests/struct_unittest.js +++ /dev/null @@ -1,279 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -define([ - "gin/test/expect", - "mojo/public/interfaces/bindings/tests/rect.mojom", - "mojo/public/interfaces/bindings/tests/test_structs.mojom", - "mojo/public/js/codec", - "mojo/public/js/validator", -], function(expect, - rect, - testStructs, - codec, - validator) { - - function testConstructors() { - var r = new rect.Rect(); - expect(r).toEqual(new rect.Rect({x:0, y:0, width:0, height:0})); - expect(r).toEqual(new rect.Rect({foo:100, bar:200})); - - r.x = 10; - r.y = 20; - r.width = 30; - r.height = 40; - var rp = new testStructs.RectPair({first: r, second: r}); - expect(rp.first).toEqual(r); - expect(rp.second).toEqual(r); - - expect(new testStructs.RectPair({second: r}).first).toBeNull(); - - var nr = new testStructs.NamedRegion(); - expect(nr.name).toBeNull(); - expect(nr.rects).toBeNull(); - expect(nr).toEqual(new testStructs.NamedRegion({})); - - nr.name = "foo"; - nr.rects = [r, r, r]; - expect(nr).toEqual(new testStructs.NamedRegion({ - name: "foo", - rects: [r, r, r], - })); - - var e = new testStructs.EmptyStruct(); - expect(e).toEqual(new testStructs.EmptyStruct({foo:123})); - } - - function testNoDefaultFieldValues() { - var s = new testStructs.NoDefaultFieldValues(); - expect(s.f0).toEqual(false); - - // f1 - f10, number type fields - for (var i = 1; i <= 10; i++) - expect(s["f" + i]).toEqual(0); - - // f11,12 strings, f13-22 handles, f23-f26 arrays, f27,28 structs - for (var i = 11; i <= 28; i++) - expect(s["f" + i]).toBeNull(); - } - - function testDefaultFieldValues() { - var s = new testStructs.DefaultFieldValues(); - expect(s.f0).toEqual(true); - - // f1 - f12, number type fields - for (var i = 1; i <= 12; i++) - expect(s["f" + i]).toEqual(100); - - // f13,14 "foo" - for (var i = 13; i <= 14; i++) - expect(s["f" + i]).toEqual("foo"); - - // f15,16 a default instance of Rect - var r = new rect.Rect(); - expect(s.f15).toEqual(r); - expect(s.f16).toEqual(r); - } - - function testScopedConstants() { - expect(testStructs.ScopedConstants.TEN).toEqual(10); - expect(testStructs.ScopedConstants.ALSO_TEN).toEqual(10); - - expect(testStructs.ScopedConstants.EType.E0).toEqual(0); - expect(testStructs.ScopedConstants.EType.E1).toEqual(1); - expect(testStructs.ScopedConstants.EType.E2).toEqual(10); - expect(testStructs.ScopedConstants.EType.E3).toEqual(10); - expect(testStructs.ScopedConstants.EType.E4).toEqual(11); - - var s = new testStructs.ScopedConstants(); - expect(s.f0).toEqual(0); - expect(s.f1).toEqual(1); - expect(s.f2).toEqual(10); - expect(s.f3).toEqual(10); - expect(s.f4).toEqual(11); - expect(s.f5).toEqual(10); - expect(s.f6).toEqual(10); - } - - function structEncodeDecode(struct) { - var structClass = struct.constructor; - var builder = new codec.MessageBuilder(1234, structClass.encodedSize); - builder.encodeStruct(structClass, struct); - var message = builder.finish(); - - var messageValidator = new validator.Validator(message); - var err = structClass.validate(messageValidator, codec.kMessageHeaderSize); - expect(err).toEqual(validator.validationError.NONE); - - var reader = new codec.MessageReader(message); - return reader.decodeStruct(structClass); - } - - function testMapKeyTypes() { - var mapFieldsStruct = new testStructs.MapKeyTypes({ - f0: new Map([[true, false], [false, true]]), // map<bool, bool> - f1: new Map([[0, 0], [1, 127], [-1, -128]]), // map<int8, int8> - f2: new Map([[0, 0], [1, 127], [2, 255]]), // map<uint8, uint8> - f3: new Map([[0, 0], [1, 32767], [2, -32768]]), // map<int16, int16> - f4: new Map([[0, 0], [1, 32768], [2, 0xFFFF]]), // map<uint16, uint16> - f5: new Map([[0, 0], [1, 32767], [2, -32768]]), // map<int32, int32> - f6: new Map([[0, 0], [1, 32768], [2, 0xFFFF]]), // map<uint32, uint32> - f7: new Map([[0, 0], [1, 32767], [2, -32768]]), // map<int64, int64> - f8: new Map([[0, 0], [1, 32768], [2, 0xFFFF]]), // map<uint64, uint64> - f9: new Map([[1000.5, -50000], [100.5, 5000]]), // map<float, float> - f10: new Map([[-100.5, -50000], [0, 50000000]]), // map<double, double> - f11: new Map([["one", "two"], ["free", "four"]]), // map<string, string> - }); - var decodedStruct = structEncodeDecode(mapFieldsStruct); - expect(decodedStruct.f0).toEqual(mapFieldsStruct.f0); - expect(decodedStruct.f1).toEqual(mapFieldsStruct.f1); - expect(decodedStruct.f2).toEqual(mapFieldsStruct.f2); - expect(decodedStruct.f3).toEqual(mapFieldsStruct.f3); - expect(decodedStruct.f4).toEqual(mapFieldsStruct.f4); - expect(decodedStruct.f5).toEqual(mapFieldsStruct.f5); - expect(decodedStruct.f6).toEqual(mapFieldsStruct.f6); - expect(decodedStruct.f7).toEqual(mapFieldsStruct.f7); - expect(decodedStruct.f8).toEqual(mapFieldsStruct.f8); - expect(decodedStruct.f9).toEqual(mapFieldsStruct.f9); - expect(decodedStruct.f10).toEqual(mapFieldsStruct.f10); - expect(decodedStruct.f11).toEqual(mapFieldsStruct.f11); - } - - function testMapValueTypes() { - var mapFieldsStruct = new testStructs.MapValueTypes({ - // map<string, array<string>> - f0: new Map([["a", ["b", "c"]], ["d", ["e"]]]), - // map<string, array<string>?> - f1: new Map([["a", null], ["b", ["c", "d"]]]), - // map<string, array<string?>> - f2: new Map([["a", [null]], ["b", [null, "d"]]]), - // map<string, array<string,2>> - f3: new Map([["a", ["1", "2"]], ["b", ["1", "2"]]]), - // map<string, array<array<string, 2>?>> - f4: new Map([["a", [["1", "2"]]], ["b", [null]]]), - // map<string, array<array<string, 2>, 1>> - f5: new Map([["a", [["1", "2"]]]]), - // map<string, Rect?> - f6: new Map([["a", null]]), - // map<string, map<string, string>> - f7: new Map([["a", new Map([["b", "c"]])]]), - // map<string, array<map<string, string>>> - f8: new Map([["a", [new Map([["b", "c"]])]]]), - // map<string, handle> - f9: new Map([["a", 1234]]), - // map<string, array<handle>> - f10: new Map([["a", [1234, 5678]]]), - // map<string, map<string, handle>> - f11: new Map([["a", new Map([["b", 1234]])]]), - }); - var decodedStruct = structEncodeDecode(mapFieldsStruct); - expect(decodedStruct.f0).toEqual(mapFieldsStruct.f0); - expect(decodedStruct.f1).toEqual(mapFieldsStruct.f1); - expect(decodedStruct.f2).toEqual(mapFieldsStruct.f2); - expect(decodedStruct.f3).toEqual(mapFieldsStruct.f3); - expect(decodedStruct.f4).toEqual(mapFieldsStruct.f4); - expect(decodedStruct.f5).toEqual(mapFieldsStruct.f5); - expect(decodedStruct.f6).toEqual(mapFieldsStruct.f6); - expect(decodedStruct.f7).toEqual(mapFieldsStruct.f7); - expect(decodedStruct.f8).toEqual(mapFieldsStruct.f8); - expect(decodedStruct.f9).toEqual(mapFieldsStruct.f9); - expect(decodedStruct.f10).toEqual(mapFieldsStruct.f10); - expect(decodedStruct.f11).toEqual(mapFieldsStruct.f11); - } - - function testFloatNumberValues() { - var decodedStruct = structEncodeDecode(new testStructs.FloatNumberValues); - expect(decodedStruct.f0).toEqual(testStructs.FloatNumberValues.V0); - expect(decodedStruct.f1).toEqual(testStructs.FloatNumberValues.V1); - expect(decodedStruct.f2).toEqual(testStructs.FloatNumberValues.V2); - expect(decodedStruct.f3).toEqual(testStructs.FloatNumberValues.V3); - expect(decodedStruct.f4).toEqual(testStructs.FloatNumberValues.V4); - expect(decodedStruct.f5).toEqual(testStructs.FloatNumberValues.V5); - expect(decodedStruct.f6).toEqual(testStructs.FloatNumberValues.V6); - expect(decodedStruct.f7).toEqual(testStructs.FloatNumberValues.V7); - expect(decodedStruct.f8).toEqual(testStructs.FloatNumberValues.V8); - expect(decodedStruct.f9).toEqual(testStructs.FloatNumberValues.V9); - } - - function testIntegerNumberValues() { - var decodedStruct = structEncodeDecode(new testStructs.IntegerNumberValues); - expect(decodedStruct.f0).toEqual(testStructs.IntegerNumberValues.V0); - expect(decodedStruct.f1).toEqual(testStructs.IntegerNumberValues.V1); - expect(decodedStruct.f2).toEqual(testStructs.IntegerNumberValues.V2); - expect(decodedStruct.f3).toEqual(testStructs.IntegerNumberValues.V3); - expect(decodedStruct.f4).toEqual(testStructs.IntegerNumberValues.V4); - expect(decodedStruct.f5).toEqual(testStructs.IntegerNumberValues.V5); - expect(decodedStruct.f6).toEqual(testStructs.IntegerNumberValues.V6); - expect(decodedStruct.f7).toEqual(testStructs.IntegerNumberValues.V7); - expect(decodedStruct.f8).toEqual(testStructs.IntegerNumberValues.V8); - expect(decodedStruct.f9).toEqual(testStructs.IntegerNumberValues.V9); - expect(decodedStruct.f10).toEqual(testStructs.IntegerNumberValues.V10); - expect(decodedStruct.f11).toEqual(testStructs.IntegerNumberValues.V11); - expect(decodedStruct.f12).toEqual(testStructs.IntegerNumberValues.V12); - expect(decodedStruct.f13).toEqual(testStructs.IntegerNumberValues.V13); - expect(decodedStruct.f14).toEqual(testStructs.IntegerNumberValues.V14); - expect(decodedStruct.f15).toEqual(testStructs.IntegerNumberValues.V15); - expect(decodedStruct.f16).toEqual(testStructs.IntegerNumberValues.V16); - expect(decodedStruct.f17).toEqual(testStructs.IntegerNumberValues.V17); - expect(decodedStruct.f18).toEqual(testStructs.IntegerNumberValues.V18); - expect(decodedStruct.f19).toEqual(testStructs.IntegerNumberValues.V19); - } - - function testUnsignedNumberValues() { - var decodedStruct = - structEncodeDecode(new testStructs.UnsignedNumberValues); - expect(decodedStruct.f0).toEqual(testStructs.UnsignedNumberValues.V0); - expect(decodedStruct.f1).toEqual(testStructs.UnsignedNumberValues.V1); - expect(decodedStruct.f2).toEqual(testStructs.UnsignedNumberValues.V2); - expect(decodedStruct.f3).toEqual(testStructs.UnsignedNumberValues.V3); - expect(decodedStruct.f4).toEqual(testStructs.UnsignedNumberValues.V4); - expect(decodedStruct.f5).toEqual(testStructs.UnsignedNumberValues.V5); - expect(decodedStruct.f6).toEqual(testStructs.UnsignedNumberValues.V6); - expect(decodedStruct.f7).toEqual(testStructs.UnsignedNumberValues.V7); - expect(decodedStruct.f8).toEqual(testStructs.UnsignedNumberValues.V8); - expect(decodedStruct.f9).toEqual(testStructs.UnsignedNumberValues.V9); - expect(decodedStruct.f10).toEqual(testStructs.UnsignedNumberValues.V10); - expect(decodedStruct.f11).toEqual(testStructs.UnsignedNumberValues.V11); - } - - - function testBitArrayValues() { - var bitArraysStruct = new testStructs.BitArrayValues({ - // array<bool, 1> f0; - f0: [true], - // array<bool, 7> f1; - f1: [true, false, true, false, true, false, true], - // array<bool, 9> f2; - f2: [true, false, true, false, true, false, true, false, true], - // array<bool> f3; - f3: [true, false, true, false, true, false, true, false], - // array<array<bool>> f4; - f4: [[true], [false], [true, false], [true, false, true, false]], - // array<array<bool>?> f5; - f5: [[true], null, null, [true, false, true, false]], - // array<array<bool, 2>?> f6; - f6: [[true, false], [true, false], [true, false]], - }); - var decodedStruct = structEncodeDecode(bitArraysStruct); - expect(decodedStruct.f0).toEqual(bitArraysStruct.f0); - expect(decodedStruct.f1).toEqual(bitArraysStruct.f1); - expect(decodedStruct.f2).toEqual(bitArraysStruct.f2); - expect(decodedStruct.f3).toEqual(bitArraysStruct.f3); - expect(decodedStruct.f4).toEqual(bitArraysStruct.f4); - expect(decodedStruct.f5).toEqual(bitArraysStruct.f5); - expect(decodedStruct.f6).toEqual(bitArraysStruct.f6); - } - - testConstructors(); - testNoDefaultFieldValues(); - testDefaultFieldValues(); - testScopedConstants(); - testMapKeyTypes(); - testMapValueTypes(); - testFloatNumberValues(); - testIntegerNumberValues(); - testUnsignedNumberValues(); - testBitArrayValues(); - this.result = "PASS"; -}); diff --git a/mojo/public/js/tests/union_unittest.js b/mojo/public/js/tests/union_unittest.js deleted file mode 100644 index c3ee297..0000000 --- a/mojo/public/js/tests/union_unittest.js +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -define([ - "gin/test/expect", - "mojo/public/interfaces/bindings/tests/test_unions.mojom", - "mojo/public/js/codec", - "mojo/public/js/validator", -], function(expect, - unions, - codec, - validator) { - function testConstructors() { - var u = new unions.PodUnion(); - expect(u.$data).toEqual(null); - expect(u.$tag).toBeUndefined(); - - u.f_uint32 = 32; - - expect(u.f_uint32).toEqual(32); - expect(u.$tag).toEqual(unions.PodUnion.Tags.f_uint32); - - var u = new unions.PodUnion({f_uint64: 64}); - expect(u.f_uint64).toEqual(64); - expect(u.$tag).toEqual(unions.PodUnion.Tags.f_uint64); - expect(function() {var v = u.f_uint32;}).toThrow(); - - expect(function() { - var u = new unions.PodUnion({ - f_uint64: 64, - f_uint32: 32, - }); - }).toThrow(); - - expect(function() { - var u = new unions.PodUnion({ foo: 64 }); }).toThrow(); - - expect(function() { - var u = new unions.PodUnion([1,2,3,4]); }).toThrow(); - } - - function structEncodeDecode(struct) { - var structClass = struct.constructor; - var builder = new codec.MessageBuilder(1234, structClass.encodedSize); - builder.encodeStruct(structClass, struct); - - var message = builder.finish(); - - var messageValidator = new validator.Validator(message); - var err = structClass.validate(messageValidator, codec.kMessageHeaderSize); - expect(err).toEqual(validator.validationError.NONE); - - var reader = new codec.MessageReader(message); - var view = reader.decoder.buffer.dataView; - - return reader.decodeStruct(structClass); - } - - function testBasicEncoding() { - var s = new unions.WrapperStruct({ - pod_union: new unions.PodUnion({ - f_uint64: 64})}); - - var decoded = structEncodeDecode(s); - expect(decoded).toEqual(s); - - var s = new unions.WrapperStruct({ - pod_union: new unions.PodUnion({ - f_bool : true})}); - - var decoded = structEncodeDecode(s); - expect(decoded.pod_union.$tag).toEqual(unions.PodUnion.Tags.f_bool); - // Use toEqual() instead of toBeTruthy() to make sure the field type is - // actually boolean. - expect(decoded.pod_union.f_bool).toEqual(true); - - var s = new unions.WrapperStruct({ - object_union: new unions.ObjectUnion({ - f_dummy: new unions.DummyStruct({ - f_int8: 8})})}); - - var decoded = structEncodeDecode(s); - expect(decoded).toEqual(s); - - var s = new unions.WrapperStruct({ - object_union: new unions.ObjectUnion({ - f_array_int8: [1, 2, 3]})}); - - var decoded = structEncodeDecode(s); - expect(decoded).toEqual(s); - - var s = new unions.WrapperStruct({ - object_union: new unions.ObjectUnion({ - f_map_int8: new Map([ - ["first", 1], - ["second", 2], - ])})}); - - var decoded = structEncodeDecode(s); - expect(decoded).toEqual(s); - - // Encoding a union with no member set is an error. - var s = new unions.WrapperStruct({ - object_union: new unions.ObjectUnion()}); - expect(function() { - structEncodeDecode(s); }).toThrow(); - } - - function testUnionsInArrayEncoding() { - var s = new unions.SmallStruct({ - pod_union_array: [ - new unions.PodUnion({f_uint32: 32}), - new unions.PodUnion({f_uint64: 64}), - ] - }); - - var decoded = structEncodeDecode(s); - expect(decoded).toEqual(s); - } - - function testUnionsInMapEncoding() { - var s = new unions.SmallStruct({ - pod_union_map: new Map([ - ["thirty-two", new unions.PodUnion({f_uint32: 32})], - ["sixty-four", new unions.PodUnion({f_uint64: 64})], - ]) - }); - - var decoded = structEncodeDecode(s); - expect(decoded).toEqual(s); - } - - function testNestedUnionsEncoding() { - var s = new unions.WrapperStruct({ - object_union: new unions.ObjectUnion({ - f_pod_union: new unions.PodUnion({f_uint32: 32}) - })}); - var decoded = structEncodeDecode(s); - expect(decoded).toEqual(s); - } - - function structValidate(struct) { - var structClass = struct.constructor; - var builder = new codec.MessageBuilder(1234, structClass.encodedSize); - builder.encodeStruct(structClass, struct); - - var message = builder.finish(); - - var messageValidator = new validator.Validator(message); - return structClass.validate(messageValidator, codec.kMessageHeaderSize); - } - - function testNullUnionMemberValidation() { - var s = new unions.WrapperStruct({ - object_union: new unions.ObjectUnion({ - f_dummy: null})}); - - var err = structValidate(s); - expect(err).toEqual(validator.validationError.UNEXPECTED_NULL_POINTER); - - var s = new unions.WrapperStruct({ - object_union: new unions.ObjectUnion({ - f_nullable: null})}); - - var err = structValidate(s); - expect(err).toEqual(validator.validationError.NONE); - } - - function testNullUnionValidation() { - var s = new unions.SmallStructNonNullableUnion({ - pod_union: null}); - - var err = structValidate(s); - expect(err).toEqual(validator.validationError.UNEXPECTED_NULL_UNION); - - var s = new unions.WrapperStruct({ - object_union: new unions.ObjectUnion({ - f_pod_union: null}) - }); - - var err = structValidate(s); - expect(err).toEqual(validator.validationError.UNEXPECTED_NULL_UNION); - } - - testConstructors(); - testBasicEncoding(); - testUnionsInArrayEncoding(); - testUnionsInMapEncoding(); - testNestedUnionsEncoding(); - testNullUnionMemberValidation(); - testNullUnionValidation(); - this.result = "PASS"; -}); diff --git a/mojo/public/js/tests/validation_unittest.js b/mojo/public/js/tests/validation_unittest.js index 2a70248..2a07315 100644 --- a/mojo/public/js/tests/validation_unittest.js +++ b/mojo/public/js/tests/validation_unittest.js @@ -278,15 +278,9 @@ define([ expect(testMessagePipe.result).toBe(core.RESULT_OK); endpoint.bind(testMessagePipe.handle1); - var testingController = endpoint.enableTestingMode(); - - var validationError; - testingController.setInvalidIncomingMessageHandler(function(error) { - validationError = error; - }); + var observer = validator.ValidationErrorObserverForTesting.getInstance(); for (var i = 0; i < testFiles.length; i++) { - validationError = noError; var testMessage = readTestMessage(testFiles[i]); var handles = new Array(testMessage.handleCount); @@ -297,8 +291,9 @@ define([ core.WRITE_MESSAGE_FLAG_NONE); expect(writeMessageValue).toBe(core.RESULT_OK); - testingController.waitForNextMessage(); - checkValidationResult(testFiles[i], validationError); + endpoint.waitForNextMessageForTesting(); + checkValidationResult(testFiles[i], observer.lastError); + observer.reset(); } expect(core.close(testMessagePipe.handle0)).toBe(core.RESULT_OK); @@ -333,6 +328,7 @@ define([ testIntegratedMessageHeaderValidation(); testIntegratedResponseMessageValidation(); testIntegratedRequestMessageValidation(); + validator.clearTestingMode(); this.result = "PASS"; }); diff --git a/mojo/public/js/validator.js b/mojo/public/js/validator.js index fee742d..283546d 100644 --- a/mojo/public/js/validator.js +++ b/mojo/public/js/validator.js @@ -28,6 +28,49 @@ define("mojo/public/js/validator", [ }; var NULL_MOJO_POINTER = "NULL_MOJO_POINTER"; + var gValidationErrorObserver = null; + + function reportValidationError(error) { + if (gValidationErrorObserver) { + gValidationErrorObserver.setLastError(error); + } + } + + var ValidationErrorObserverForTesting = (function() { + function Observer() { + this.lastError = validationError.NONE; + this.callback = null; + } + + Observer.prototype.setLastError = function(error) { + this.lastError = error; + if (this.callback) { + this.callback(error); + } + }; + + Observer.prototype.reset = function(error) { + this.lastError = validationError.NONE; + this.callback = null; + }; + + return { + getInstance: function() { + if (!gValidationErrorObserver) { + gValidationErrorObserver = new Observer(); + } + return gValidationErrorObserver; + } + }; + })(); + + function isTestingMode() { + return Boolean(gValidationErrorObserver); + } + + function clearTestingMode() { + gValidationErrorObserver = null; + } function isEnumClass(cls) { return cls instanceof codec.Enum; @@ -180,6 +223,7 @@ define("mojo/public/js/validator", [ return fieldVersion <= structVersion; }; + // TODO(wangjimmy): Add support for v2 messages. Validator.prototype.validateMessageHeader = function() { var err = this.validateStructHeader(0, codec.kMessageHeaderSize); @@ -508,5 +552,9 @@ define("mojo/public/js/validator", [ var exports = {}; exports.validationError = validationError; exports.Validator = Validator; + exports.ValidationErrorObserverForTesting = ValidationErrorObserverForTesting; + exports.reportValidationError = reportValidationError; + exports.isTestingMode = isTestingMode; + exports.clearTestingMode = clearTestingMode; return exports; }); diff --git a/mojo/public/tools/bindings/README.md b/mojo/public/tools/bindings/README.md new file mode 100644 index 0000000..737c7e6 --- /dev/null +++ b/mojo/public/tools/bindings/README.md @@ -0,0 +1,749 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojom IDL and Bindings Generator +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview + +Mojom is the IDL for Mojo bindings interfaces. Given a `.mojom` file, the +[bindings generator](https://cs.chromium.org/chromium/src/mojo/public/tools/bindings) +outputs bindings for all supported languages: **C++**, **JavaScript**, and +**Java**. + +For a trivial example consider the following hypothetical Mojom file we write to +`//services/widget/public/interfaces/frobinator.mojom`: + +``` +module widget.mojom; + +interface Frobinator { + Frobinate(); +}; +``` + +This defines a single [interface](#Interfaces) named `Frobinator` in a +[module](#Modules) named `widget.mojom` (and thus fully qualified in Mojom as +`widget.mojom.Frobinator`.) Note that many interfaces and/or other types of +definitions may be included in a single Mojom file. + +If we add a corresponding GN target to +`//services/widget/public/interfaces/BUILD.gn`: + +``` +import("mojo/public/tools/bindings/mojom.gni") + +mojom("interfaces") { + sources = [ + "frobinator.mojom", + ] +} +``` + +and then build this target: + +``` +ninja -C out/r services/widget/public/interfaces +``` + +we'll find several generated sources in our output directory: + +``` +out/r/gen/services/widget/public/interfaces/frobinator.mojom.cc +out/r/gen/services/widget/public/interfaces/frobinator.mojom.h +out/r/gen/services/widget/public/interfaces/frobinator.mojom.js +out/r/gen/services/widget/public/interfaces/frobinator.mojom.srcjar +... +``` + +Each of these generated source modules includes a set of definitions +representing the Mojom contents within the target language. For more details +regarding the generated outputs please see +[documentation for individual target languages](#Generated-Code-For-Target-Languages). + +## Mojom Syntax + +Mojom IDL allows developers to define **structs**, **unions**, **interfaces**, +**constants**, and **enums**, all within the context of a **module**. These +definitions are used to generate code in the supported target languages at build +time. + +Mojom files may **import** other Mojom files in order to reference their +definitions. + +### Primitive Types +Mojom supports a few basic data types which may be composed into structs or used +for message parameters. + +| Type | Description +|-------------------------------|-------------------------------------------------------| +| `bool` | Boolean type (`true` or `false`.) +| `int8`, `uint8` | Signed or unsigned 8-bit integer. +| `int16`, `uint16` | Signed or unsigned 16-bit integer. +| `int32`, `uint32` | Signed or unsigned 32-bit integer. +| `int64`, `uint64` | Signed or unsigned 64-bit integer. +| `float`, `double` | 32- or 64-bit floating point number. +| `string` | UTF-8 encoded string. +| `array<T>` | Array of any Mojom type *T*; for example, `array<uint8>` or `array<array<string>>`. +| `array<T, N>` | Fixed-length array of any Mojom type *T*. The parameter *N* must be an integral constant. +| `map<S, T>` | Associated array maping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type. +| `handle` | Generic Mojo handle. May be any type of handle, including a wrapped native platform handle. +| `handle<message_pipe>` | Generic message pipe handle. +| `handle<shared_buffer>` | Shared buffer handle. +| `handle<data_pipe_producer>` | Data pipe producer handle. +| `handle<data_pipe_consumer>` | Data pipe consumer handle. +| *`InterfaceType`* | Any user-defined Mojom interface type. This is sugar for a strongly-typed message pipe handle which should eventually be used to make outgoing calls on the interface. +| *`InterfaceType&`* | An interface request for any user-defined Mojom interface type. This is sugar for a more strongly-typed message pipe handle which is expected to receive request messages and should therefore eventually be bound to an implementation of the interface. +| *`associated InterfaceType`* | An associated interface handle. See [Associated Interfaces](#Associated-Interfaces) +| *`associated InterfaceType&`* | An associated interface request. See [Associated Interfaces](#Associated-Interfaces) +| *T*? | An optional (nullable) value. Primitive numeric types (integers, floats, booleans, and enums) are not nullable. All other types are nullable. + +### Modules + +Every Mojom file may optionally specify a single **module** to which it belongs. + +This is used strictly for aggregaging all defined symbols therein within a +common Mojom namespace. The specific impact this has on generated binidngs code +varies for each target language. For example, if the following Mojom is used to +generate bindings: + +``` +module business.stuff; + +interface MoneyGenerator { + GenerateMoney(); +}; +``` + +Generated C++ bindings will define a class interface `MoneyGenerator` in the +`business::stuff` namespace, while Java bindings will define an interface +`MoneyGenerator` in the `org.chromium.business.stuff` package. JavaScript +bindings at this time are unaffected by module declarations. + +**NOTE:** By convention in the Chromium codebase, **all** Mojom files should +declare a module name with at least (and preferrably exactly) one top-level name +as well as an inner `mojom` module suffix. *e.g.*, `chrome.mojom`, +`business.mojom`, *etc.* + +This convention makes it easy to tell which symbols are generated by Mojom when +reading non-Mojom code, and it also avoids namespace collisions in the fairly +common scenario where you have a real C++ or Java `Foo` along with a +corresponding Mojom `Foo` for its serialized representation. + +### Imports + +If your Mojom references definitions from other Mojom files, you must **import** +those files. Import syntax is as follows: + +``` +import "services/widget/public/interfaces/frobinator.mojom"; +``` + +Import paths are always relative to the top-level directory. + +Note that circular imports are **not** supported. + +### Structs + +Structs are defined using the **struct** keyword, and they provide a way to +group related fields together: + +``` cpp +struct StringPair { + string first; + string second; +}; +``` + +Struct fields may be comprised of any of the types listed above in the +[Primitive Types](#Primitive-Types) section. + +Default values may be specified as long as they are constant: + +``` cpp +struct Request { + int32 id = -1; + string details; +}; +``` + +What follows is a fairly +comprehensive example using the supported field types: + +``` cpp +struct StringPair { + string first; + string second; +}; + +enum AnEnum { + YES, + NO +}; + +interface SampleInterface { + DoStuff(); +}; + +struct AllTheThings { + // Note that these types can never be marked nullable! + bool boolean_value; + int8 signed_8bit_value = 42; + uint8 unsigned_8bit_value; + int16 signed_16bit_value; + uint16 unsigned_16bit_value; + int32 signed_32bit_value; + uint32 unsigned_32bit_value; + int64 signed_64bit_value; + uint64 unsigned_64bit_value; + float float_value_32bit; + double float_value_64bit; + AnEnum enum_value = AnEnum.YES; + + // Strings may be nullable. + string? maybe_a_string_maybe_not; + + // Structs may contain other structs. These may also be nullable. + StringPair some_strings; + StringPair? maybe_some_more_strings; + + // In fact structs can also be nested, though in practice you must always make + // such fields nullable -- otherwise messages would need to be infinitely long + // in order to pass validation! + AllTheThings? more_things; + + // Arrays may be templated over any Mojom type, and are always nullable: + array<int32> numbers; + array<int32>? maybe_more_numbers; + + // Arrays of arrays of arrays... are fine. + array<array<array<AnEnum>>> this_works_but_really_plz_stop; + + // The element type may be nullable if it's a type which is allowed to be + // nullable. + array<AllTheThings?> more_maybe_things; + + // Fixed-size arrays get some extra validation on the receiving end to ensure + // that the correct number of elements is always received. + array<uint64, 2> uuid; + + // Maps follow many of the same rules as arrays. Key types may be any + // non-handle, non-collection type, and value types may be any supported + // struct field type. Maps may also be nullable. + map<string, int32> one_map; + map<AnEnum, string>? maybe_another_map; + map<StringPair, AllTheThings?>? maybe_a_pretty_weird_but_valid_map; + map<StringPair, map<int32, array<map<string, string>?>?>?> ridiculous; + + // And finally, all handle types are valid as struct fields and may be + // nullable. Note that interfaces and interface requests (the "Foo" and + // "Foo&" type syntax respectively) are just strongly-typed message pipe + // handles. + handle generic_handle; + handle<data_pipe_consumer> reader; + handle<data_pipe_producer>? maybe_writer; + handle<shared_buffer> dumping_ground; + handle<message_pipe> raw_message_pipe; + SampleInterface? maybe_a_sample_interface_client_pipe; + SampleInterface& non_nullable_sample_interface_request; + SampleInterface&? nullable_sample_interface_request; + associated SampleInterface associated_interface_client; + associated SampleInterface& associated_interface_request; + assocaited SampleInterface&? maybe_another_associated_request; +}; +``` + +For details on how all of these different types translate to usable generated +code, see +[documentation for individual target languages](#Generated-Code-For-Target-Languages). + +### Enumeration Types + +Enumeration types may be defined using the **enum** keyword either directly +within a module or within the namespace of some struct or interface: + +``` +module business.mojom; + +enum Department { + SALES = 0, + DEV, +}; + +struct Employee { + enum Type { + FULL_TIME, + PART_TIME, + }; + + Type type; + // ... +}; +``` + +That that similar to C-style enums, individual values may be explicitly assigned +within an enum definition. By default values are based at zero and incremenet by +1 sequentially. + +The effect of nested definitions on generated bindings varies depending on the +target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages) + +### Constants + +Constants may be defined using the **const** keyword either directly within a +module or within the namespace of some struct or interface: + +``` +module business.mojom; + +const string kServiceName = "business"; + +struct Employee { + const uint64 kInvalidId = 0; + + enum Type { + FULL_TIME, + PART_TIME, + }; + + uint64 id = kInvalidId; + Type type; +}; +``` + +The effect of nested definitions on generated bindings varies depending on the +target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages) + +### Interfaces + +An **interface** is a logical bundle of parameterized request messages. Each +request message may optionally define a parameterized response message. Here's +syntax to define an interface `Foo` with various kinds of requests: + +``` +interface Foo { + // A request which takes no arguments and expects no response. + MyMessage(); + + // A request which has some arguments and expects no response. + MyOtherMessage(string name, array<uint8> bytes); + + // A request which expects a single-argument response. + MyMessageWithResponse(string command) => (bool success); + + // A request which expects a response with multiple arguments. + MyMessageWithMoarResponse(string a, string b) => (int8 c, int8 d); +}; +``` + +Anything which is a valid struct field type (see [Structs](#Structs)) is also a +valid request or response argument type. The type notation is the same for both. + +### Attributes + +Mojom definitions may have their meaning altered by **attributes**, specified +with a syntax similar to Java or C# attributes. There are a handle of +interesting attributes supported today. + +**`[Sync]`** +: The `Sync` attribute may be specified for any interface method which expects + a response. This makes it so that callers of the method can wait + synchronously for a response. See + [Synchronous Calls](/mojo/public/cpp/bindings#Synchronous-Calls) in the C++ + bindings documentation. Note that sync calls are not currently supported in + other target languages. + +**`[Extensible]`** +: The `Extensible` attribute may be specified for any enum definition. This + essentially disables builtin range validation when receiving values of the + enum type in a message, allowing older bindings to tolerate unrecognized + values from newer versions of the enum. + +**`[Native]`** +: The `Native` attribute may be specified for an empty struct declaration to + provide a nominal bridge between Mojo IPC and legacy `IPC::ParamTraits` or + `IPC_STRUCT_TRAITS*` macros. + See [Using Legacy IPC Traits](/ipc#Using-Legacy-IPC-Traits) for more + details. Note support for this attribute is strictly limited to C++ bindings + generation. + +**`[MinVersion=N]`** +: The `MinVersion` attribute is used to specify the version at which a given + field, enum value, interface method, or method parameter was introduced. + See [Versioning](#Versioning) for more details. + +## Generated Code For Target Languages + +When the bindings generator successfully processes an input Mojom file, it emits +corresponding code for each supported target language. For more details on how +Mojom concepts translate to a given target langauge, please refer to the +bindings API documentation for that language: + +* [C++ Bindings](/mojo/public/cpp/bindings) +* [JavaScript Bindings](/mojo/public/js) +* [Java Bindings](/mojo/public/java/bindings) + +## Message Validation + +Regardless of target language, all interface messages are validated during +deserialization before they are dispatched to a receiving implementation of the +interface. This helps to ensure consitent validation across interfaces without +leaving the burden to developers and security reviewers every time a new message +is added. + +If a message fails validation, it is never dispatched. Instead a **connection +error** is raised on the binding object (see +[C++ Connection Errors](/mojo/public/cpp/bindings#Connection-Errors), +[Java Connection Errors](/mojo/public/java/bindings#Connection-Errors), or +[JavaScript Connection Errors](/mojo/public/js#Connection-Errors) for details.) + +Some baseline level of validation is done automatically for primitive Mojom +types. + +### Non-Nullable Objects + +Mojom fields or parameter values (*e.g.*, structs, interfaces, arrays, *etc.*) +may be marked nullable in Mojom definitions (see +[Primitive Types](#Primitive-Types).) If a field or parameter is **not** marked +nullable but a message is received with a null value in its place, that message +will fail validation. + +### Enums + +Enums declared in Mojom are automatically validated against the range of legal +values. For example if a Mojom declares the enum: + +``` cpp +enum AdvancedBoolean { + TRUE = 0, + FALSE = 1, + FILE_NOT_FOUND = 2, +}; +``` + +and a message is received with the integral value 3 (or anything other than 0, +1, or 2) in place of some `AdvancedBoolean` field or parameter, the message will +fail validation. + +*** note +NOTE: It's possible to avoid this type of validation error by explicitly marking +an enum as [Extensible](#Attributes) if you anticipate your enum being exchanged +between two different versions of the binding interface. See +[Versioning](#Versioning). +*** + +### Other failures + +There are a host of internal validation errors that may occur when a malformed +message is received, but developers should not be concerned with these +specifically; in general they can only result from internal bindings bugs, +compromised processes, or some remote endpoint making a dubious effort to +manually encode their own bindings messages. + +### Custom Validation + +It's also possible for developers to define custom validation logic for specific +Mojom struct types by exploiting the +[type mapping](/mojo/public/cpp/bindings#Type-Mapping) system for C++ bindings. +Messages rejected by custom validation logic trigger the same validation failure +behavior as the built-in type validation routines. + +## Associated Interfaces + +As mentioned in the [Primitive Types](#Primitive-Types) section above, interface +and interface request fields and parameters may be marked as `associated`. This +essentially means that they are piggy-backed on some other interface's message +pipe. + +Because individual interface message pipes operate independently there can be no +relative ordering guarantees among them. Associated interfaces are useful when +one interface needs to guarantee strict FIFO ordering with respect to one or +more other interfaces, as they allow interfaces to share a single pipe. + +Currenly associated interfaces are only supported in generated C++ bindings. +See the documentation for +[C++ Associated Interfaces](/mojo/public/cpp/bindings#Associated-Interfaces). + +## Versioning + +### Overview + +*** note +**NOTE:** You don't need to worry about versioning if you don't care about +backwards compatibility. Specifically, all parts of Chrome are updated +atomically today and there is not yet any possibility of any two Chrome +processes communicating with two different versions of any given Mojom +interface. +*** + +Services extend their interfaces to support new features over time, and clients +want to use those new features when they are available. If services and clients +are not updated at the same time, it's important for them to be able to +communicate with each other using different snapshots (versions) of their +interfaces. + +This document shows how to extend Mojom interfaces in a backwards-compatible +way. Changing interfaces in a non-backwards-compatible way is not discussed, +because in that case communication between different interface versions is +impossible anyway. + +### Versioned Structs + +You can use the `MinVersion` [attribute](#Attributes) to indicate from which +version a struct field is introduced. Assume you have the following struct: + +``` cpp +struct Employee { + uint64 employee_id; + string name; +}; +``` + +and you would like to add a birthday field. You can do: + +``` cpp +struct Employee { + uint64 employee_id; + string name; + [MinVersion=1] Date? birthday; +}; +``` + +By default, fields belong to version 0. New fields must be appended to the +struct definition (*i.e*., existing fields must not change **ordinal value**) +with the `MinVersion` attribute set to a number greater than any previous +existing versions. + +**Ordinal value** refers to the relative positional layout of a struct's fields +(and an interface's methods) when encoded in a message. Implicitly, ordinal +numbers are assigned to fields according to lexical position. In the example +above, `employee_id` has an ordinal value of 0 and `name` has an ordinal value +of 1. + +Ordinal values can be specified explicitly using `**@**` notation, subject to +the following hard constraints: + +* For any given struct or interface, if any field or method explicitly specifies + an ordinal value, all fields or methods must explicitly specify an ordinal + value. +* For an *N*-field struct or *N*-method interface, the set of explicitly + assigned ordinal values must be limited to the range *[0, N-1]*. + +You may reorder fields, but you must ensure that the ordinal values of existing +fields remain unchanged. For example, the following struct remains +backwards-compatible: + +``` cpp +struct Employee { + uint64 employee_id@0; + [MinVersion=1] Date? birthday@2; + string name@1; +}; +``` + +*** note +**NOTE:** Newly added fields of Mojo object or handle types MUST be nullable. +See [Primitive Types](#Primitive-Types). +*** + +### Versioned Interfaces + +There are two dimensions on which an interface can be extended + +**Appending New Parameters To Existing Methods** +: Parameter lists are treated as structs internally, so all the rules of + versioned structs apply to method parameter lists. The only difference is + that the version number is scoped to the whole interface rather than to any + individual parameter list. + + Please note that adding a response to a message which did not previously + expect a response is a not a backwards-compatible change. + +**Appending New Methods** +: Similarly, you can reorder methods with explicit ordinal values as long as + the ordinal values of existing methods are unchanged. + +For example: + +``` cpp +// Old version: +interface HumanResourceDatabase { + AddEmployee(Employee employee) => (bool success); + QueryEmployee(uint64 id) => (Employee? employee); +}; + +// New version: +interface HumanResourceDatabase { + AddEmployee(Employee employee) => (bool success); + + QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print) + => (Employee? employee, + [MinVersion=1] array<uint8>? finger_print); + + [MinVersion=1] + AttachFingerPrint(uint64 id, array<uint8> finger_print) + => (bool success); +}; +``` + +Similar to [versioned structs](#Versioned-Structs), when you pass the parameter +list of a request or response method to a destination using an older version of +an interface, unrecognized fields are silently discarded. However, if the method +call itself is not recognized, it is considered a validation error and the +receiver will close its end of the interface pipe. For example, if a client on +version 1 of the above interface sends an `AttachFingerPrint` request to an +implementation of version 0, the client will be disconnected. + +Bindings target languages that support versioning expose means to query or +assert the remote version from a client handle (*e.g.*, an +`InterfacePtr<T>` in C++ bindings.) + +See +[C++ Versioning Considerations](/mojo/public/cpp/bindings#Versioning-Considerations) +and [Java Versioning Considerations](/mojo/public/java/bindings#Versioning-Considerations) + +### Versioned Enums + +**By default, enums are non-extensible**, which means that generated message +validation code does not expect to see new values in the future. When an unknown +value is seen for a non-extensible enum field or parameter, a validation error +is raised. + +If you want an enum to be extensible in the future, you can apply the +`[Extensible]` [attribute](#Attributes): + +``` cpp +[Extensible] +enum Department { + SALES, + DEV, +}; +``` + +And later you can extend this enum without breaking backwards compatibility: + +``` cpp +[Extensible] +enum Department { + SALES, + DEV, + [MinVersion=1] RESEARCH, +}; +``` + +*** note +**NOTE:** For versioned enum definitions, the use of a `[MinVersion]` attribute +is strictly for documentation purposes. It has no impact on the generated code. +*** + +With extensible enums, bound interface implementations may receive unknown enum +values and will need to deal with them gracefully. See +[C++ Versioning Considerations](/mojo/public/cpp/bindings#Versioning-Considerations) +for details. + +## Grammar Reference + +Below is the (BNF-ish) context-free grammar of the Mojom language: + +``` +MojomFile = StatementList +StatementList = Statement StatementList | Statement +Statement = ModuleStatement | ImportStatement | Definition + +ModuleStatement = AttributeSection "module" Identifier ";" +ImportStatement = "import" StringLiteral ";" +Definition = Struct Union Interface Enum Const + +AttributeSection = "[" AttributeList "]" +AttributeList = <empty> | NonEmptyAttributeList +NonEmptyAttributeList = Attribute + | Attribute "," NonEmptyAttributeList +Attribute = Name + | Name "=" Name + | Name "=" Literal + +Struct = AttributeSection "struct" Name "{" StructBody "}" ";" + | AttributeSection "struct" Name ";" +StructBody = <empty> + | StructBody Const + | StructBody Enum + | StructBody StructField +StructField = AttributeSection TypeSpec Name Orginal Default ";" + +Union = AttributeSection "union" Name "{" UnionBody "}" ";" +UnionBody = <empty> | UnionBody UnionField +UnionField = AttributeSection TypeSpec Name Ordinal ";" + +Interface = AttributeSection "interface" Name "{" InterfaceBody "}" ";" +InterfaceBody = <empty> + | InterfaceBody Const + | InterfaceBody Enum + | InterfaceBody Method +Method = AttributeSection Name Ordinal "(" ParamterList ")" Response ";" +ParameterList = <empty> | NonEmptyParameterList +NonEmptyParameterList = Parameter + | Parameter "," NonEmptyParameterList +Parameter = AttributeSection TypeSpec Name Ordinal +Response = <empty> | "=>" "(" ParameterList ")" + +TypeSpec = TypeName "?" | TypeName +TypeName = BasicTypeName + | Array + | FixedArray + | Map + | InterfaceRequest +BasicTypeName = Identifier | "associated" Identifier | HandleType | NumericType +NumericType = "bool" | "int8" | "uint8" | "int16" | "uint16" | "int32" + | "uint32" | "int64" | "uint64" | "float" | "double" +HandleType = "handle" | "handle" "<" SpecificHandleType ">" +SpecificHandleType = "message_pipe" + | "shared_buffer" + | "data_pipe_consumer" + | "data_pipe_producer" +Array = "array" "<" TypeSpec ">" +FixedArray = "array" "<" TypeSpec "," IntConstDec ">" +Map = "map" "<" Identifier "," TypeSpec ">" +InterfaceRequest = Identifier "&" | "associated" Identifier "&" + +Ordinal = <empty> | OrdinalValue + +Default = <empty> | "=" Constant + +Enum = AttributeSection "enum" Name "{" NonEmptyEnumValueList "}" ";" + | AttributeSection "enum" Name "{" NonEmptyEnumValueList "," "}" ";" +NonEmptyEnumValueList = EnumValue | NonEmptyEnumValueList "," EnumValue +EnumValue = AttributeSection Name + | AttributeSection Name "=" Integer + | AttributeSection Name "=" Identifier + +Const = "const" TypeSpec Name "=" Constant ";" + +Constant = Literal | Identifier ";" + +Identifier = Name | Name "." Identifier + +Literal = Integer | Float | "true" | "false" | "default" | StringLiteral + +Integer = IntConst | "+" IntConst | "-" IntConst +IntConst = IntConstDec | IntConstHex + +Float = FloatConst | "+" FloatConst | "-" FloatConst + +; The rules below are for tokens matched strictly according to the given regexes + +Identifier = /[a-zA-Z_][0-9a-zA-Z_]*/ +IntConstDec = /0|(1-9[0-9]*)/ +IntConstHex = /0[xX][0-9a-fA-F]+/ +OrdinalValue = /@(0|(1-9[0-9]*))/ +FloatConst = ... # Imagine it's close enough to C-style float syntax. +StringLiteral = ... # Imagine it's close enough to C-style string literals, including escapes. +``` + +## Additional Documentation + +[Mojom Message Format](https://docs.google.com/document/d/13pv9cFh5YKuBggDBQ1-AL8VReF-IYpFOFpRfvWFrwio/edit) +: Describes the wire format used by Mojo bindings interfaces over message + pipes. + +[Input Format of Mojom Message Validation Tests](https://docs.google.com/document/d/1-y-2IYctyX2NPaLxJjpJfzVNWCC2SR2MJAD9MpIytHQ/edit) +: Describes a text format used to facilitate bindings message validation + tests. diff --git a/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/mojo/public/tools/bindings/chromium_bindings_configuration.gni index 831157f..ca36723 100644 --- a/mojo/public/tools/bindings/chromium_bindings_configuration.gni +++ b/mojo/public/tools/bindings/chromium_bindings_configuration.gni @@ -21,6 +21,7 @@ _typemap_imports = [ "//device/gamepad/public/interfaces/typemaps.gni", "//device/generic_sensor/public/interfaces/typemaps.gni", "//device/usb/public/interfaces/typemaps.gni", + "//extensions/common/typemaps.gni", "//gpu/ipc/common/typemaps.gni", "//media/capture/mojo/typemaps.gni", "//media/mojo/interfaces/typemaps.gni", @@ -31,6 +32,7 @@ _typemap_imports = [ "//services/resource_coordinator/public/cpp/typemaps.gni", "//services/service_manager/public/cpp/typemaps.gni", "//services/ui/gpu/interfaces/typemaps.gni", + "//services/ui/public/interfaces/cursor/typemaps.gni", "//services/ui/public/interfaces/ime/typemaps.gni", "//services/video_capture/public/interfaces/typemaps.gni", "//skia/public/interfaces/typemaps.gni", @@ -40,6 +42,7 @@ _typemap_imports = [ "//ui/events/devices/mojo/typemaps.gni", "//ui/events/mojo/typemaps.gni", "//ui/gfx/typemaps.gni", + "//ui/latency/mojo/typemaps.gni", "//ui/message_center/mojo/typemaps.gni", "//url/mojo/typemaps.gni", ] diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl index a23b107..aba1838 100644 --- a/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl +++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl @@ -169,14 +169,14 @@ bool {{proxy_name}}::{{method.name}}( "&serialization_context")}} bool result = false; - mojo::MessageReceiver* responder = + std::unique_ptr<mojo::MessageReceiver> responder( new {{class_name}}_{{method.name}}_HandleSyncResponse( &result {%- for param in method.response_parameters -%} , param_{{param.name}} -{%- endfor %}); - if (!receiver_->AcceptWithResponder(builder.message(), responder)) - delete responder; +{%- endfor %})); + ignore_result(receiver_->AcceptWithResponder(builder.message(), + std::move(responder))); return result; } {%- endif %} @@ -200,15 +200,15 @@ void {{proxy_name}}::{{method.name}}( "&serialization_context")}} {%- if method.response_parameters != None %} - mojo::MessageReceiver* responder = - new {{class_name}}_{{method.name}}_ForwardToCallback(std::move(callback)); - if (!receiver_->AcceptWithResponder(builder.message(), responder)) - delete responder; + std::unique_ptr<mojo::MessageReceiver> responder( + new {{class_name}}_{{method.name}}_ForwardToCallback( + std::move(callback))); + ignore_result(receiver_->AcceptWithResponder(builder.message(), + std::move(responder))); {%- else %} - bool ok = receiver_->Accept(builder.message()); - // This return value may be ignored as !ok implies the Connector has + // This return value may be ignored as false implies the Connector has // encountered an error, which will be visible through other means. - ALLOW_UNUSED_LOCAL(ok); + ignore_result(receiver_->Accept(builder.message())); {%- endif %} } {%- endfor %} @@ -226,10 +226,10 @@ class {{class_name}}_{{method.name}}_ProxyToResponder { static {{class_name}}::{{method.name}}Callback CreateCallback( uint64_t request_id, bool is_sync, - mojo::MessageReceiverWithStatus* responder) { + std::unique_ptr<mojo::MessageReceiverWithStatus> responder) { std::unique_ptr<{{class_name}}_{{method.name}}_ProxyToResponder> proxy( new {{class_name}}_{{method.name}}_ProxyToResponder( - request_id, is_sync, responder)); + request_id, is_sync, std::move(responder))); return base::Bind(&{{class_name}}_{{method.name}}_ProxyToResponder::Run, base::Passed(&proxy)); } @@ -245,17 +245,17 @@ class {{class_name}}_{{method.name}}_ProxyToResponder { #endif // If the Callback was dropped then deleting the responder will close // the pipe so the calling application knows to stop waiting for a reply. - delete responder_; + responder_ = nullptr; } private: {{class_name}}_{{method.name}}_ProxyToResponder( uint64_t request_id, bool is_sync, - mojo::MessageReceiverWithStatus* responder) + std::unique_ptr<mojo::MessageReceiverWithStatus> responder) : request_id_(request_id), is_sync_(is_sync), - responder_(responder) { + responder_(std::move(responder)) { } void Run( @@ -264,7 +264,7 @@ class {{class_name}}_{{method.name}}_ProxyToResponder { uint64_t request_id_; bool is_sync_; - mojo::MessageReceiverWithStatus* responder_; + std::unique_ptr<mojo::MessageReceiverWithStatus> responder_; DISALLOW_COPY_AND_ASSIGN({{class_name}}_{{method.name}}_ProxyToResponder); }; @@ -285,12 +285,10 @@ void {{class_name}}_{{method.name}}_ProxyToResponder::Run( {{build_message(response_params_struct, "in_%s", params_description, "&serialization_context")}} - bool ok = responder_->Accept(builder.message()); - ALLOW_UNUSED_LOCAL(ok); - // TODO(darin): !ok returned here indicates a malformed message, and that may - // be good reason to close the connection. However, we don't have a way to do - // that from here. We should add a way. - delete responder_; + ignore_result(responder_->Accept(builder.message())); + // TODO(darin): Accept() returning false indicates a malformed message, and + // that may be good reason to close the connection. However, we don't have a + // way to do that from here. We should add a way. responder_ = nullptr; } {%- endif -%} @@ -334,7 +332,7 @@ bool {{class_name}}StubDispatch::Accept( bool {{class_name}}StubDispatch::AcceptWithResponder( {{interface.name}}* impl, mojo::Message* message, - mojo::MessageReceiverWithStatus* responder) { + std::unique_ptr<mojo::MessageReceiverWithStatus> responder) { {%- if interface.methods %} switch (message->header()->name) { {%- for method in interface.methods %} @@ -350,7 +348,8 @@ bool {{class_name}}StubDispatch::AcceptWithResponder( {{class_name}}::{{method.name}}Callback callback = {{class_name}}_{{method.name}}_ProxyToResponder::CreateCallback( message->request_id(), - message->has_flag(mojo::Message::kFlagIsSync), responder); + message->has_flag(mojo::Message::kFlagIsSync), + std::move(responder)); // A null |impl| means no implementation was bound. assert(impl); TRACE_EVENT0("mojom", "{{class_name}}::{{method.name}}"); diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl index 9f01348..79ab46f 100644 --- a/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl +++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl @@ -1,9 +1,10 @@ class {{export_attribute}} {{interface.name}}StubDispatch { public: static bool Accept({{interface.name}}* impl, mojo::Message* message); - static bool AcceptWithResponder({{interface.name}}* impl, - mojo::Message* message, - mojo::MessageReceiverWithStatus* responder); + static bool AcceptWithResponder( + {{interface.name}}* impl, + mojo::Message* message, + std::unique_ptr<mojo::MessageReceiverWithStatus> responder); }; template <typename ImplRefTraits = @@ -28,11 +29,11 @@ class {{interface.name}}Stub bool AcceptWithResponder( mojo::Message* message, - mojo::MessageReceiverWithStatus* responder) override { + std::unique_ptr<mojo::MessageReceiverWithStatus> responder) override { if (ImplRefTraits::IsNull(sink_)) return false; return {{interface.name}}StubDispatch::AcceptWithResponder( - ImplRefTraits::GetRawPointer(&sink_), message, responder); + ImplRefTraits::GetRawPointer(&sink_), message, std::move(responder)); } private: diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl index acdad5e..804a46b 100644 --- a/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl +++ b/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl @@ -40,6 +40,7 @@ namespace {{variant}} { #include <utility> #include "base/callback.h" +#include "base/macros.h" #include "base/optional.h" #include "mojo/public/cpp/bindings/associated_interface_ptr.h" #include "mojo/public/cpp/bindings/associated_interface_ptr_info.h" diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl index 9e6e46f..7ad9b4e 100644 --- a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl +++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl @@ -73,7 +73,7 @@ class {{export_attribute}} {{struct.name}} { UserType* output) { return mojo::internal::StructDeserializeImpl< {{struct.name}}::DataView, {{serialization_result_type}}>( - input, output); + input, output, Validate); } {#--- Struct members #} @@ -83,8 +83,11 @@ class {{export_attribute}} {{struct.name}} { {{type}} {{name}}; {%- endfor %} -{%- if struct|contains_move_only_members %} private: + static bool Validate(const void* data, + mojo::internal::ValidationContext* validation_context); + +{%- if struct|contains_move_only_members %} DISALLOW_COPY_AND_ASSIGN({{struct.name}}); {%- endif %} }; diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl index e75543f..ab8c22d 100644 --- a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl +++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl @@ -31,3 +31,9 @@ size_t {{struct.name}}::Hash(size_t seed) const { return seed; } {%- endif %} + +bool {{struct.name}}::Validate( + const void* data, + mojo::internal::ValidationContext* validation_context) { + return Data_::Validate(data, validation_context); +} diff --git a/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl index 54e2d4e..11e319c 100644 --- a/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl +++ b/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl @@ -93,7 +93,7 @@ {%- if method.response_parameters != None %} case k{{interface.name}}_{{method.name}}_Name: var params = reader.decodeStruct({{interface.name}}_{{method.name}}_Params); - return this.{{method.name|stylize_method}}( + this.{{method.name|stylize_method}}( {%- for parameter in method.parameters -%} params.{{parameter.name}}{% if not loop.last %}, {% endif -%} {%- endfor %}).then(function(response) { @@ -111,10 +111,11 @@ params.{{parameter.name}}{% if not loop.last %}, {% endif -%} var message = builder.finish(); responder.accept(message); }); + return true; {%- endif %} {%- endfor %} default: - return Promise.reject(Error("Unhandled message: " + reader.messageName)); + return false; } }; diff --git a/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl b/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl index 3ce4ab6..3637b19 100644 --- a/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl +++ b/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl @@ -2,26 +2,69 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +{%- if use_new_js_bindings %} + +'use strict'; + +(function() { + var mojomId = '{{module.path}}'; + if (mojo.internal.isMojomLoaded(mojomId)) { + console.warn('The following mojom is loaded multiple times: ' + mojomId); + return; + } + mojo.internal.markMojomLoaded(mojomId); + + // TODO(yzshen): Define these aliases to minimize the differences between the + // old/new modes. Remove them when the old mode goes away. + var bindings = mojo; + var codec = mojo.internal; + var validator = mojo.internal; + +{%- for import in imports %} + var {{import.unique_name}} = + mojo.internal.exposeNamespace('{{import.module.namespace}}'); + if (mojo.config.autoLoadMojomDeps) { + mojo.internal.loadMojomIfNecessary( + '{{import.module.path}}', + new URL( + '{{import.module|get_relative_path(module)}}.js', + document.currentScript.src).href); + } +{%- endfor %} + +{% include "module_definition.tmpl" %} +})(); + +{%- else %} + define("{{module.path}}", [ -{%- if module.path != "mojo/public/interfaces/bindings/interface_control_messages.mojom" %} +{%- if module.path != + "mojo/public/interfaces/bindings/interface_control_messages.mojom" and + module.path != + "mojo/public/interfaces/bindings/pipe_control_messages.mojom" %} "mojo/public/js/bindings", -{%- endif %} +{%- endif %} "mojo/public/js/codec", "mojo/public/js/core", "mojo/public/js/validator", -{%- for import in imports %} +{%- for import in imports %} "{{import.module.path}}", -{%- endfor %} +{%- endfor %} ], function( -{%- if module.path != "mojo/public/interfaces/bindings/interface_control_messages.mojom" -%} +{%- if module.path != + "mojo/public/interfaces/bindings/interface_control_messages.mojom" and + module.path != + "mojo/public/interfaces/bindings/pipe_control_messages.mojom" -%} bindings, {% endif -%} codec, core, validator -{%- for import in imports -%} +{%- for import in imports -%} , {{import.unique_name}} -{%- endfor -%} +{%- endfor -%} ) { {%- include "module_definition.tmpl" %} return exports; }); + +{%- endif %} diff --git a/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl index ddfef72..a119ee9 100644 --- a/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl +++ b/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl @@ -1,5 +1,5 @@ {#--- Constants #} -{%- for constant in module.constants %} +{%- for constant in module.constants %} var {{constant.name}} = {{constant.value|expression_to_text}}; {%- endfor %} @@ -25,8 +25,13 @@ {%- include "interface_definition.tmpl" %} {%- endfor %} +{%- if use_new_js_bindings %} + var exports = mojo.internal.exposeNamespace("{{module.namespace}}"); +{%- else %} var exports = {}; -{%- for constant in module.constants %} +{%- endif %} + +{%- for constant in module.constants %} exports.{{constant.name}} = {{constant.name}}; {%- endfor %} {%- for enum in enums %} @@ -41,10 +46,4 @@ {%- for interface in interfaces %} exports.{{interface.name}} = {{interface.name}}; exports.{{interface.name}}Ptr = {{interface.name}}Ptr; -{#--- Interface Client #} -{%- if interface.client in interfaces|map(attribute='name') %} - exports.{{interface.name}}.client = {{interface.client}}; -{%- elif interface.client in imported_interfaces %} - exports.{{interface.name}}.client = {{imported_interfaces[interface.client]}}; -{%- endif %} {%- endfor %} diff --git a/mojo/public/tools/bindings/generators/mojom_js_generator.py b/mojo/public/tools/bindings/generators/mojom_js_generator.py index 0eedb31..ab9635e 100644 --- a/mojo/public/tools/bindings/generators/mojom_js_generator.py +++ b/mojo/public/tools/bindings/generators/mojom_js_generator.py @@ -7,6 +7,7 @@ import mojom.generate.generator as generator import mojom.generate.module as mojom import mojom.generate.pack as pack +import os from mojom.generate.template_expander import UseJinja _kind_to_javascript_default_value = { @@ -324,6 +325,9 @@ def IsAnyHandleOrInterfaceField(field): def IsEnumField(field): return mojom.IsEnumKind(field.kind) +def GetRelativePath(module, base_module): + return os.path.relpath(module.path, os.path.dirname(base_module.path)) + class Generator(generator.Generator): @@ -348,6 +352,7 @@ class Generator(generator.Generator): "is_union_field": IsUnionField, "js_type": JavaScriptType, "payload_size": JavaScriptPayloadSize, + "get_relative_path": GetRelativePath, "stylize_method": generator.StudlyCapsToCamel, "union_decode_snippet": JavaScriptUnionDecodeSnippet, "union_encode_snippet": JavaScriptUnionEncodeSnippet, @@ -368,6 +373,7 @@ class Generator(generator.Generator): "module": self.module, "structs": self.GetStructs() + self.GetStructsFromMethods(), "unions": self.GetUnions(), + "use_new_js_bindings": self.use_new_js_bindings, "interfaces": self.GetInterfaces(), "imported_interfaces": self.GetImportedInterfaces(), } diff --git a/mojo/public/tools/bindings/mojom.gni b/mojo/public/tools/bindings/mojom.gni index 2466636..4a244fb 100644 --- a/mojo/public/tools/bindings/mojom.gni +++ b/mojo/public/tools/bindings/mojom.gni @@ -134,6 +134,13 @@ if (enable_mojom_typemapping) { # cpp_only (optional) # If set to true, only the C++ bindings targets will be generated. # +# use_new_js_bindings (optional) +# If set to true, the generated JS code will use the new module loading +# approach and the core API exposed by Web IDL. +# +# TODO(yzshen): Switch all existing users to use_new_js_bindings=true and +# remove the old mode. +# # The following parameters are used to support the component build. They are # needed so that bindings which are linked with a component can use the same # export settings for classes. The first three are for the chromium variant, and @@ -434,6 +441,11 @@ template("mojom") { if (defined(invoker.use_once_callback) && invoker.use_once_callback) { args += [ "--use_once_callback" ] } + + if (defined(invoker.use_new_js_bindings) && + invoker.use_new_js_bindings) { + args += [ "--use_new_js_bindings" ] + } } } diff --git a/mojo/public/tools/bindings/mojom_bindings_generator.py b/mojo/public/tools/bindings/mojom_bindings_generator.py index a5fb51b..a9650d7 100755 --- a/mojo/public/tools/bindings/mojom_bindings_generator.py +++ b/mojo/public/tools/bindings/mojom_bindings_generator.py @@ -7,7 +7,7 @@ import argparse -import importlib +import imp import json import os import pprint @@ -43,9 +43,9 @@ from mojom.parse.parser import Parse _BUILTIN_GENERATORS = { - "c++": "mojom_cpp_generator", - "javascript": "mojom_js_generator", - "java": "mojom_java_generator", + "c++": "mojom_cpp_generator.py", + "javascript": "mojom_js_generator.py", + "java": "mojom_java_generator.py", } @@ -57,11 +57,14 @@ def LoadGenerators(generators_string): generators = {} for generator_name in [s.strip() for s in generators_string.split(",")]: language = generator_name.lower() - if language not in _BUILTIN_GENERATORS: + if language in _BUILTIN_GENERATORS: + generator_name = os.path.join(script_dir, "generators", + _BUILTIN_GENERATORS[language]) + else: print "Unknown generator name %s" % generator_name sys.exit(1) - generator_module = importlib.import_module( - "generators.%s" % _BUILTIN_GENERATORS[language]) + generator_module = imp.load_source(os.path.basename(generator_name)[:-3], + generator_name) generators[language] = generator_module return generators @@ -164,6 +167,7 @@ class MojomProcessor(object): variant=args.variant, bytecode_path=args.bytecode_path, for_blink=args.for_blink, use_once_callback=args.use_once_callback, + use_new_js_bindings=args.use_new_js_bindings, export_attribute=args.export_attribute, export_header=args.export_header, generate_non_variant_code=args.generate_non_variant_code) @@ -295,6 +299,10 @@ def main(): "--use_once_callback", action="store_true", help="Use base::OnceCallback instead of base::RepeatingCallback.") generate_parser.add_argument( + "--use_new_js_bindings", action="store_true", + help="Use the new module loading approach and the core API exposed by " + "Web IDL. This option only affects the JavaScript bindings.") + generate_parser.add_argument( "--export_attribute", type=str, default="", help="Optional attribute to specify on class declaration to export it " "for the component build.") diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/generator.py b/mojo/public/tools/bindings/pylib/mojom/generate/generator.py index e4ab373..0e64af7 100644 --- a/mojo/public/tools/bindings/pylib/mojom/generate/generator.py +++ b/mojo/public/tools/bindings/pylib/mojom/generate/generator.py @@ -38,8 +38,8 @@ class Generator(object): # files to stdout. def __init__(self, module, output_dir=None, typemap=None, variant=None, bytecode_path=None, for_blink=False, use_once_callback=False, - export_attribute=None, export_header=None, - generate_non_variant_code=False): + use_new_js_bindings=False, export_attribute=None, + export_header=None, generate_non_variant_code=False): self.module = module self.output_dir = output_dir self.typemap = typemap or {} @@ -47,6 +47,7 @@ class Generator(object): self.bytecode_path = bytecode_path self.for_blink = for_blink self.use_once_callback = use_once_callback + self.use_new_js_bindings = use_new_js_bindings self.export_attribute = export_attribute self.export_header = export_header self.generate_non_variant_code = generate_non_variant_code |