aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHidehiko Abe <hidehiko@google.com>2018-04-19 20:42:46 +0900
committerHidehiko Abe <hidehiko@google.com>2018-04-20 11:11:59 +0900
commit991e618ea063cc28060d6baa0c0d6ffa3ad29452 (patch)
treed7c62aca68333ccc7dc58e356d515dd01c734a7f
parent1e4a1e50aed7593adac78af6f319adb67f03d7bb (diff)
downloadlibmojo-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
-rw-r--r--Android.bp127
-rw-r--r--ipc/ipc_sync_message.h11
-rw-r--r--mojo/README.md145
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/HandleMock.java11
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java5
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java126
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java18
-rw-r--r--mojo/android/system/base_run_loop.cc1
-rw-r--r--mojo/android/system/core_impl.cc58
-rw-r--r--mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java69
-rw-r--r--mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java9
-rw-r--r--mojo/android/system/watcher_impl.cc9
-rw-r--r--mojo/common/data_pipe_drainer.cc4
-rw-r--r--mojo/common/data_pipe_drainer.h4
-rw-r--r--mojo/common/data_pipe_utils.cc9
-rw-r--r--mojo/edk/embedder/README.md29
-rw-r--r--mojo/edk/embedder/configuration.h4
-rw-r--r--mojo/edk/embedder/embedder_unittest.cc89
-rw-r--r--mojo/edk/embedder/entrypoints.cc73
-rw-r--r--mojo/edk/js/core.cc61
-rw-r--r--mojo/edk/js/drain_data.cc4
-rw-r--r--mojo/edk/js/drain_data.h4
-rw-r--r--mojo/edk/js/tests/BUILD.gn1
-rw-r--r--mojo/edk/js/tests/js_to_cpp_tests.cc5
-rw-r--r--mojo/edk/js/tests/run_js_unittests.cc26
-rw-r--r--mojo/edk/js/waiting_callback.cc4
-rw-r--r--mojo/edk/js/waiting_callback.h4
-rw-r--r--mojo/edk/system/BUILD.gn22
-rw-r--r--mojo/edk/system/awakable.h34
-rw-r--r--mojo/edk/system/awakable_list.cc87
-rw-r--r--mojo/edk/system/awakable_list.h72
-rw-r--r--mojo/edk/system/awakable_list_unittest.cc356
-rw-r--r--mojo/edk/system/configuration.cc1
-rw-r--r--mojo/edk/system/core.cc236
-rw-r--r--mojo/edk/system/core.h47
-rw-r--r--mojo/edk/system/core_test_base.cc73
-rw-r--r--mojo/edk/system/core_test_base.h17
-rw-r--r--mojo/edk/system/core_unittest.cc402
-rw-r--r--mojo/edk/system/data_pipe_consumer_dispatcher.cc103
-rw-r--r--mojo/edk/system/data_pipe_consumer_dispatcher.h18
-rw-r--r--mojo/edk/system/data_pipe_producer_dispatcher.cc94
-rw-r--r--mojo/edk/system/data_pipe_producer_dispatcher.h18
-rw-r--r--mojo/edk/system/data_pipe_unittest.cc242
-rw-r--r--mojo/edk/system/dispatcher.cc26
-rw-r--r--mojo/edk/system/dispatcher.h57
-rw-r--r--mojo/edk/system/handle_signals_state.h42
-rw-r--r--mojo/edk/system/message_pipe_dispatcher.cc109
-rw-r--r--mojo/edk/system/message_pipe_dispatcher.h18
-rw-r--r--mojo/edk/system/message_pipe_perftest.cc7
-rw-r--r--mojo/edk/system/message_pipe_unittest.cc93
-rw-r--r--mojo/edk/system/multiprocess_message_pipe_unittest.cc124
-rw-r--r--mojo/edk/system/node_controller.cc33
-rw-r--r--mojo/edk/system/ports/BUILD.gn1
-rw-r--r--mojo/edk/system/request_context.cc54
-rw-r--r--mojo/edk/system/request_context.h29
-rw-r--r--mojo/edk/system/signals_unittest.cc76
-rw-r--r--mojo/edk/system/wait_set_dispatcher.cc312
-rw-r--r--mojo/edk/system/wait_set_dispatcher.h103
-rw-r--r--mojo/edk/system/wait_set_dispatcher_unittest.cc493
-rw-r--r--mojo/edk/system/waiter.cc102
-rw-r--r--mojo/edk/system/waiter.h80
-rw-r--r--mojo/edk/system/waiter_test_utils.cc70
-rw-r--r--mojo/edk/system/waiter_test_utils.h104
-rw-r--r--mojo/edk/system/waiter_unittest.cc298
-rw-r--r--mojo/edk/system/watch.cc83
-rw-r--r--mojo/edk/system/watch.h124
-rw-r--r--mojo/edk/system/watch_unittest.cc480
-rw-r--r--mojo/edk/system/watcher.cc53
-rw-r--r--mojo/edk/system/watcher.h88
-rw-r--r--mojo/edk/system/watcher_dispatcher.cc232
-rw-r--r--mojo/edk/system/watcher_dispatcher.h101
-rw-r--r--mojo/edk/system/watcher_set.cc73
-rw-r--r--mojo/edk/system/watcher_set.h55
-rw-r--r--mojo/edk/system/watcher_unittest.cc1637
-rw-r--r--mojo/edk/test/mojo_test_base.cc37
-rw-r--r--mojo/edk/test/mojo_test_base.h10
-rw-r--r--mojo/edk/test/multiprocess_test_helper.cc45
-rw-r--r--mojo/edk/test/multiprocess_test_helper.h7
-rw-r--r--mojo/public/README.md43
-rw-r--r--mojo/public/c/README.md22
-rw-r--r--mojo/public/c/system/BUILD.gn2
-rw-r--r--mojo/public/c/system/README.md869
-rw-r--r--mojo/public/c/system/buffer.h73
-rw-r--r--mojo/public/c/system/core.h2
-rw-r--r--mojo/public/c/system/data_pipe.h136
-rw-r--r--mojo/public/c/system/functions.h169
-rw-r--r--mojo/public/c/system/platform_handle.h43
-rw-r--r--mojo/public/c/system/tests/BUILD.gn1
-rw-r--r--mojo/public/c/system/tests/core_perftest.cc4
-rw-r--r--mojo/public/c/system/tests/core_unittest.cc72
-rw-r--r--mojo/public/c/system/tests/core_unittest_pure_c.c28
-rw-r--r--mojo/public/c/system/thunks.cc71
-rw-r--r--mojo/public/c/system/thunks.h64
-rw-r--r--mojo/public/c/system/types.h45
-rw-r--r--mojo/public/c/system/wait_set.h137
-rw-r--r--mojo/public/c/system/watcher.h184
-rw-r--r--mojo/public/cpp/README.md38
-rw-r--r--mojo/public/cpp/bindings/BUILD.gn3
-rw-r--r--mojo/public/cpp/bindings/README.md1231
-rw-r--r--mojo/public/cpp/bindings/associated_interface_ptr.h24
-rw-r--r--mojo/public/cpp/bindings/binding.h4
-rw-r--r--mojo/public/cpp/bindings/connector.h17
-rw-r--r--mojo/public/cpp/bindings/interface_endpoint_client.h2
-rw-r--r--mojo/public/cpp/bindings/lib/associated_interface_ptr.cc18
-rw-r--r--mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h2
-rw-r--r--mojo/public/cpp/bindings/lib/connector.cc194
-rw-r--r--mojo/public/cpp/bindings/lib/control_message_handler.cc14
-rw-r--r--mojo/public/cpp/bindings/lib/control_message_handler.h9
-rw-r--r--mojo/public/cpp/bindings/lib/control_message_proxy.cc10
-rw-r--r--mojo/public/cpp/bindings/lib/interface_endpoint_client.cc27
-rw-r--r--mojo/public/cpp/bindings/lib/interface_ptr_state.h2
-rw-r--r--mojo/public/cpp/bindings/lib/multiplex_router.cc86
-rw-r--r--mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc5
-rw-r--r--mojo/public/cpp/bindings/lib/serialization.h7
-rw-r--r--mojo/public/cpp/bindings/lib/sync_event_watcher.cc67
-rw-r--r--mojo/public/cpp/bindings/lib/sync_handle_registry.cc75
-rw-r--r--mojo/public/cpp/bindings/lib/sync_handle_watcher.cc6
-rw-r--r--mojo/public/cpp/bindings/message.h21
-rw-r--r--mojo/public/cpp/bindings/sync_call_restrictions.h7
-rw-r--r--mojo/public/cpp/bindings/sync_event_watcher.h68
-rw-r--r--mojo/public/cpp/bindings/sync_handle_registry.h30
-rw-r--r--mojo/public/cpp/bindings/tests/BUILD.gn2
-rw-r--r--mojo/public/cpp/bindings/tests/associated_interface_unittest.cc6
-rw-r--r--mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc18
-rw-r--r--mojo/public/cpp/bindings/tests/bindings_perftest.cc10
-rw-r--r--mojo/public/cpp/bindings/tests/handle_passing_unittest.cc4
-rw-r--r--mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc30
-rw-r--r--mojo/public/cpp/bindings/tests/router_test_util.cc16
-rw-r--r--mojo/public/cpp/bindings/tests/router_test_util.h13
-rw-r--r--mojo/public/cpp/bindings/tests/sample_service_unittest.cc5
-rw-r--r--mojo/public/cpp/bindings/tests/struct_traits_unittest.cc4
-rw-r--r--mojo/public/cpp/bindings/tests/struct_unittest.cc21
-rw-r--r--mojo/public/cpp/bindings/tests/sync_method_unittest.cc166
-rw-r--r--mojo/public/cpp/bindings/tests/wtf_map_unittest.cc4
-rw-r--r--mojo/public/cpp/bindings/thread_safe_interface_ptr.h138
-rw-r--r--mojo/public/cpp/system/BUILD.gn7
-rw-r--r--mojo/public/cpp/system/README.md396
-rw-r--r--mojo/public/cpp/system/functions.h6
-rw-r--r--mojo/public/cpp/system/handle.h106
-rw-r--r--mojo/public/cpp/system/handle_signals_state.h83
-rw-r--r--mojo/public/cpp/system/simple_watcher.cc279
-rw-r--r--mojo/public/cpp/system/simple_watcher.h215
-rw-r--r--mojo/public/cpp/system/tests/BUILD.gn5
-rw-r--r--mojo/public/cpp/system/tests/core_unittest.cc52
-rw-r--r--mojo/public/cpp/system/tests/handle_signals_state_unittest.cc42
-rw-r--r--mojo/public/cpp/system/tests/simple_watcher_unittest.cc277
-rw-r--r--mojo/public/cpp/system/tests/wait_set_unittest.cc376
-rw-r--r--mojo/public/cpp/system/tests/wait_unittest.cc321
-rw-r--r--mojo/public/cpp/system/tests/watcher_unittest.cc181
-rw-r--r--mojo/public/cpp/system/wait.cc200
-rw-r--r--mojo/public/cpp/system/wait.h75
-rw-r--r--mojo/public/cpp/system/wait_set.cc371
-rw-r--r--mojo/public/cpp/system/wait_set.h124
-rw-r--r--mojo/public/cpp/system/watcher.cc111
-rw-r--r--mojo/public/cpp/system/watcher.h118
-rw-r--r--mojo/public/cpp/test_support/lib/test_utils.cc4
-rw-r--r--mojo/public/interfaces/bindings/BUILD.gn12
-rw-r--r--mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom67
-rw-r--r--mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom46
-rw-r--r--mojo/public/interfaces/bindings/tests/BUILD.gn43
-rw-r--r--mojo/public/interfaces/bindings/tests/echo.mojom12
-rw-r--r--mojo/public/interfaces/bindings/tests/echo_import.mojom10
-rw-r--r--mojo/public/interfaces/bindings/tests/test_export2.mojom10
-rw-r--r--mojo/public/java/bindings/README.md12
-rw-r--r--mojo/public/java/system/README.md25
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/Core.java138
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/Handle.java6
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java7
-rw-r--r--mojo/public/js/BUILD.gn49
-rw-r--r--mojo/public/js/README.md7
-rw-r--r--mojo/public/js/bindings.js85
-rw-r--r--mojo/public/js/codec.js12
-rw-r--r--mojo/public/js/connector.js12
-rw-r--r--mojo/public/js/constants.cc10
-rw-r--r--mojo/public/js/constants.h5
-rw-r--r--mojo/public/js/core.js35
-rw-r--r--mojo/public/js/interface_types.js18
-rw-r--r--mojo/public/js/lib/control_message_handler.js10
-rw-r--r--mojo/public/js/lib/control_message_proxy.js24
-rw-r--r--mojo/public/js/lib/interface_endpoint_client.js232
-rw-r--r--mojo/public/js/lib/interface_endpoint_handle.js158
-rw-r--r--mojo/public/js/lib/pipe_control_message_handler.js61
-rw-r--r--mojo/public/js/lib/pipe_control_message_proxy.js56
-rw-r--r--mojo/public/js/new_bindings/base.js111
-rw-r--r--mojo/public/js/new_bindings/bindings.js52
-rw-r--r--mojo/public/js/new_bindings/buffer.js9
-rw-r--r--mojo/public/js/new_bindings/codec.js107
-rw-r--r--mojo/public/js/new_bindings/connector.js51
-rw-r--r--mojo/public/js/new_bindings/interface_types.js22
-rw-r--r--mojo/public/js/new_bindings/lib/control_message_handler.js71
-rw-r--r--mojo/public/js/new_bindings/lib/control_message_proxy.js63
-rw-r--r--mojo/public/js/new_bindings/router.js39
-rw-r--r--mojo/public/js/new_bindings/unicode.js14
-rw-r--r--mojo/public/js/new_bindings/validator.js89
-rw-r--r--mojo/public/js/router.js348
-rw-r--r--mojo/public/js/tests/codec_unittest.js314
-rw-r--r--mojo/public/js/tests/connection_unittest.js136
-rw-r--r--mojo/public/js/tests/core_unittest.js51
-rw-r--r--mojo/public/js/tests/interface_ptr_unittest.js240
-rw-r--r--mojo/public/js/tests/sample_service_unittest.js173
-rw-r--r--mojo/public/js/tests/struct_unittest.js279
-rw-r--r--mojo/public/js/tests/union_unittest.js194
-rw-r--r--mojo/public/js/tests/validation_unittest.js14
-rw-r--r--mojo/public/js/validator.js48
-rw-r--r--mojo/public/tools/bindings/README.md749
-rw-r--r--mojo/public/tools/bindings/chromium_bindings_configuration.gni3
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl49
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl11
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl1
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl7
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl6
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl5
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl57
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl15
-rw-r--r--mojo/public/tools/bindings/generators/mojom_js_generator.py6
-rw-r--r--mojo/public/tools/bindings/mojom.gni12
-rwxr-xr-xmojo/public/tools/bindings/mojom_bindings_generator.py22
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/generator.py5
218 files changed, 11846 insertions, 8395 deletions
diff --git a/Android.bp b/Android.bp
index 7ae795e..0d9a8ce 100644
--- a/Android.bp
+++ b/Android.bp
@@ -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(), &params, &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, &notified1)));
+
+ 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, &notified2)));
+
+ // 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