Ken Rockot | 686e413 | 2017-04-26 00:03:31 | [diff] [blame] | 1 | # Mojo C++ System API |
Ken Rockot | 929282c | 2018-05-02 17:07:29 | [diff] [blame] | 2 | This document is a subset of the [Mojo documentation](/mojo/README.md). |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 3 | |
| 4 | [TOC] |
| 5 | |
| 6 | ## Overview |
| 7 | The Mojo C++ System API provides a convenient set of helper classes and |
| 8 | functions for working with Mojo primitives. Unlike the low-level |
Ken Rockot | 929282c | 2018-05-02 17:07:29 | [diff] [blame] | 9 | [C API](/mojo/public/c/system/README.md) (upon which this is built) this library |
| 10 | takes advantage of C++ language features and common STL and `//base` types to |
| 11 | provide a slightly more idiomatic interface to the Mojo system layer, making it |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 12 | generally easier to use. |
| 13 | |
| 14 | This document provides a brief guide to API usage with example code snippets. |
| 15 | For a detailed API references please consult the headers in |
Ken Rockot | 929282c | 2018-05-02 17:07:29 | [diff] [blame] | 16 | [//mojo/public/cpp/system](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/README.md). |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 17 | |
| 18 | Note that all API symbols referenced in this document are implicitly in the |
| 19 | top-level `mojo` namespace. |
| 20 | |
| 21 | ## Scoped, Typed Handles |
| 22 | |
| 23 | All types of Mojo handles in the C API are simply opaque, integral `MojoHandle` |
| 24 | values. The C++ API has more strongly typed wrappers defined for different |
| 25 | handle types: `MessagePipeHandle`, `SharedBufferHandle`, |
Ken Rockot | 7c05e3de | 2018-06-26 02:54:45 | [diff] [blame] | 26 | `DataPipeConsumerHandle`, `DataPipeProducerHandle`, `TrapHandle`, and |
| 27 | `InvitationHandle`. |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 28 | |
| 29 | Each of these also has a corresponding, move-only, scoped type for safer usage: |
| 30 | `ScopedMessagePipeHandle`, `ScopedSharedBufferHandle`, and so on. When a scoped |
| 31 | handle type is destroyed, its handle is automatically closed via `MojoClose`. |
| 32 | When working with raw handles you should **always** prefer to use one of the |
| 33 | scoped types for ownership. |
| 34 | |
| 35 | Similar to `std::unique_ptr`, scoped handle types expose a `get()` method to get |
| 36 | at the underlying unscoped handle type as well as the `->` operator to |
| 37 | dereference the scoper and make calls directly on the underlying handle type. |
| 38 | |
| 39 | ## Message Pipes |
| 40 | |
| 41 | There are two ways to create a new message pipe using the C++ API. You may |
| 42 | construct a `MessagePipe` object: |
| 43 | |
| 44 | ``` cpp |
| 45 | mojo::MessagePipe pipe; |
| 46 | |
| 47 | // NOTE: Because pipes are bi-directional there is no implicit semantic |
| 48 | // difference between |handle0| or |handle1| here. They're just two ends of a |
| 49 | // pipe. The choice to treat one as a "client" and one as a "server" is entirely |
Chase Phillips | d3e0292 | 2018-08-16 21:00:02 | [diff] [blame] | 50 | // the API user's decision. |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 51 | mojo::ScopedMessagePipeHandle client = std::move(pipe.handle0); |
| 52 | mojo::ScopedMessagePipeHandle server = std::move(pipe.handle1); |
| 53 | ``` |
| 54 | |
| 55 | or you may call `CreateMessagePipe`: |
| 56 | |
| 57 | ``` cpp |
| 58 | mojo::ScopedMessagePipeHandle client; |
| 59 | mojo::ScopedMessagePipeHandle server; |
| 60 | mojo::CreateMessagePipe(nullptr, &client, &server); |
| 61 | ``` |
| 62 | |
| 63 | There are also some helper functions for constructing message objects and |
| 64 | reading/writing them on pipes using the library's more strongly-typed C++ |
| 65 | handles: |
| 66 | |
| 67 | ``` cpp |
| 68 | mojo::ScopedMessageHandle message; |
| 69 | mojo::AllocMessage(6, nullptr, 0, MOJO_ALLOC_MESSAGE_FLAG_NONE, &message); |
| 70 | |
| 71 | void *buffer; |
| 72 | mojo::GetMessageBuffer(message.get(), &buffer); |
| 73 | |
Hyowon Kim | dd196c9 | 2023-12-04 01:38:43 | [diff] [blame] | 74 | constexpr std::string_view kMessage = "hello"; |
Peter Kasting | 1ed31f5 | 2025-01-27 22:15:35 | [diff] [blame] | 75 | std::ranges::copy(kMessage, static_cast<char*>(buffer)); |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 76 | |
| 77 | mojo::WriteMessageNew(client.get(), std::move(message), |
| 78 | MOJO_WRITE_MESSAGE_FLAG_NONE); |
| 79 | |
| 80 | // Some time later... |
| 81 | |
| 82 | mojo::ScopedMessageHandle received_message; |
| 83 | uint32_t num_bytes; |
| 84 | mojo::ReadMessageNew(server.get(), &received_message, &num_bytes, nullptr, |
| 85 | nullptr, MOJO_READ_MESSAGE_FLAG_NONE); |
| 86 | ``` |
| 87 | |
| 88 | See [message_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/message_pipe.h) |
| 89 | for detailed C++ message pipe API documentation. |
| 90 | |
| 91 | ## Data Pipes |
| 92 | |
| 93 | Similar to [Message Pipes](#Message-Pipes), the C++ library has some simple |
| 94 | helpers for more strongly-typed data pipe usage: |
| 95 | |
| 96 | ``` cpp |
| 97 | mojo::DataPipe pipe; |
Helen Li | e464f72 | 2018-07-25 18:47:57 | [diff] [blame] | 98 | mojo::ScopedDataPipeProducerHandle producer = std::move(pipe.producer_handle); |
| 99 | mojo::ScopedDataPipeConsumerHandle consumer = std::move(pipe.consumer_handle); |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 100 | |
| 101 | // Or alternatively: |
| 102 | mojo::ScopedDataPipeProducerHandle producer; |
| 103 | mojo::ScopedDataPipeConsumerHandle consumer; |
Robert Sesek | 3bce5dd | 2021-02-19 19:27:58 | [diff] [blame] | 104 | mojo::CreateDataPipe(nullptr, producer, consumer); |
Marijn Kruisselbrink | 283e9918 | 2017-05-18 22:08:30 | [diff] [blame] | 105 | ``` |
| 106 | |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 107 | C++ helpers which correspond directly to the |
Ken Rockot | 929282c | 2018-05-02 17:07:29 | [diff] [blame] | 108 | [Data Pipe C API](/mojo/public/c/system/README.md#Data-Pipes) for immediate and |
| 109 | two-phase I/O are provided as well. For example: |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 110 | |
| 111 | ``` cpp |
| 112 | uint32_t num_bytes = 7; |
Reilly Grant | 789c63e | 2017-08-04 15:30:16 | [diff] [blame] | 113 | producer.WriteData("hihihi", &num_bytes, MOJO_WRITE_DATA_FLAG_NONE); |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 114 | |
| 115 | // Some time later... |
| 116 | |
| 117 | char buffer[64]; |
| 118 | uint32_t num_bytes = 64; |
Reilly Grant | 789c63e | 2017-08-04 15:30:16 | [diff] [blame] | 119 | consumer.ReadData(buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE); |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 120 | ``` |
| 121 | |
| 122 | See [data_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/data_pipe.h) |
| 123 | for detailed C++ data pipe API documentation. |
| 124 | |
| 125 | ## Shared Buffers |
| 126 | |
Chase Phillips | d3e0292 | 2018-08-16 21:00:02 | [diff] [blame] | 127 | A new shared buffer can be allocated like so: |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 128 | |
| 129 | ``` cpp |
| 130 | mojo::ScopedSharedBufferHandle buffer = |
Noel Gordon | 00fecee | 2017-11-21 21:38:05 | [diff] [blame] | 131 | mojo::SharedBufferHandle::Create(4096); |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 132 | ``` |
| 133 | |
| 134 | This new handle can be cloned arbitrarily many times by using the underlying |
| 135 | handle's `Clone` method: |
| 136 | |
| 137 | ``` cpp |
Tommy Chiang | 911f9903 | 2024-01-02 21:07:48 | [diff] [blame] | 138 | mojo::ScopedSharedBufferHandle another_handle = |
| 139 | buffer->Clone(mojo::SharedBufferHandle::AccessMode::READ_WRITE); |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 140 | mojo::ScopedSharedBufferHandle read_only_handle = |
| 141 | buffer->Clone(mojo::SharedBufferHandle::AccessMode::READ_ONLY); |
| 142 | ``` |
| 143 | |
| 144 | And finally the library also provides a scoper for mapping the shared buffer's |
| 145 | memory: |
| 146 | |
| 147 | ``` cpp |
| 148 | mojo::ScopedSharedBufferMapping mapping = buffer->Map(64); |
Joonghun Park | a7771e7 | 2019-06-03 18:12:08 | [diff] [blame] | 149 | static_cast<int*>(mapping.get())[0] = 42; |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 150 | |
| 151 | mojo::ScopedSharedBufferMapping another_mapping = buffer->MapAtOffset(64, 4); |
Joonghun Park | a7771e7 | 2019-06-03 18:12:08 | [diff] [blame] | 152 | static_cast<int*>(mapping.get())[0] = 43; |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 153 | ``` |
| 154 | |
| 155 | When `mapping` and `another_mapping` are destroyed, they automatically unmap |
| 156 | their respective memory regions. |
| 157 | |
| 158 | See [buffer.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/buffer.h) |
| 159 | for detailed C++ shared buffer API documentation. |
| 160 | |
| 161 | ## Native Platform Handles (File Descriptors, Windows Handles, *etc.*) |
| 162 | |
| 163 | The C++ library provides several helpers for wrapping system handle types. |
| 164 | These are specifically useful when working with a few `//base` types, namely |
Ken Rockot | 7c05e3de | 2018-06-26 02:54:45 | [diff] [blame] | 165 | `base::PlatformFile`, `base::SharedMemoryHandle` (deprecated), and various |
| 166 | strongly-typed shared memory region types like |
| 167 | `base::ReadOnlySharedMemoryRegion`. See |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 168 | [platform_handle.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/platform_handle.h) |
| 169 | for detailed C++ platform handle API documentation. |
| 170 | |
Ken Rockot | 7c05e3de | 2018-06-26 02:54:45 | [diff] [blame] | 171 | ## Signals & Traps |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 172 | |
Ken Rockot | 7c05e3de | 2018-06-26 02:54:45 | [diff] [blame] | 173 | For an introduction to the concepts of handle signals and traps, check out |
Ken Rockot | 929282c | 2018-05-02 17:07:29 | [diff] [blame] | 174 | the C API's documentation on |
Ken Rockot | 7c05e3de | 2018-06-26 02:54:45 | [diff] [blame] | 175 | [Signals & Traps](/mojo/public/c/system/README.md#Signals-Traps). |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 176 | |
| 177 | ### Querying Signals |
| 178 | |
| 179 | Any C++ handle type's last known signaling state can be queried by calling the |
| 180 | `QuerySignalsState` method on the handle: |
| 181 | |
| 182 | ``` cpp |
| 183 | mojo::MessagePipe message_pipe; |
| 184 | mojo::DataPipe data_pipe; |
| 185 | mojo::HandleSignalsState a = message_pipe.handle0->QuerySignalsState(); |
| 186 | mojo::HandleSignalsState b = data_pipe.consumer->QuerySignalsState(); |
| 187 | ``` |
| 188 | |
| 189 | The `HandleSignalsState` is a thin wrapper interface around the C API's |
| 190 | `MojoHandleSignalsState` structure with convenient accessors for testing |
| 191 | the signal bitmasks. Whereas when using the C API you might write: |
| 192 | |
| 193 | ``` c |
| 194 | struct MojoHandleSignalsState state; |
| 195 | MojoQueryHandleSignalsState(handle0, &state); |
| 196 | if (state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE) { |
| 197 | // ... |
| 198 | } |
| 199 | ``` |
| 200 | |
| 201 | the C++ API equivalent would be: |
| 202 | |
| 203 | ``` cpp |
| 204 | if (message_pipe.handle0->QuerySignalsState().readable()) { |
| 205 | // ... |
| 206 | } |
| 207 | ``` |
| 208 | |
| 209 | ### Watching Handles |
| 210 | |
| 211 | The [`mojo::SimpleWatcher`](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/simple_watcher.h) |
Ken Rockot | 929282c | 2018-05-02 17:07:29 | [diff] [blame] | 212 | class serves as a convenient helper for using the |
Ken Rockot | 7c05e3de | 2018-06-26 02:54:45 | [diff] [blame] | 213 | [low-level traps API](/mojo/public/c/system/README.md#Signals-Traps) |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 214 | to watch a handle for signaling state changes. A `SimpleWatcher` is bound to a |
Sam McNally | d482b4b | 2017-07-17 03:45:03 | [diff] [blame] | 215 | single sequence and always dispatches its notifications on a |
| 216 | `base::SequencedTaskRunner`. |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 217 | |
| 218 | `SimpleWatcher` has two possible modes of operation, selected at construction |
| 219 | time by the `mojo::SimpleWatcher::ArmingPolicy` enum: |
| 220 | |
| 221 | * `MANUAL` mode requires the user to manually call `Arm` and/or `ArmOrNotify` |
| 222 | before any notifications will fire regarding the state of the watched handle. |
| 223 | Every time the notification callback is run, the `SimpleWatcher` must be |
Ken Rockot | 856f977 | 2017-04-11 19:41:39 | [diff] [blame] | 224 | rearmed again before the next one can fire. See |
Ken Rockot | 7c05e3de | 2018-06-26 02:54:45 | [diff] [blame] | 225 | [Arming a Trap](/mojo/public/c/system/README.md#Arming-a-Trap) and the |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 226 | documentation in `SimpleWatcher`'s header. |
| 227 | |
| 228 | * `AUTOMATIC` mode ensures that the `SimpleWatcher` always either is armed or |
| 229 | has a pending notification task queued for execution. |
| 230 | |
| 231 | `AUTOMATIC` mode is more convenient but can result in redundant notification |
| 232 | tasks, especially if the provided callback does not make a strong effort to |
| 233 | return the watched handle to an uninteresting signaling state (by *e.g.*, |
| 234 | reading all its available messages when notified of readability.) |
| 235 | |
| 236 | Example usage: |
| 237 | |
| 238 | ``` cpp |
| 239 | class PipeReader { |
| 240 | public: |
| 241 | PipeReader(mojo::ScopedMessagePipeHandle pipe) |
| 242 | : pipe_(std::move(pipe)), |
| 243 | watcher_(mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC) { |
| 244 | // NOTE: base::Unretained is safe because the callback can never be run |
| 245 | // after SimpleWatcher destruction. |
| 246 | watcher_.Watch(pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE, |
danakj | 4632659 | 2019-11-28 17:53:35 | [diff] [blame] | 247 | base::BindRepeating(&PipeReader::OnReadable, |
danakj | ee3226e9 | 2019-12-03 16:39:38 | [diff] [blame] | 248 | base::Unretained(this))); |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 249 | } |
| 250 | |
| 251 | ~PipeReader() {} |
| 252 | |
| 253 | private: |
| 254 | void OnReadable(MojoResult result) { |
| 255 | while (result == MOJO_RESULT_OK) { |
| 256 | mojo::ScopedMessageHandle message; |
| 257 | uint32_t num_bytes; |
| 258 | result = mojo::ReadMessageNew(pipe_.get(), &message, &num_bytes, nullptr, |
| 259 | nullptr, MOJO_READ_MESSAGE_FLAG_NONE); |
| 260 | DCHECK_EQ(result, MOJO_RESULT_OK); |
| 261 | messages_.emplace_back(std::move(message)); |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | mojo::ScopedMessagePipeHandle pipe_; |
| 266 | mojo::SimpleWatcher watcher_; |
| 267 | std::vector<mojo::ScopedMessageHandle> messages_; |
| 268 | }; |
| 269 | |
| 270 | mojo::MessagePipe pipe; |
| 271 | PipeReader reader(std::move(pipe.handle0)); |
| 272 | |
| 273 | // Written messages will asynchronously end up in |reader.messages_|. |
| 274 | WriteABunchOfStuff(pipe.handle1.get()); |
| 275 | ``` |
| 276 | |
| 277 | ## Synchronous Waiting |
| 278 | |
Sam McNally | d482b4b | 2017-07-17 03:45:03 | [diff] [blame] | 279 | The C++ System API defines some utilities to block a calling sequence while |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 280 | waiting for one or more handles to change signaling state in an interesting way. |
Ken Rockot | 929282c | 2018-05-02 17:07:29 | [diff] [blame] | 281 | These threads combine usage of the |
Ken Rockot | 7c05e3de | 2018-06-26 02:54:45 | [diff] [blame] | 282 | [low-level traps API](/mojo/public/c/system/README.md#Signals-Traps) |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 283 | with common synchronization primitives (namely `base::WaitableEvent`.) |
| 284 | |
| 285 | While these API features should be used sparingly, they are sometimes necessary. |
| 286 | |
Ken Rockot | 856f977 | 2017-04-11 19:41:39 | [diff] [blame] | 287 | See the documentation in |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 288 | [wait.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait.h) |
| 289 | and [wait_set.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait_set.h) |
| 290 | for a more detailed API reference. |
| 291 | |
| 292 | ### Waiting On a Single Handle |
| 293 | |
Sam McNally | d482b4b | 2017-07-17 03:45:03 | [diff] [blame] | 294 | The `mojo::Wait` function simply blocks the calling sequence until a given |
| 295 | signal mask is either partially satisfied or fully unsatisfiable on a given |
| 296 | handle. |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 297 | |
| 298 | ``` cpp |
| 299 | mojo::MessagePipe pipe; |
| 300 | mojo::WriteMessageRaw(pipe.handle0.get(), "hey", 3, nullptr, nullptr, |
| 301 | MOJO_WRITE_MESSAGE_FLAG_NONE); |
| 302 | MojoResult result = mojo::Wait(pipe.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); |
| 303 | DCHECK_EQ(result, MOJO_RESULT_OK); |
| 304 | |
| 305 | // Guaranteed to succeed because we know |handle1| is readable now. |
| 306 | mojo::ScopedMessageHandle message; |
| 307 | uint32_t num_bytes; |
| 308 | mojo::ReadMessageNew(pipe.handle1.get(), &num_bytes, nullptr, nullptr, |
| 309 | MOJO_READ_MESSAGE_FLAG_NONE); |
| 310 | ``` |
| 311 | |
| 312 | `mojo::Wait` is most typically useful in limited testing scenarios. |
| 313 | |
| 314 | ### Waiting On Multiple Handles |
| 315 | |
| 316 | `mojo::WaitMany` provides a simple API to wait on multiple handles |
| 317 | simultaneously, returning when any handle's given signal mask is either |
| 318 | partially satisfied or fully unsatisfiable. |
| 319 | |
| 320 | ``` cpp |
| 321 | mojo::MessagePipe a, b; |
| 322 | GoDoSomethingWithPipes(std:move(a.handle1), std::move(b.handle1)); |
| 323 | |
| 324 | mojo::MessagePipeHandle handles[2] = {a.handle0.get(), b.handle0.get()}; |
| 325 | MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE, |
| 326 | MOJO_HANDLE_SIGNAL_READABLE}; |
| 327 | size_t ready_index; |
| 328 | MojoResult result = mojo::WaitMany(handles, signals, 2, &ready_index); |
| 329 | if (ready_index == 0) { |
| 330 | // a.handle0 was ready. |
| 331 | } else { |
| 332 | // b.handle0 was ready. |
| 333 | } |
| 334 | ``` |
| 335 | |
| 336 | Similar to `mojo::Wait`, `mojo::WaitMany` is primarily useful in testing. When |
Ken Rockot | 856f977 | 2017-04-11 19:41:39 | [diff] [blame] | 337 | waiting on multiple handles in production code, you should almost always instead |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 338 | use a more efficient and more flexible `mojo::WaitSet` as described in the next |
| 339 | section. |
| 340 | |
| 341 | ### Waiting On Handles and Events Simultaneously |
| 342 | |
| 343 | Typically when waiting on one or more handles to signal, the set of handles and |
| 344 | conditions being waited upon do not change much between consecutive blocking |
| 345 | waits. It's also often useful to be able to interrupt the blocking operation |
| 346 | as efficiently as possible. |
| 347 | |
| 348 | [`mojo::WaitSet`](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait_set.h) |
| 349 | is designed with these conditions in mind. A `WaitSet` maintains a persistent |
| 350 | set of (not-owned) Mojo handles and `base::WaitableEvent`s, which may be |
| 351 | explicitly added to or removed from the set at any time. |
| 352 | |
| 353 | The `WaitSet` may be waited upon repeatedly, each time blocking the calling |
Sam McNally | d482b4b | 2017-07-17 03:45:03 | [diff] [blame] | 354 | sequence until either one of the handles attains an interesting signaling state |
| 355 | or one of the events is signaled. For example let's suppose we want to wait up |
| 356 | to 5 seconds for either one of two handles to become readable: |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 357 | |
| 358 | ``` cpp |
| 359 | base::WaitableEvent timeout_event( |
| 360 | base::WaitableEvent::ResetPolicy::MANUAL, |
| 361 | base::WaitableEvent::InitialState::NOT_SIGNALED); |
| 362 | mojo::MessagePipe a, b; |
| 363 | |
| 364 | GoDoStuffWithPipes(std::move(a.handle1), std::move(b.handle1)); |
| 365 | |
| 366 | mojo::WaitSet wait_set; |
| 367 | wait_set.AddHandle(a.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); |
| 368 | wait_set.AddHandle(b.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); |
| 369 | wait_set.AddEvent(&timeout_event); |
| 370 | |
| 371 | // Ensure the Wait() lasts no more than 5 seconds. |
kylechar | 14fec317 | 2019-02-20 17:40:19 | [diff] [blame] | 372 | bg_thread->task_runner()->PostDelayedTask(FROM_HERE, base::BindOnce([](base::WaitableEvent* e) { e->Signal(); }, &timeout_event); |
Peter Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 373 | base::Seconds(5)); |
rockot | f59d2d6 | 2017-04-01 02:49:08 | [diff] [blame] | 374 | |
| 375 | base::WaitableEvent* ready_event = nullptr; |
| 376 | size_t num_ready_handles = 1; |
| 377 | mojo::Handle ready_handle; |
| 378 | MojoResult ready_result; |
| 379 | wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result); |
| 380 | |
| 381 | // The apex of thread-safety. |
| 382 | bg_thread->Stop(); |
| 383 | |
| 384 | if (ready_event) { |
| 385 | // The event signaled... |
| 386 | } |
| 387 | |
| 388 | if (num_ready_handles > 0) { |
| 389 | // At least one of the handles signaled... |
| 390 | // NOTE: This and the above condition are not mutually exclusive. If handle |
| 391 | // signaling races with timeout, both things might be true. |
| 392 | } |
| 393 | ``` |
Ken Rockot | 7c05e3de | 2018-06-26 02:54:45 | [diff] [blame] | 394 | |
| 395 | ## Invitations |
| 396 | Invitations are the means by which two processes can have Mojo IPC bootstrapped |
| 397 | between them. An invitation must be transmitted over some platform-specific IPC |
| 398 | primitive (*e.g.* a Windows named pipe or UNIX domain socket), and the public |
| 399 | [platform support library](/mojo/public/cpp/platform/README.md) provides some |
| 400 | lightweight, cross-platform abstractions for those primitives. |
| 401 | |
| 402 | For any two processes looking to be connected, one must send an |
| 403 | `OutgoingInvitation` and the other must accept an `IncomingInvitation`. The |
| 404 | sender can attach named message pipe handles to the `OutgoingInvitation`, and |
| 405 | the receiver can extract them from its `IncomingInvitation`. |
| 406 | |
| 407 | Basic usage might look something like this in the case where one process is |
| 408 | responsible for launching the other. |
| 409 | |
| 410 | ``` cpp |
| 411 | #include "base/command_line.h" |
| 412 | #include "base/process/launch.h" |
| 413 | #include "mojo/public/cpp/platform/platform_channel.h" |
| 414 | #include "mojo/public/cpp/system/invitation.h" |
| 415 | #include "mojo/public/cpp/system/message_pipe.h" |
| 416 | |
| 417 | mojo::ScopedMessagePipeHandle LaunchAndConnectSomething() { |
| 418 | // Under the hood, this is essentially always an OS pipe (domain socket pair, |
| 419 | // Windows named pipe, Fuchsia channel, etc). |
| 420 | mojo::PlatformChannel channel; |
| 421 | |
| 422 | mojo::OutgoingInvitation invitation; |
| 423 | |
| 424 | // Attach a message pipe to be extracted by the receiver. The other end of the |
| 425 | // pipe is returned for us to use locally. |
| 426 | mojo::ScopedMessagePipeHandle pipe = |
| 427 | invitation->AttachMessagePipe("arbitrary pipe name"); |
| 428 | |
| 429 | base::LaunchOptions options; |
| 430 | base::CommandLine command_line("some_executable") |
| 431 | channel.PrepareToPassRemoteEndpoint(&options, &command_line); |
| 432 | base::Process child_process = base::LaunchProcess(command_line, options); |
| 433 | channel.RemoteProcessLaunchAttempted(); |
| 434 | |
| 435 | OutgoingInvitation::Send(std::move(invitation), child_process.Handle(), |
| 436 | channel.TakeLocalEndpoint()); |
| 437 | return pipe; |
| 438 | } |
| 439 | ``` |
| 440 | |
| 441 | The launched process can in turn accept an `IncomingInvitation`: |
| 442 | |
| 443 | ``` cpp |
| 444 | #include "base/command_line.h" |
| 445 | #include "base/threading/thread.h" |
Ken Rockot | dba46db | 2018-07-04 18:41:04 | [diff] [blame] | 446 | #include "mojo/core/embedder/embedder.h" |
| 447 | #include "mojo/core/embedder/scoped_ipc_support.h" |
Ken Rockot | 7c05e3de | 2018-06-26 02:54:45 | [diff] [blame] | 448 | #include "mojo/public/cpp/platform/platform_channel.h" |
| 449 | #include "mojo/public/cpp/system/invitation.h" |
| 450 | #include "mojo/public/cpp/system/message_pipe.h" |
| 451 | |
| 452 | int main(int argc, char** argv) { |
| 453 | // Basic Mojo initialization for a new process. |
Ken Rockot | dba46db | 2018-07-04 18:41:04 | [diff] [blame] | 454 | mojo::core::Init(); |
Ken Rockot | 7c05e3de | 2018-06-26 02:54:45 | [diff] [blame] | 455 | base::Thread ipc_thread("ipc!"); |
| 456 | ipc_thread.StartWithOptions( |
Chris Sharp | 7840c58 | 2019-08-02 15:45:32 | [diff] [blame] | 457 | base::Thread::Options(base::MessagePumpType::IO, 0)); |
Ken Rockot | dba46db | 2018-07-04 18:41:04 | [diff] [blame] | 458 | mojo::core::ScopedIPCSupport ipc_support( |
Ken Rockot | 7c05e3de | 2018-06-26 02:54:45 | [diff] [blame] | 459 | ipc_thread.task_runner(), |
Ken Rockot | dba46db | 2018-07-04 18:41:04 | [diff] [blame] | 460 | mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN); |
Ken Rockot | 7c05e3de | 2018-06-26 02:54:45 | [diff] [blame] | 461 | |
| 462 | // Accept an invitation. |
| 463 | mojo::IncomingInvitation invitation = mojo::IncomingInvitation::Accept( |
| 464 | mojo::PlatformChannel::RecoverPassedEndpointFromCommandLine( |
| 465 | *base::CommandLine::ForCurrentProcess())); |
| 466 | mojo::ScopedMessagePipeHandle pipe = |
| 467 | invitation->ExtractMessagePipe("arbitrary pipe name"); |
| 468 | |
| 469 | // etc... |
| 470 | return GoListenForMessagesAndRunForever(std::move(pipe)); |
| 471 | } |
| 472 | ``` |
| 473 | |
| 474 | Now we have IPC initialized between the two processes. |
| 475 | |
| 476 | Also keep in mind that bindings interfaces are just message pipes with some |
| 477 | semantic and syntactic sugar wrapping them, so you can use these primordial |
| 478 | message pipe handles as mojom interfaces. For example: |
| 479 | |
| 480 | ``` cpp |
| 481 | // Process A |
| 482 | mojo::OutgoingInvitation invitation; |
| 483 | auto pipe = invitation->AttachMessagePipe("x"); |
Alexander Timin | 572daa8 | 2020-01-24 17:33:04 | [diff] [blame] | 484 | mojo::Receiver<foo::mojom::Bar> receiver( |
Ken Rockot | 7c05e3de | 2018-06-26 02:54:45 | [diff] [blame] | 485 | &bar_impl, |
Alexander Timin | 572daa8 | 2020-01-24 17:33:04 | [diff] [blame] | 486 | mojo::PendingReceiver<foo::mojom::Bar>(std::move(pipe))); |
Ken Rockot | 7c05e3de | 2018-06-26 02:54:45 | [diff] [blame] | 487 | |
| 488 | // Process B |
| 489 | auto invitation = mojo::IncomingInvitation::Accept(...); |
| 490 | auto pipe = invitation->ExtractMessagePipe("x"); |
Alexander Timin | 572daa8 | 2020-01-24 17:33:04 | [diff] [blame] | 491 | mojo::Remote<foo::mojom::Bar> bar( |
| 492 | mojo::PendingRemote<foo::mojom::Bar>(std::move(pipe), 0)); |
Ken Rockot | 7c05e3de | 2018-06-26 02:54:45 | [diff] [blame] | 493 | |
| 494 | // Will asynchronously invoke bar_impl.DoSomething() in process A. |
| 495 | bar->DoSomething(); |
| 496 | ``` |
| 497 | |
| 498 | And just to be sure, the usage here could be reversed: the invitation sender |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 499 | could just as well treat its pipe endpoint as a `Remote<Bar>` while the receiver |
| 500 | treats theirs as a `PendingReceiver<Bar>` to be bound. |
Ken Rockot | 7c05e3de | 2018-06-26 02:54:45 | [diff] [blame] | 501 | |
| 502 | ### Process Networks |
| 503 | Accepting an invitation admits the accepting process into the sender's connected |
| 504 | network of processes. Once this is done, it's possible for the newly admitted |
| 505 | process to establish communication with any other process in that network via |
| 506 | normal message pipe passing. |
| 507 | |
| 508 | This does not mean that the invited process can proactively locate and connect |
| 509 | to other processes without assistance; rather it means that Mojo handles created |
| 510 | by the process can safely be transferred to any other process in the network |
| 511 | over established message pipes, and similarly that Mojo handles created by any |
| 512 | other process in the network can be safely passed to the newly admitted process. |
| 513 | |
| 514 | ### Invitation Restrictions |
| 515 | A process may only belong to a single network at a time. |
| 516 | |
| 517 | Additionally, once a process has joined a network, it cannot join another for |
| 518 | the remainder of its lifetime even if it has lost the connection to its original |
| 519 | network. This restriction will soon be lifted, but for now developers must be |
| 520 | mindful of it when authoring any long-running daemon process that will accept an |
| 521 | incoming invitation. |
| 522 | |
| 523 | ### Isolated Invitations |
| 524 | It is possible to have two independent networks of Mojo-connected processes; for |
| 525 | example, a long-running system daemon which uses Mojo to talk to child processes |
| 526 | of its own, as well as the Chrome browser process running with no common |
| 527 | ancestor, talking to its own child processes. |
| 528 | |
| 529 | In this scenario it may be desirable to have a process in one network talk to a |
| 530 | process in the other network. Normal invitations cannot be used here since both |
| 531 | processes already belong to a network. In this case, an **isolated** invitation |
| 532 | can be used. These work just like regular invitations, except the sender must |
| 533 | call `OutgoingInvitation::SendIsolated` and the receiver must call |
| 534 | `IncomingInvitation::AcceptIsolated`. |
| 535 | |
| 536 | Once a connection is established via isolated invitation, Mojo IPC can be used |
| 537 | normally, with the exception that transitive process connections are not |
| 538 | supported; that is, if process A sends a message pipe handle to process B via |
| 539 | an isolated connection, process B cannot reliably send that pipe handle onward |
| 540 | to another process in its own network. Isolated invitations therefore may only |
| 541 | be used to facilitate direct 1:1 communication between two processes. |