diff options
Diffstat (limited to 'mojo/public/cpp/system')
29 files changed, 4515 insertions, 0 deletions
diff --git a/mojo/public/cpp/system/BUILD.gn b/mojo/public/cpp/system/BUILD.gn new file mode 100644 index 0000000000..35087ef6f1 --- /dev/null +++ b/mojo/public/cpp/system/BUILD.gn @@ -0,0 +1,57 @@ +# 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. + +# Deletes libsystem.dylib from the build dir, since it shadows +# /usr/lib/libSystem.dylib on macOS. +# TODO(thakis): Remove this after a while. +action("clean_up_old_dylib") { + script = "//build/rm.py" + stamp = "$target_gen_dir/clean_up_stamp" + outputs = [ + stamp, + ] + args = [ + "--stamp", + rebase_path(stamp, root_build_dir), + "-f", + "libsystem.dylib", + ] +} + +component("system") { + output_name = "mojo_public_system_cpp" + + sources = [ + "buffer.cc", + "buffer.h", + "core.h", + "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", + ] + + public_deps = [ + "//base", + "//mojo/public/c/system", + ] + deps = [ + ":clean_up_old_dylib", + ] + + defines = [ "MOJO_CPP_SYSTEM_IMPLEMENTATION" ] +} diff --git a/mojo/public/cpp/system/README.md b/mojo/public/cpp/system/README.md new file mode 100644 index 0000000000..782744f0b1 --- /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/buffer.cc b/mojo/public/cpp/system/buffer.cc new file mode 100644 index 0000000000..49f45d8498 --- /dev/null +++ b/mojo/public/cpp/system/buffer.cc @@ -0,0 +1,46 @@ +// 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/buffer.h" + +namespace mojo { + +// static +ScopedSharedBufferHandle SharedBufferHandle::Create(uint64_t num_bytes) { + MojoCreateSharedBufferOptions options = { + sizeof(options), MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE}; + SharedBufferHandle handle; + MojoCreateSharedBuffer(&options, num_bytes, handle.mutable_value()); + return MakeScopedHandle(handle); +} + +ScopedSharedBufferHandle SharedBufferHandle::Clone( + SharedBufferHandle::AccessMode access_mode) const { + ScopedSharedBufferHandle result; + if (!is_valid()) + return result; + + MojoDuplicateBufferHandleOptions options = { + sizeof(options), MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE}; + if (access_mode == AccessMode::READ_ONLY) + options.flags |= MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY; + SharedBufferHandle result_handle; + MojoDuplicateBufferHandle(value(), &options, result_handle.mutable_value()); + result.reset(result_handle); + return result; +} + +ScopedSharedBufferMapping SharedBufferHandle::Map(uint64_t size) const { + return MapAtOffset(size, 0); +} + +ScopedSharedBufferMapping SharedBufferHandle::MapAtOffset( + uint64_t size, + uint64_t offset) const { + void* buffer = nullptr; + MojoMapBuffer(value(), offset, size, &buffer, MOJO_MAP_BUFFER_FLAG_NONE); + return ScopedSharedBufferMapping(buffer); +} + +} // namespace mojo diff --git a/mojo/public/cpp/system/buffer.h b/mojo/public/cpp/system/buffer.h new file mode 100644 index 0000000000..1ae923cb75 --- /dev/null +++ b/mojo/public/cpp/system/buffer.h @@ -0,0 +1,82 @@ +// 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. + +// This file provides a C++ wrapping around the Mojo C API for shared buffers, +// replacing the prefix of "Mojo" with a "mojo" namespace, and using more +// strongly-typed representations of |MojoHandle|s. +// +// Please see "mojo/public/c/system/buffer.h" for complete documentation of the +// API. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_BUFFER_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_BUFFER_H_ + +#include <stdint.h> + +#include <memory> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/system_export.h" + +namespace mojo { +namespace internal { + +struct Unmapper { + void operator()(void* buffer) { + MojoResult result = MojoUnmapBuffer(buffer); + DCHECK_EQ(MOJO_RESULT_OK, result); + } +}; + +} // namespace internal + +using ScopedSharedBufferMapping = std::unique_ptr<void, internal::Unmapper>; + +class SharedBufferHandle; + +typedef ScopedHandleBase<SharedBufferHandle> ScopedSharedBufferHandle; + +// A strongly-typed representation of a |MojoHandle| referring to a shared +// buffer. +class MOJO_CPP_SYSTEM_EXPORT SharedBufferHandle + : NON_EXPORTED_BASE(public Handle) { + public: + enum class AccessMode { + READ_WRITE, + READ_ONLY, + }; + + SharedBufferHandle() {} + explicit SharedBufferHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. + + // Creates a new SharedBufferHandle. Returns an invalid handle on failure. + static ScopedSharedBufferHandle Create(uint64_t num_bytes); + + // Clones this shared buffer handle. If |access_mode| is READ_ONLY or this is + // a read-only handle, the new handle will be read-only. On failure, this will + // return an empty result. + ScopedSharedBufferHandle Clone(AccessMode = AccessMode::READ_WRITE) const; + + // Maps |size| bytes of this shared buffer. On failure, this will return a + // null mapping. + ScopedSharedBufferMapping Map(uint64_t size) const; + + // Maps |size| bytes of this shared buffer, starting |offset| bytes into the + // buffer. On failure, this will return a null mapping. + ScopedSharedBufferMapping MapAtOffset(uint64_t size, uint64_t offset) const; +}; + +static_assert(sizeof(SharedBufferHandle) == sizeof(Handle), + "Bad size for C++ SharedBufferHandle"); +static_assert(sizeof(ScopedSharedBufferHandle) == sizeof(SharedBufferHandle), + "Bad size for C++ ScopedSharedBufferHandle"); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_BUFFER_H_ diff --git a/mojo/public/cpp/system/core.h b/mojo/public/cpp/system/core.h new file mode 100644 index 0000000000..f1d18d977f --- /dev/null +++ b/mojo/public/cpp/system/core.h @@ -0,0 +1,14 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_CORE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_CORE_H_ + +#include "mojo/public/cpp/system/buffer.h" +#include "mojo/public/cpp/system/data_pipe.h" +#include "mojo/public/cpp/system/functions.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/message_pipe.h" + +#endif // MOJO_PUBLIC_CPP_SYSTEM_CORE_H_ diff --git a/mojo/public/cpp/system/data_pipe.h b/mojo/public/cpp/system/data_pipe.h new file mode 100644 index 0000000000..0dbc3c74e5 --- /dev/null +++ b/mojo/public/cpp/system/data_pipe.h @@ -0,0 +1,163 @@ +// 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. + +// This file provides a C++ wrapping around the Mojo C API for data pipes, +// replacing the prefix of "Mojo" with a "mojo" namespace, and using more +// strongly-typed representations of |MojoHandle|s. +// +// Please see "mojo/public/c/system/data_pipe.h" for complete documentation of +// the API. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_H_ + +#include <stdint.h> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/cpp/system/handle.h" + +namespace mojo { + +// A strongly-typed representation of a |MojoHandle| to the producer end of a +// data pipe. +class DataPipeProducerHandle : public Handle { + public: + DataPipeProducerHandle() {} + explicit DataPipeProducerHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. +}; + +static_assert(sizeof(DataPipeProducerHandle) == sizeof(Handle), + "Bad size for C++ DataPipeProducerHandle"); + +typedef ScopedHandleBase<DataPipeProducerHandle> ScopedDataPipeProducerHandle; +static_assert(sizeof(ScopedDataPipeProducerHandle) == + sizeof(DataPipeProducerHandle), + "Bad size for C++ ScopedDataPipeProducerHandle"); + +// A strongly-typed representation of a |MojoHandle| to the consumer end of a +// data pipe. +class DataPipeConsumerHandle : public Handle { + public: + DataPipeConsumerHandle() {} + explicit DataPipeConsumerHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. +}; + +static_assert(sizeof(DataPipeConsumerHandle) == sizeof(Handle), + "Bad size for C++ DataPipeConsumerHandle"); + +typedef ScopedHandleBase<DataPipeConsumerHandle> ScopedDataPipeConsumerHandle; +static_assert(sizeof(ScopedDataPipeConsumerHandle) == + sizeof(DataPipeConsumerHandle), + "Bad size for C++ ScopedDataPipeConsumerHandle"); + +// Creates a new data pipe. See |MojoCreateDataPipe()| for complete +// documentation. +inline MojoResult CreateDataPipe( + const MojoCreateDataPipeOptions* options, + ScopedDataPipeProducerHandle* data_pipe_producer, + ScopedDataPipeConsumerHandle* data_pipe_consumer) { + DCHECK(data_pipe_producer); + DCHECK(data_pipe_consumer); + DataPipeProducerHandle producer_handle; + DataPipeConsumerHandle consumer_handle; + MojoResult rv = MojoCreateDataPipe(options, + producer_handle.mutable_value(), + consumer_handle.mutable_value()); + // Reset even on failure (reduces the chances that a "stale"/incorrect handle + // will be used). + data_pipe_producer->reset(producer_handle); + data_pipe_consumer->reset(consumer_handle); + return rv; +} + +// Writes to a data pipe. See |MojoWriteData| for complete documentation. +inline MojoResult WriteDataRaw(DataPipeProducerHandle data_pipe_producer, + const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags) { + return MojoWriteData(data_pipe_producer.value(), elements, num_bytes, flags); +} + +// Begins a two-phase write to a data pipe. See |MojoBeginWriteData()| for +// complete documentation. +inline MojoResult BeginWriteDataRaw(DataPipeProducerHandle data_pipe_producer, + void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) { + return MojoBeginWriteData( + data_pipe_producer.value(), buffer, buffer_num_bytes, flags); +} + +// Completes a two-phase write to a data pipe. See |MojoEndWriteData()| for +// complete documentation. +inline MojoResult EndWriteDataRaw(DataPipeProducerHandle data_pipe_producer, + uint32_t num_bytes_written) { + return MojoEndWriteData(data_pipe_producer.value(), num_bytes_written); +} + +// 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 from a data pipe. See |MojoBeginReadData()| for +// complete documentation. +inline MojoResult BeginReadDataRaw(DataPipeConsumerHandle data_pipe_consumer, + const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) { + return MojoBeginReadData( + data_pipe_consumer.value(), buffer, buffer_num_bytes, flags); +} + +// Completes a two-phase read from a data pipe. See |MojoEndReadData()| for +// complete documentation. +inline MojoResult EndReadDataRaw(DataPipeConsumerHandle data_pipe_consumer, + uint32_t num_bytes_read) { + return MojoEndReadData(data_pipe_consumer.value(), num_bytes_read); +} + +// A wrapper class that automatically creates a data pipe and owns both handles. +// TODO(vtl): Make an even more friendly version? (Maybe templatized for a +// particular type instead of some "element"? Maybe functions that take +// vectors?) +class DataPipe { + public: + DataPipe(); + explicit DataPipe(const MojoCreateDataPipeOptions& options); + ~DataPipe(); + + ScopedDataPipeProducerHandle producer_handle; + ScopedDataPipeConsumerHandle consumer_handle; +}; + +inline DataPipe::DataPipe() { + MojoResult result = + CreateDataPipe(nullptr, &producer_handle, &consumer_handle); + ALLOW_UNUSED_LOCAL(result); + DCHECK_EQ(MOJO_RESULT_OK, result); +} + +inline DataPipe::DataPipe(const MojoCreateDataPipeOptions& options) { + MojoResult result = + CreateDataPipe(&options, &producer_handle, &consumer_handle); + ALLOW_UNUSED_LOCAL(result); + DCHECK_EQ(MOJO_RESULT_OK, result); +} + +inline DataPipe::~DataPipe() { +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_H_ diff --git a/mojo/public/cpp/system/functions.h b/mojo/public/cpp/system/functions.h new file mode 100644 index 0000000000..31edf57ab5 --- /dev/null +++ b/mojo/public/cpp/system/functions.h @@ -0,0 +1,26 @@ +// 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. + +// This file provides a C++ wrapping around the standalone functions of the Mojo +// C API, replacing the prefix of "Mojo" with a "mojo" namespace. +// +// Please see "mojo/public/c/system/functions.h" for complete documentation of +// the API. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_ + +#include "mojo/public/c/system/functions.h" + +namespace mojo { + +// Returns the current |MojoTimeTicks| value. See |MojoGetTimeTicksNow()| for +// complete documentation. +inline MojoTimeTicks GetTimeTicksNow() { + return MojoGetTimeTicksNow(); +} + +} // 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 new file mode 100644 index 0000000000..781944eb76 --- /dev/null +++ b/mojo/public/cpp/system/handle.h @@ -0,0 +1,220 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_HANDLE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_HANDLE_H_ + +#include <stdint.h> +#include <limits> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#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 { + +// OVERVIEW +// +// |Handle| and |...Handle|: +// +// |Handle| is a simple, copyable wrapper for the C type |MojoHandle| (which is +// just an integer). Its purpose is to increase type-safety, not provide +// lifetime management. For the same purpose, we have trivial *subclasses* of +// |Handle|, e.g., |MessagePipeHandle| and |DataPipeProducerHandle|. |Handle| +// and its subclasses impose *no* extra overhead over using |MojoHandle|s +// directly. +// +// Note that though we provide constructors for |Handle|/|...Handle| from a +// |MojoHandle|, we do not provide, e.g., a constructor for |MessagePipeHandle| +// from a |Handle|. This is for type safety: If we did, you'd then be able to +// construct a |MessagePipeHandle| from, e.g., a |DataPipeProducerHandle| (since +// it's a |Handle|). +// +// |ScopedHandleBase| and |Scoped...Handle|: +// +// |ScopedHandleBase<HandleType>| is a templated scoped wrapper, for the handle +// types above (in the same sense that a C++11 |unique_ptr<T>| is a scoped +// wrapper for a |T*|). It provides lifetime management, closing its owned +// handle on destruction. It also provides (emulated) move semantics, again +// along the lines of C++11's |unique_ptr| (and exactly like Chromium's +// |scoped_ptr|). +// +// |ScopedHandle| is just (a typedef of) a |ScopedHandleBase<Handle>|. +// Similarly, |ScopedMessagePipeHandle| is just a +// |ScopedHandleBase<MessagePipeHandle>|. Etc. Note that a +// |ScopedMessagePipeHandle| is *not* a (subclass of) |ScopedHandle|. +// +// Wrapper functions: +// +// We provide simple wrappers for the |Mojo...()| functions (in +// mojo/public/c/system/core.h -- see that file for details on individual +// functions). +// +// The general guideline is functions that imply ownership transfer of a handle +// should take (or produce) an appropriate |Scoped...Handle|, while those that +// don't take a |...Handle|. For example, |CreateMessagePipe()| has two +// |ScopedMessagePipe| "out" parameters, whereas |Wait()| and |WaitMany()| take +// |Handle| parameters. Some, have both: e.g., |DuplicatedBuffer()| takes a +// suitable (unscoped) handle (e.g., |SharedBufferHandle|) "in" parameter and +// produces a suitable scoped handle (e.g., |ScopedSharedBufferHandle| a.k.a. +// |ScopedHandleBase<SharedBufferHandle>|) as an "out" parameter. +// +// An exception are some of the |...Raw()| functions. E.g., |CloseRaw()| takes a +// |Handle|, leaving the user to discard the wrapper. +// +// ScopedHandleBase ------------------------------------------------------------ + +// Scoper for the actual handle types defined further below. It's move-only, +// like the C++11 |unique_ptr|. +template <class HandleType> +class ScopedHandleBase { + public: + using RawHandleType = HandleType; + + ScopedHandleBase() {} + explicit ScopedHandleBase(HandleType handle) : handle_(handle) {} + ~ScopedHandleBase() { CloseIfNecessary(); } + + template <class CompatibleHandleType> + explicit ScopedHandleBase(ScopedHandleBase<CompatibleHandleType> other) + : handle_(other.release()) {} + + // Move-only constructor and operator=. + ScopedHandleBase(ScopedHandleBase&& other) : handle_(other.release()) {} + ScopedHandleBase& operator=(ScopedHandleBase&& other) { + if (&other != this) { + CloseIfNecessary(); + handle_ = other.release(); + } + return *this; + } + + const HandleType& get() const { return handle_; } + const HandleType* operator->() const { return &handle_; } + + template <typename PassedHandleType> + static ScopedHandleBase<HandleType> From( + ScopedHandleBase<PassedHandleType> other) { + static_assert( + sizeof(static_cast<PassedHandleType*>(static_cast<HandleType*>(0))), + "HandleType is not a subtype of PassedHandleType"); + return ScopedHandleBase<HandleType>( + static_cast<HandleType>(other.release().value())); + } + + void swap(ScopedHandleBase& other) { handle_.swap(other.handle_); } + + HandleType release() WARN_UNUSED_RESULT { + HandleType rv; + rv.swap(handle_); + return rv; + } + + void reset(HandleType handle = HandleType()) { + CloseIfNecessary(); + handle_ = handle; + } + + bool is_valid() const { return handle_.is_valid(); } + + bool operator==(const ScopedHandleBase& other) const { + return handle_.value() == other.get().value(); + } + + private: + void CloseIfNecessary() { + if (handle_.is_valid()) + handle_.Close(); + } + + HandleType handle_; + + DISALLOW_COPY_AND_ASSIGN(ScopedHandleBase); +}; + +template <typename HandleType> +inline ScopedHandleBase<HandleType> MakeScopedHandle(HandleType handle) { + return ScopedHandleBase<HandleType>(handle); +} + +// Handle ---------------------------------------------------------------------- + +const MojoHandle kInvalidHandleValue = MOJO_HANDLE_INVALID; + +// Wrapper base class for |MojoHandle|. +class Handle { + public: + Handle() : value_(kInvalidHandleValue) {} + explicit Handle(MojoHandle value) : value_(value) {} + ~Handle() {} + + void swap(Handle& other) { + MojoHandle temp = value_; + value_ = other.value_; + other.value_ = temp; + } + + bool is_valid() const { return value_ != kInvalidHandleValue; } + + const MojoHandle& value() const { return value_; } + MojoHandle* mutable_value() { return &value_; } + void set_value(MojoHandle value) { value_ = value; } + + void Close() { + DCHECK(is_valid()); + MojoResult result = MojoClose(value_); + ALLOW_UNUSED_LOCAL(result); + 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_; + + // Copying and assignment allowed. +}; + +// Should have zero overhead. +static_assert(sizeof(Handle) == sizeof(MojoHandle), "Bad size for C++ Handle"); + +// The scoper should also impose no more overhead. +typedef ScopedHandleBase<Handle> ScopedHandle; +static_assert(sizeof(ScopedHandle) == sizeof(Handle), + "Bad size for C++ ScopedHandle"); + +// |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. +template <class HandleType> +inline void Close(ScopedHandleBase<HandleType> /*handle*/) { +} + +// Most users should typically use |Close()| (above) instead. +inline MojoResult CloseRaw(Handle handle) { + return MojoClose(handle.value()); +} + +// Strict weak ordering, so that |Handle|s can be used as keys in |std::map|s, +inline bool operator<(const Handle a, const Handle b) { + return a.value() < b.value(); +} + +// Comparison, so that |Handle|s can be used as keys in hash maps. +inline bool operator==(const Handle a, const Handle b) { + return a.value() == b.value(); +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_HANDLE_H_ 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 0000000000..9e2430f15a --- /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/message.cc b/mojo/public/cpp/system/message.cc new file mode 100644 index 0000000000..09d8d46e6d --- /dev/null +++ b/mojo/public/cpp/system/message.cc @@ -0,0 +1,13 @@ +// 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/message.h" + +namespace mojo { + +ScopedMessageHandle::~ScopedMessageHandle() { + +} + +} // namespace mojo diff --git a/mojo/public/cpp/system/message.h b/mojo/public/cpp/system/message.h new file mode 100644 index 0000000000..d4406ee808 --- /dev/null +++ b/mojo/public/cpp/system/message.h @@ -0,0 +1,83 @@ +// 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_PUBLIC_CPP_SYSTEM_MESSAGE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_H_ + +#include <limits> + +#include "base/macros.h" +#include "base/strings/string_piece.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/cpp/system/handle.h" + +namespace mojo { + +const MojoMessageHandle kInvalidMessageHandleValue = + MOJO_MESSAGE_HANDLE_INVALID; + +// Handle wrapper base class for a |MojoMessageHandle|. +class MessageHandle { + public: + MessageHandle() : value_(kInvalidMessageHandleValue) {} + explicit MessageHandle(MojoMessageHandle value) : value_(value) {} + ~MessageHandle() {} + + void swap(MessageHandle& other) { + MojoMessageHandle temp = value_; + value_ = other.value_; + other.value_ = temp; + } + + bool is_valid() const { return value_ != kInvalidMessageHandleValue; } + + const MojoMessageHandle& value() const { return value_; } + MojoMessageHandle* mutable_value() { return &value_; } + void set_value(MojoMessageHandle value) { value_ = value; } + + void Close() { + DCHECK(is_valid()); + MojoResult result = MojoFreeMessage(value_); + ALLOW_UNUSED_LOCAL(result); + DCHECK_EQ(MOJO_RESULT_OK, result); + } + + private: + MojoMessageHandle value_; +}; + +using ScopedMessageHandle = ScopedHandleBase<MessageHandle>; + +inline MojoResult AllocMessage(size_t num_bytes, + const MojoHandle* handles, + size_t num_handles, + MojoAllocMessageFlags flags, + ScopedMessageHandle* handle) { + DCHECK_LE(num_bytes, std::numeric_limits<uint32_t>::max()); + DCHECK_LE(num_handles, std::numeric_limits<uint32_t>::max()); + MojoMessageHandle raw_handle; + MojoResult rv = MojoAllocMessage(static_cast<uint32_t>(num_bytes), handles, + static_cast<uint32_t>(num_handles), flags, + &raw_handle); + if (rv != MOJO_RESULT_OK) + return rv; + + handle->reset(MessageHandle(raw_handle)); + return MOJO_RESULT_OK; +} + +inline MojoResult GetMessageBuffer(MessageHandle message, void** buffer) { + DCHECK(message.is_valid()); + return MojoGetMessageBuffer(message.value(), buffer); +} + +inline MojoResult NotifyBadMessage(MessageHandle message, + const base::StringPiece& error) { + DCHECK(message.is_valid()); + return MojoNotifyBadMessage(message.value(), error.data(), error.size()); +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_H_ diff --git a/mojo/public/cpp/system/message_pipe.h b/mojo/public/cpp/system/message_pipe.h new file mode 100644 index 0000000000..7fbe43f7f4 --- /dev/null +++ b/mojo/public/cpp/system/message_pipe.h @@ -0,0 +1,158 @@ +// 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. + +// This file provides a C++ wrapping around the Mojo C API for message pipes, +// replacing the prefix of "Mojo" with a "mojo" namespace, and using more +// strongly-typed representations of |MojoHandle|s. +// +// Please see "mojo/public/c/system/message_pipe.h" for complete documentation +// of the API. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_PIPE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_PIPE_H_ + +#include <stdint.h> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/message.h" + +namespace mojo { + +// A strongly-typed representation of a |MojoHandle| to one end of a message +// pipe. +class MessagePipeHandle : public Handle { + public: + MessagePipeHandle() {} + explicit MessagePipeHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. +}; + +static_assert(sizeof(MessagePipeHandle) == sizeof(Handle), + "Bad size for C++ MessagePipeHandle"); + +typedef ScopedHandleBase<MessagePipeHandle> ScopedMessagePipeHandle; +static_assert(sizeof(ScopedMessagePipeHandle) == sizeof(MessagePipeHandle), + "Bad size for C++ ScopedMessagePipeHandle"); + +// Creates a message pipe. See |MojoCreateMessagePipe()| for complete +// documentation. +inline MojoResult CreateMessagePipe(const MojoCreateMessagePipeOptions* options, + ScopedMessagePipeHandle* message_pipe0, + ScopedMessagePipeHandle* message_pipe1) { + DCHECK(message_pipe0); + DCHECK(message_pipe1); + MessagePipeHandle handle0; + MessagePipeHandle handle1; + MojoResult rv = MojoCreateMessagePipe( + options, handle0.mutable_value(), handle1.mutable_value()); + // Reset even on failure (reduces the chances that a "stale"/incorrect handle + // will be used). + message_pipe0->reset(handle0); + message_pipe1->reset(handle1); + return rv; +} + +// The following "...Raw" versions fully expose the underlying API, and don't +// help with ownership of handles (especially when writing messages). It is +// expected that in most cases these methods will be called through generated +// bindings anyway. +// TODO(vtl): Write friendlier versions of these functions (using scoped +// handles and/or vectors) if there is a demonstrated need for them. + +// Writes to a message pipe. If handles are attached, on success the handles +// will no longer be valid (the receiver will receive equivalent, but logically +// different, handles). See |MojoWriteMessage()| for complete documentation. +inline MojoResult WriteMessageRaw(MessagePipeHandle message_pipe, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags) { + return MojoWriteMessage( + message_pipe.value(), bytes, num_bytes, handles, num_handles, flags); +} + +// Reads from a message pipe. See |MojoReadMessage()| for complete +// documentation. +inline MojoResult ReadMessageRaw(MessagePipeHandle message_pipe, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + return MojoReadMessage( + message_pipe.value(), bytes, num_bytes, handles, num_handles, flags); +} + +// Writes to a message pipe. Takes ownership of |message| and any attached +// handles. +inline MojoResult WriteMessageNew(MessagePipeHandle message_pipe, + ScopedMessageHandle message, + MojoWriteMessageFlags flags) { + return MojoWriteMessageNew( + message_pipe.value(), message.release().value(), flags); +} + +// Reads from a message pipe. See |MojoReadMessageNew()| for complete +// documentation. +inline MojoResult ReadMessageNew(MessagePipeHandle message_pipe, + ScopedMessageHandle* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + MojoMessageHandle raw_message; + MojoResult rv = MojoReadMessageNew(message_pipe.value(), &raw_message, + num_bytes, handles, num_handles, flags); + if (rv != MOJO_RESULT_OK) + return rv; + + message->reset(MessageHandle(raw_message)); + return MOJO_RESULT_OK; +} + +// Fuses two message pipes together at the given handles. See +// |MojoFuseMessagePipes()| for complete documentation. +inline MojoResult FuseMessagePipes(ScopedMessagePipeHandle message_pipe0, + ScopedMessagePipeHandle message_pipe1) { + return MojoFuseMessagePipes(message_pipe0.release().value(), + message_pipe1.release().value()); +} + +// A wrapper class that automatically creates a message pipe and owns both +// handles. +class MessagePipe { + public: + MessagePipe(); + explicit MessagePipe(const MojoCreateMessagePipeOptions& options); + ~MessagePipe(); + + ScopedMessagePipeHandle handle0; + ScopedMessagePipeHandle handle1; +}; + +inline MessagePipe::MessagePipe() { + MojoResult result = CreateMessagePipe(nullptr, &handle0, &handle1); + DCHECK_EQ(MOJO_RESULT_OK, result); + DCHECK(handle0.is_valid()); + DCHECK(handle1.is_valid()); +} + +inline MessagePipe::MessagePipe(const MojoCreateMessagePipeOptions& options) { + MojoResult result = CreateMessagePipe(&options, &handle0, &handle1); + DCHECK_EQ(MOJO_RESULT_OK, result); + DCHECK(handle0.is_valid()); + DCHECK(handle1.is_valid()); +} + +inline MessagePipe::~MessagePipe() { +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_PIPE_H_ diff --git a/mojo/public/cpp/system/platform_handle.cc b/mojo/public/cpp/system/platform_handle.cc new file mode 100644 index 0000000000..42e4abac83 --- /dev/null +++ b/mojo/public/cpp/system/platform_handle.cc @@ -0,0 +1,178 @@ +// 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/platform_handle.h" + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include <mach/mach.h> +#include "base/mac/mach_logging.h" +#endif + +namespace mojo { + +namespace { + +uint64_t PlatformHandleValueFromPlatformFile(base::PlatformFile file) { +#if defined(OS_WIN) + return reinterpret_cast<uint64_t>(file); +#else + return static_cast<uint64_t>(file); +#endif +} + +base::PlatformFile PlatformFileFromPlatformHandleValue(uint64_t value) { +#if defined(OS_WIN) + return reinterpret_cast<base::PlatformFile>(value); +#else + return static_cast<base::PlatformFile>(value); +#endif +} + +} // namespace + +ScopedHandle WrapPlatformFile(base::PlatformFile platform_file) { + MojoPlatformHandle platform_handle; + platform_handle.struct_size = sizeof(MojoPlatformHandle); + platform_handle.type = kPlatformFileHandleType; + platform_handle.value = PlatformHandleValueFromPlatformFile(platform_file); + + MojoHandle mojo_handle; + MojoResult result = MojoWrapPlatformHandle(&platform_handle, &mojo_handle); + CHECK_EQ(result, MOJO_RESULT_OK); + + return ScopedHandle(Handle(mojo_handle)); +} + +MojoResult UnwrapPlatformFile(ScopedHandle handle, base::PlatformFile* file) { + MojoPlatformHandle platform_handle; + platform_handle.struct_size = sizeof(MojoPlatformHandle); + MojoResult result = MojoUnwrapPlatformHandle(handle.release().value(), + &platform_handle); + if (result != MOJO_RESULT_OK) + return result; + + if (platform_handle.type == MOJO_PLATFORM_HANDLE_TYPE_INVALID) { + *file = base::kInvalidPlatformFile; + } else { + CHECK_EQ(platform_handle.type, kPlatformFileHandleType); + *file = PlatformFileFromPlatformHandleValue(platform_handle.value); + } + + return MOJO_RESULT_OK; +} + +ScopedSharedBufferHandle WrapSharedMemoryHandle( + const base::SharedMemoryHandle& memory_handle, + size_t size, + bool read_only) { +#if defined(OS_POSIX) && !(defined(OS_MACOSX) && !defined(OS_IOS)) + if (memory_handle.fd == base::kInvalidPlatformFile) + return ScopedSharedBufferHandle(); +#else + if (!memory_handle.IsValid()) + return ScopedSharedBufferHandle(); +#endif + MojoPlatformHandle platform_handle; + platform_handle.struct_size = sizeof(MojoPlatformHandle); + platform_handle.type = kPlatformSharedBufferHandleType; +#if defined(OS_MACOSX) && !defined(OS_IOS) + platform_handle.value = + static_cast<uint64_t>(memory_handle.GetMemoryObject()); +#elif defined(OS_POSIX) + platform_handle.value = PlatformHandleValueFromPlatformFile(memory_handle.fd); +#elif defined(OS_WIN) + platform_handle.value = + PlatformHandleValueFromPlatformFile(memory_handle.GetHandle()); +#endif + + MojoPlatformSharedBufferHandleFlags flags = + MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE; + if (read_only) + flags |= MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY; + + MojoHandle mojo_handle; + MojoResult result = MojoWrapPlatformSharedBufferHandle( + &platform_handle, size, flags, &mojo_handle); + CHECK_EQ(result, MOJO_RESULT_OK); + + return ScopedSharedBufferHandle(SharedBufferHandle(mojo_handle)); +} + +MojoResult UnwrapSharedMemoryHandle(ScopedSharedBufferHandle handle, + base::SharedMemoryHandle* memory_handle, + size_t* size, + bool* read_only) { + if (!handle.is_valid()) + return MOJO_RESULT_INVALID_ARGUMENT; + MojoPlatformHandle platform_handle; + platform_handle.struct_size = sizeof(MojoPlatformHandle); + + MojoPlatformSharedBufferHandleFlags flags; + size_t num_bytes; + MojoResult result = MojoUnwrapPlatformSharedBufferHandle( + handle.release().value(), &platform_handle, &num_bytes, &flags); + if (result != MOJO_RESULT_OK) + return result; + + if (size) + *size = num_bytes; + + if (read_only) + *read_only = flags & MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY; + +#if defined(OS_MACOSX) && !defined(OS_IOS) + CHECK_EQ(platform_handle.type, MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT); + *memory_handle = base::SharedMemoryHandle( + static_cast<mach_port_t>(platform_handle.value), num_bytes, + base::GetCurrentProcId()); +#elif defined(OS_POSIX) + CHECK_EQ(platform_handle.type, MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR); + *memory_handle = base::SharedMemoryHandle( + static_cast<int>(platform_handle.value), false); +#elif defined(OS_WIN) + CHECK_EQ(platform_handle.type, MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE); + *memory_handle = base::SharedMemoryHandle( + reinterpret_cast<HANDLE>(platform_handle.value), + base::GetCurrentProcId()); +#endif + + return MOJO_RESULT_OK; +} + +#if defined(OS_MACOSX) && !defined(OS_IOS) +ScopedHandle WrapMachPort(mach_port_t port) { + kern_return_t kr = + mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, 1); + MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) + << "MachPortAttachmentMac mach_port_mod_refs"; + if (kr != KERN_SUCCESS) + return ScopedHandle(); + + MojoPlatformHandle platform_handle; + platform_handle.struct_size = sizeof(MojoPlatformHandle); + platform_handle.type = MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT; + platform_handle.value = static_cast<uint64_t>(port); + + MojoHandle mojo_handle; + MojoResult result = MojoWrapPlatformHandle(&platform_handle, &mojo_handle); + CHECK_EQ(result, MOJO_RESULT_OK); + + return ScopedHandle(Handle(mojo_handle)); +} + +MojoResult UnwrapMachPort(ScopedHandle handle, mach_port_t* port) { + MojoPlatformHandle platform_handle; + platform_handle.struct_size = sizeof(MojoPlatformHandle); + MojoResult result = + MojoUnwrapPlatformHandle(handle.release().value(), &platform_handle); + if (result != MOJO_RESULT_OK) + return result; + + CHECK_EQ(platform_handle.type, MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT); + *port = static_cast<mach_port_t>(platform_handle.value); + return MOJO_RESULT_OK; +} +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + +} // namespace mojo diff --git a/mojo/public/cpp/system/platform_handle.h b/mojo/public/cpp/system/platform_handle.h new file mode 100644 index 0000000000..801264efce --- /dev/null +++ b/mojo/public/cpp/system/platform_handle.h @@ -0,0 +1,92 @@ +// 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. + +// This file provides a C++ wrapping around the Mojo C API for platform handles, +// replacing the prefix of "Mojo" with a "mojo" namespace. +// +// Please see "mojo/public/c/system/platform_handle.h" for complete +// documentation of the API. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_PLATFORM_HANDLE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_PLATFORM_HANDLE_H_ + +#include <stdint.h> + +#include "base/compiler_specific.h" +#include "base/files/file.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/shared_memory_handle.h" +#include "base/process/process_handle.h" +#include "mojo/public/c/system/platform_handle.h" +#include "mojo/public/cpp/system/buffer.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/system_export.h" + +#if defined(OS_WIN) +#include <windows.h> +#endif + +namespace mojo { + +#if defined(OS_POSIX) +const MojoPlatformHandleType kPlatformFileHandleType = + MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR; + +#if defined(OS_MACOSX) && !defined(OS_IOS) +const MojoPlatformHandleType kPlatformSharedBufferHandleType = + MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT; +#else +const MojoPlatformHandleType kPlatformSharedBufferHandleType = + MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR; +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + +#elif defined(OS_WIN) +const MojoPlatformHandleType kPlatformFileHandleType = + MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE; + +const MojoPlatformHandleType kPlatformSharedBufferHandleType = + MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE; +#endif // defined(OS_POSIX) + +// Wraps a PlatformFile as a Mojo handle. Takes ownership of the file object. +MOJO_CPP_SYSTEM_EXPORT +ScopedHandle WrapPlatformFile(base::PlatformFile platform_file); + +// Unwraps a PlatformFile from a Mojo handle. +MOJO_CPP_SYSTEM_EXPORT +MojoResult UnwrapPlatformFile(ScopedHandle handle, base::PlatformFile* file); + +// Wraps a base::SharedMemoryHandle as a Mojo handle. Takes ownership of the +// SharedMemoryHandle. Note that |read_only| is only an indicator of whether +// |memory_handle| only supports read-only mapping. It does NOT have any +// influence on the access control of the shared buffer object. +MOJO_CPP_SYSTEM_EXPORT +ScopedSharedBufferHandle WrapSharedMemoryHandle( + const base::SharedMemoryHandle& memory_handle, + size_t size, + bool read_only); + +// Unwraps a base::SharedMemoryHandle from a Mojo handle. The caller assumes +// responsibility for the lifetime of the SharedMemoryHandle. +MOJO_CPP_SYSTEM_EXPORT MojoResult +UnwrapSharedMemoryHandle(ScopedSharedBufferHandle handle, + base::SharedMemoryHandle* memory_handle, + size_t* size, + bool* read_only); + +#if defined(OS_MACOSX) && !defined(OS_IOS) +// Wraps a mach_port_t as a Mojo handle. This takes a reference to the +// Mach port. +MOJO_CPP_SYSTEM_EXPORT ScopedHandle WrapMachPort(mach_port_t port); + +// Unwraps a mach_port_t from a Mojo handle. The caller gets ownership of the +// Mach port. +MOJO_CPP_SYSTEM_EXPORT MojoResult UnwrapMachPort(ScopedHandle handle, + mach_port_t* port); +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_PLATFORM_HANDLE_H_ diff --git a/mojo/public/cpp/system/simple_watcher.cc b/mojo/public/cpp/system/simple_watcher.cc new file mode 100644 index 0000000000..ae96faa395 --- /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 0000000000..9001884c97 --- /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/system_export.h b/mojo/public/cpp/system/system_export.h new file mode 100644 index 0000000000..c9bb140db3 --- /dev/null +++ b/mojo/public/cpp/system/system_export.h @@ -0,0 +1,34 @@ +// 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_PUBLIC_CPP_SYSTEM_SYSTEM_EXPORT_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_SYSTEM_EXPORT_H_ + +#if defined(COMPONENT_BUILD) + +#if defined(WIN32) + +#if defined(MOJO_CPP_SYSTEM_IMPLEMENTATION) +#define MOJO_CPP_SYSTEM_EXPORT __declspec(dllexport) +#else +#define MOJO_CPP_SYSTEM_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_CPP_SYSTEM_IMPLEMENTATION) +#define MOJO_CPP_SYSTEM_EXPORT __attribute((visibility("default"))) +#else +#define MOJO_CPP_SYSTEM_EXPORT +#endif + +#endif // defined(WIN32) + +#else // !defined(COMPONENT_BUILD) + +#define MOJO_CPP_SYSTEM_EXPORT + +#endif // defined(COMPONENT_BUILD) + +#endif // MOJO_PUBLIC_CPP_SYSTEM_SYSTEM_EXPORT_H_ diff --git a/mojo/public/cpp/system/tests/BUILD.gn b/mojo/public/cpp/system/tests/BUILD.gn new file mode 100644 index 0000000000..705d009c9c --- /dev/null +++ b/mojo/public/cpp/system/tests/BUILD.gn @@ -0,0 +1,23 @@ +# 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. + +source_set("tests") { + testonly = true + + sources = [ + "core_unittest.cc", + "handle_signals_state_unittest.cc", + "simple_watcher_unittest.cc", + "wait_set_unittest.cc", + "wait_unittest.cc", + ] + + deps = [ + "//base", + "//mojo/public/c/system/tests", + "//mojo/public/cpp/system", + "//mojo/public/cpp/test_support:test_utils", + "//testing/gtest", + ] +} diff --git a/mojo/public/cpp/system/tests/core_unittest.cc b/mojo/public/cpp/system/tests/core_unittest.cc new file mode 100644 index 0000000000..40a94f008f --- /dev/null +++ b/mojo/public/cpp/system/tests/core_unittest.cc @@ -0,0 +1,510 @@ +// 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. + +// This file tests the C++ Mojo system core wrappers. +// TODO(vtl): Maybe rename "CoreCppTest" -> "CoreTest" if/when this gets +// compiled into a different binary from the C API tests. + +#include "mojo/public/cpp/system/core.h" + +#include <stddef.h> +#include <stdint.h> +#include <map> +#include <utility> + +#include "mojo/public/cpp/system/wait.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +const MojoHandleSignals kSignalReadableWritable = + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE; + +const MojoHandleSignals kSignalAll = MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED; + +TEST(CoreCppTest, GetTimeTicksNow) { + const MojoTimeTicks start = GetTimeTicksNow(); + EXPECT_NE(static_cast<MojoTimeTicks>(0), start) + << "GetTimeTicksNow should return nonzero value"; +} + +TEST(CoreCppTest, Basic) { + // Basic |Handle| implementation: + { + EXPECT_EQ(MOJO_HANDLE_INVALID, kInvalidHandleValue); + + Handle h0; + EXPECT_EQ(kInvalidHandleValue, h0.value()); + EXPECT_EQ(kInvalidHandleValue, *h0.mutable_value()); + EXPECT_FALSE(h0.is_valid()); + + Handle h1(static_cast<MojoHandle>(123)); + EXPECT_EQ(static_cast<MojoHandle>(123), h1.value()); + EXPECT_EQ(static_cast<MojoHandle>(123), *h1.mutable_value()); + EXPECT_TRUE(h1.is_valid()); + *h1.mutable_value() = static_cast<MojoHandle>(456); + EXPECT_EQ(static_cast<MojoHandle>(456), h1.value()); + EXPECT_TRUE(h1.is_valid()); + + h1.swap(h0); + EXPECT_EQ(static_cast<MojoHandle>(456), h0.value()); + EXPECT_TRUE(h0.is_valid()); + EXPECT_FALSE(h1.is_valid()); + + h1.set_value(static_cast<MojoHandle>(789)); + h0.swap(h1); + EXPECT_EQ(static_cast<MojoHandle>(789), h0.value()); + EXPECT_TRUE(h0.is_valid()); + EXPECT_EQ(static_cast<MojoHandle>(456), h1.value()); + EXPECT_TRUE(h1.is_valid()); + + // Make sure copy constructor works. + Handle h2(h0); + EXPECT_EQ(static_cast<MojoHandle>(789), h2.value()); + // And assignment. + h2 = h1; + EXPECT_EQ(static_cast<MojoHandle>(456), h2.value()); + + // Make sure that we can put |Handle|s into |std::map|s. + h0 = Handle(static_cast<MojoHandle>(987)); + h1 = Handle(static_cast<MojoHandle>(654)); + h2 = Handle(static_cast<MojoHandle>(321)); + Handle h3; + std::map<Handle, int> handle_to_int; + handle_to_int[h0] = 0; + handle_to_int[h1] = 1; + handle_to_int[h2] = 2; + handle_to_int[h3] = 3; + + EXPECT_EQ(4u, handle_to_int.size()); + EXPECT_FALSE(handle_to_int.find(h0) == handle_to_int.end()); + EXPECT_EQ(0, handle_to_int[h0]); + EXPECT_FALSE(handle_to_int.find(h1) == handle_to_int.end()); + EXPECT_EQ(1, handle_to_int[h1]); + EXPECT_FALSE(handle_to_int.find(h2) == handle_to_int.end()); + EXPECT_EQ(2, handle_to_int[h2]); + EXPECT_FALSE(handle_to_int.find(h3) == handle_to_int.end()); + EXPECT_EQ(3, handle_to_int[h3]); + EXPECT_TRUE(handle_to_int.find(Handle(static_cast<MojoHandle>(13579))) == + handle_to_int.end()); + + // TODO(vtl): With C++11, support |std::unordered_map|s, etc. (Or figure out + // how to support the variations of |hash_map|.) + } + + // |Handle|/|ScopedHandle| functions: + { + ScopedHandle h; + + EXPECT_EQ(kInvalidHandleValue, h.get().value()); + + // This should be a no-op. + Close(std::move(h)); + + // It should still be invalid. + EXPECT_EQ(kInvalidHandleValue, h.get().value()); + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + 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); + 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): + { + EXPECT_FALSE(MakeScopedHandle(Handle()).is_valid()); + EXPECT_FALSE(MakeScopedHandle(MessagePipeHandle()).is_valid()); + EXPECT_FALSE(MakeScopedHandle(DataPipeProducerHandle()).is_valid()); + EXPECT_FALSE(MakeScopedHandle(DataPipeConsumerHandle()).is_valid()); + EXPECT_FALSE(MakeScopedHandle(SharedBufferHandle()).is_valid()); + } + + // |MessagePipeHandle|/|ScopedMessagePipeHandle| functions: + { + MessagePipeHandle h_invalid; + EXPECT_FALSE(h_invalid.is_valid()); + EXPECT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + WriteMessageRaw( + h_invalid, nullptr, 0, nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE)); + char buffer[10] = {0}; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + WriteMessageRaw(h_invalid, + buffer, + sizeof(buffer), + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + ReadMessageRaw(h_invalid, + nullptr, + nullptr, + nullptr, + nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + ReadMessageRaw(h_invalid, + buffer, + &buffer_size, + nullptr, + nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + + // Basic tests of waiting and closing. + MojoHandle hv0 = kInvalidHandleValue; + { + ScopedMessagePipeHandle h0; + ScopedMessagePipeHandle h1; + EXPECT_FALSE(h0.get().is_valid()); + EXPECT_FALSE(h1.get().is_valid()); + + CreateMessagePipe(nullptr, &h0, &h1); + EXPECT_TRUE(h0.get().is_valid()); + EXPECT_TRUE(h1.get().is_valid()); + EXPECT_NE(h0.get().value(), h1.get().value()); + // Save the handle values, so we can check that things got closed + // correctly. + hv0 = h0.get().value(); + MojoHandle hv1 = h1.get().value(); + MojoHandleSignalsState state = h0->QuerySignalsState(); + + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(kSignalAll, state.satisfiable_signals); + + std::vector<Handle> wh; + wh.push_back(h0.get()); + wh.push_back(h1.get()); + std::vector<MojoHandleSignals> sigs; + sigs.push_back(MOJO_HANDLE_SIGNAL_READABLE); + sigs.push_back(MOJO_HANDLE_SIGNAL_WRITABLE); + std::vector<MojoHandleSignalsState> states(sigs.size()); + + 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); + EXPECT_EQ(kSignalAll, states[1].satisfiable_signals); + + // Test closing |h1| explicitly. + Close(std::move(h1)); + EXPECT_FALSE(h1.get().is_valid()); + + // Make sure |h1| is closed. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + Wait(Handle(hv1), ~MOJO_HANDLE_SIGNAL_NONE)); + + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + 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); + } + // |hv0| should have been closed when |h0| went out of scope, so this close + // should fail. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(hv0)); + + // Actually test writing/reading messages. + { + ScopedMessagePipeHandle h0; + ScopedMessagePipeHandle h1; + CreateMessagePipe(nullptr, &h0, &h1); + + const char kHello[] = "hello"; + const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello)); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h0.get(), + kHello, + kHelloSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + MojoHandleSignalsState 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); + + char buffer[10] = {0}; + uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + ReadMessageRaw(h1.get(), + buffer, + &buffer_size, + nullptr, + nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kHelloSize, buffer_size); + EXPECT_STREQ(kHello, buffer); + + // Send a handle over the previously-establish message pipe. Use the + // |MessagePipe| wrapper (to test it), which automatically creates a + // message pipe. + MessagePipe mp; + + // Write a message to |mp.handle0|, before we send |mp.handle1|. + const char kWorld[] = "world!"; + const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld)); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(mp.handle0.get(), + kWorld, + kWorldSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Send |mp.handle1| over |h1| to |h0|. + MojoHandle handles[5]; + handles[0] = mp.handle1.release().value(); + EXPECT_NE(kInvalidHandleValue, handles[0]); + EXPECT_FALSE(mp.handle1.get().is_valid()); + uint32_t handles_count = 1; + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h1.get(), + kHello, + kHelloSize, + handles, + handles_count, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + // |handles[0]| should actually be invalid now. + 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, &state)); + EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals); + EXPECT_EQ(kSignalAll, state.satisfiable_signals); + + memset(buffer, 0, sizeof(buffer)); + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + for (size_t i = 0; i < arraysize(handles); i++) + handles[i] = kInvalidHandleValue; + handles_count = static_cast<uint32_t>(arraysize(handles)); + EXPECT_EQ(MOJO_RESULT_OK, + ReadMessageRaw(h0.get(), + buffer, + &buffer_size, + handles, + &handles_count, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kHelloSize, buffer_size); + EXPECT_STREQ(kHello, buffer); + EXPECT_EQ(1u, handles_count); + EXPECT_NE(kInvalidHandleValue, handles[0]); + + // Read from the sent/received handle. + mp.handle1.reset(MessagePipeHandle(handles[0])); + // Save |handles[0]| to check that it gets properly closed. + hv0 = handles[0]; + + EXPECT_EQ(MOJO_RESULT_OK, + Wait(mp.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &state)); + EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals); + EXPECT_EQ(kSignalAll, state.satisfiable_signals); + + memset(buffer, 0, sizeof(buffer)); + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + for (size_t i = 0; i < arraysize(handles); i++) + handles[i] = kInvalidHandleValue; + handles_count = static_cast<uint32_t>(arraysize(handles)); + EXPECT_EQ(MOJO_RESULT_OK, + ReadMessageRaw(mp.handle1.get(), + buffer, + &buffer_size, + handles, + &handles_count, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kWorldSize, buffer_size); + EXPECT_STREQ(kWorld, buffer); + EXPECT_EQ(0u, handles_count); + } + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(hv0)); + } + + // TODO(vtl): Test |CloseRaw()|. + // TODO(vtl): Test |reset()| more thoroughly? +} + +TEST(CoreCppTest, TearDownWithMessagesEnqueued) { + // Tear down a message pipe which still has a message enqueued, with the + // message also having a valid message pipe handle. + { + ScopedMessagePipeHandle h0; + ScopedMessagePipeHandle h1; + CreateMessagePipe(nullptr, &h0, &h1); + + // Send a handle over the previously-establish message pipe. + ScopedMessagePipeHandle h2; + ScopedMessagePipeHandle h3; + if (CreateMessagePipe(nullptr, &h2, &h3) != MOJO_RESULT_OK) + CreateMessagePipe(nullptr, &h2, &h3); // Must be old EDK. + + // Write a message to |h2|, before we send |h3|. + const char kWorld[] = "world!"; + const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld)); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h2.get(), + kWorld, + kWorldSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + // And also a message to |h3|. + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h3.get(), + kWorld, + kWorldSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Send |h3| over |h1| to |h0|. + const char kHello[] = "hello"; + const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello)); + MojoHandle h3_value; + h3_value = h3.release().value(); + EXPECT_NE(kInvalidHandleValue, h3_value); + EXPECT_FALSE(h3.get().is_valid()); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h1.get(), + kHello, + kHelloSize, + &h3_value, + 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + // |h3_value| should actually be invalid now. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(h3_value)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0.release().value())); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1.release().value())); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h2.release().value())); + } + + // Do this in a different order: make the enqueued message pipe handle only + // half-alive. + { + ScopedMessagePipeHandle h0; + ScopedMessagePipeHandle h1; + CreateMessagePipe(nullptr, &h0, &h1); + + // Send a handle over the previously-establish message pipe. + ScopedMessagePipeHandle h2; + ScopedMessagePipeHandle h3; + if (CreateMessagePipe(nullptr, &h2, &h3) != MOJO_RESULT_OK) + CreateMessagePipe(nullptr, &h2, &h3); // Must be old EDK. + + // Write a message to |h2|, before we send |h3|. + const char kWorld[] = "world!"; + const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld)); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h2.get(), + kWorld, + kWorldSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + // And also a message to |h3|. + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h3.get(), + kWorld, + kWorldSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Send |h3| over |h1| to |h0|. + const char kHello[] = "hello"; + const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello)); + MojoHandle h3_value; + h3_value = h3.release().value(); + EXPECT_NE(kInvalidHandleValue, h3_value); + EXPECT_FALSE(h3.get().is_valid()); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h1.get(), + kHello, + kHelloSize, + &h3_value, + 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + // |h3_value| should actually be invalid now. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(h3_value)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h2.release().value())); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0.release().value())); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1.release().value())); + } +} + +TEST(CoreCppTest, ScopedHandleMoveCtor) { + ScopedSharedBufferHandle buffer1 = SharedBufferHandle::Create(1024); + EXPECT_TRUE(buffer1.is_valid()); + + ScopedSharedBufferHandle buffer2 = SharedBufferHandle::Create(1024); + EXPECT_TRUE(buffer2.is_valid()); + + // If this fails to close buffer1, ScopedHandleBase::CloseIfNecessary() will + // assert. + buffer1 = std::move(buffer2); + + EXPECT_TRUE(buffer1.is_valid()); + EXPECT_FALSE(buffer2.is_valid()); +} + +TEST(CoreCppTest, BasicSharedBuffer) { + ScopedSharedBufferHandle h0 = SharedBufferHandle::Create(100); + ASSERT_TRUE(h0.is_valid()); + + // Map everything. + ScopedSharedBufferMapping mapping = h0->Map(100); + ASSERT_TRUE(mapping); + static_cast<char*>(mapping.get())[50] = 'x'; + + // Duplicate |h0| to |h1|. + ScopedSharedBufferHandle h1 = + h0->Clone(SharedBufferHandle::AccessMode::READ_ONLY); + ASSERT_TRUE(h1.is_valid()); + + // Close |h0|. + h0.reset(); + + // The mapping should still be good. + static_cast<char*>(mapping.get())[51] = 'y'; + + // Unmap it. + mapping.reset(); + + // Map half of |h1|. + mapping = h1->MapAtOffset(50, 50); + ASSERT_TRUE(mapping); + + // It should have what we wrote. + EXPECT_EQ('x', static_cast<char*>(mapping.get())[0]); + EXPECT_EQ('y', static_cast<char*>(mapping.get())[1]); + + // Unmap it. + mapping.reset(); + h1.reset(); + + // Creating a 1 EB shared buffer should fail without crashing. + EXPECT_FALSE(SharedBufferHandle::Create(1ULL << 60).is_valid()); +} + +// TODO(vtl): Write data pipe tests. + +} // namespace +} // namespace mojo 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 0000000000..82f538e17a --- /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 0000000000..795f262c4e --- /dev/null +++ b/mojo/public/cpp/system/tests/simple_watcher_unittest.cc @@ -0,0 +1,277 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/system/simple_watcher.h" + +#include <memory> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +template <typename Handler> +void RunResultHandler(Handler f, MojoResult result) { + f(result); +} + +template <typename Handler> +SimpleWatcher::ReadyCallback OnReady(Handler f) { + return base::Bind(&RunResultHandler<Handler>, f); +} + +SimpleWatcher::ReadyCallback NotReached() { + return OnReady([](MojoResult) { NOTREACHED(); }); +} + +class SimpleWatcherTest : public testing::Test { + public: + SimpleWatcherTest() {} + ~SimpleWatcherTest() override {} + + private: + base::MessageLoop message_loop_; + + DISALLOW_COPY_AND_ASSIGN(SimpleWatcherTest); +}; + +TEST_F(SimpleWatcherTest, WatchBasic) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + bool notified = false; + base::RunLoop run_loop; + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, + OnReady([&](MojoResult result) { + EXPECT_EQ(MOJO_RESULT_OK, result); + notified = true; + run_loop.Quit(); + }))); + EXPECT_TRUE(b_watcher.IsWatching()); + + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + run_loop.Run(); + EXPECT_TRUE(notified); + + b_watcher.Cancel(); +} + +TEST_F(SimpleWatcherTest, WatchUnsatisfiable) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + a.reset(); + + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL); + EXPECT_EQ( + MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached())); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, b_watcher.Arm()); +} + +TEST_F(SimpleWatcherTest, WatchInvalidHandle) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + a.reset(); + b.reset(); + + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached())); + EXPECT_FALSE(b_watcher.IsWatching()); +} + +TEST_F(SimpleWatcherTest, Cancel) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + base::RunLoop run_loop; + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ( + MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached())); + EXPECT_TRUE(b_watcher.IsWatching()); + b_watcher.Cancel(); + EXPECT_FALSE(b_watcher.IsWatching()); + + // This should never trigger the watcher. + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, + run_loop.QuitClosure()); + run_loop.Run(); +} + +TEST_F(SimpleWatcherTest, CancelOnClose) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + base::RunLoop run_loop; + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, + OnReady([&](MojoResult result) { + EXPECT_EQ(MOJO_RESULT_CANCELLED, result); + run_loop.Quit(); + }))); + EXPECT_TRUE(b_watcher.IsWatching()); + + // This should trigger the watcher above. + b.reset(); + + run_loop.Run(); + + EXPECT_FALSE(b_watcher.IsWatching()); +} + +TEST_F(SimpleWatcherTest, CancelOnDestruction) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + base::RunLoop run_loop; + { + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ( + MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached())); + EXPECT_TRUE(b_watcher.IsWatching()); + + // |b_watcher| should be cancelled when it goes out of scope. + } + + // This should never trigger the watcher above. + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, + run_loop.QuitClosure()); + run_loop.Run(); +} + +TEST_F(SimpleWatcherTest, CloseAndCancel) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, + OnReady([](MojoResult result) { FAIL(); }))); + EXPECT_TRUE(b_watcher.IsWatching()); + + // This should trigger the watcher above... + b.reset(); + // ...but the watcher is cancelled first. + b_watcher.Cancel(); + + EXPECT_FALSE(b_watcher.IsWatching()); + + base::RunLoop().RunUntilIdle(); +} + +TEST_F(SimpleWatcherTest, UnarmedCancel) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL); + base::RunLoop loop; + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind( + [](base::RunLoop* loop, MojoResult result) { + EXPECT_EQ(result, MOJO_RESULT_CANCELLED); + loop->Quit(); + }, + &loop))); + + // This message write will not wake up the watcher since the watcher isn't + // armed. Instead, the cancellation will dispatch due to the reset below. + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + b.reset(); + loop.Run(); +} + +TEST_F(SimpleWatcherTest, ManualArming) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL); + base::RunLoop loop; + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind( + [](base::RunLoop* loop, MojoResult result) { + EXPECT_EQ(result, MOJO_RESULT_OK); + loop->Quit(); + }, + &loop))); + EXPECT_EQ(MOJO_RESULT_OK, b_watcher.Arm()); + + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + loop.Run(); +} + +TEST_F(SimpleWatcherTest, ManualArmOrNotifyWhileSignaled) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + base::RunLoop loop1; + SimpleWatcher b_watcher1(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL); + bool notified1 = false; + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher1.Watch( + b.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind( + [](base::RunLoop* loop, bool* notified, MojoResult result) { + EXPECT_EQ(result, MOJO_RESULT_OK); + *notified = true; + loop->Quit(); + }, + &loop1, ¬ified1))); + + base::RunLoop loop2; + SimpleWatcher b_watcher2(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL); + bool notified2 = false; + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher2.Watch( + b.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind( + [](base::RunLoop* loop, bool* notified, MojoResult result) { + EXPECT_EQ(result, MOJO_RESULT_OK); + *notified = true; + loop->Quit(); + }, + &loop2, ¬ified2))); + + // First ensure that |b| is readable. + EXPECT_EQ(MOJO_RESULT_OK, b_watcher1.Arm()); + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + loop1.Run(); + + EXPECT_TRUE(notified1); + EXPECT_FALSE(notified2); + notified1 = false; + + // Now verify that ArmOrNotify results in a notification. + b_watcher2.ArmOrNotify(); + loop2.Run(); + + EXPECT_FALSE(notified1); + EXPECT_TRUE(notified2); +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/system/tests/wait_set_unittest.cc b/mojo/public/cpp/system/tests/wait_set_unittest.cc new file mode 100644 index 0000000000..d60cb45924 --- /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 0000000000..1d9d3c69bc --- /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/wait.cc b/mojo/public/cpp/system/wait.cc new file mode 100644 index 0000000000..e4e124f25c --- /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 0000000000..808e44fc25 --- /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 0000000000..1728f81b95 --- /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 0000000000..5047a86a48 --- /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 new file mode 100644 index 0000000000..0c62ba8e20 --- /dev/null +++ b/mojo/public/cpp/system/watcher.cc @@ -0,0 +1,20 @@ +// 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 "mojo/public/c/system/functions.h" + +namespace mojo { + +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 new file mode 100644 index 0000000000..d0a257814d --- /dev/null +++ b/mojo/public/cpp/system/watcher.h @@ -0,0 +1,37 @@ +// 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_PUBLIC_CPP_SYSTEM_WATCHER_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_WATCHER_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 strongly-typed representation of a |MojoHandle| for a watcher. +class WatcherHandle : public Handle { + public: + WatcherHandle() = default; + explicit WatcherHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. +}; + +static_assert(sizeof(WatcherHandle) == sizeof(Handle), + "Bad size for C++ WatcherHandle"); + +typedef ScopedHandleBase<WatcherHandle> ScopedWatcherHandle; +static_assert(sizeof(ScopedWatcherHandle) == sizeof(WatcherHandle), + "Bad size for C++ ScopedWatcherHandle"); + +MOJO_CPP_SYSTEM_EXPORT MojoResult +CreateWatcher(MojoWatcherCallback callback, + ScopedWatcherHandle* watcher_handle); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_WATCHER_H_ |