931 lines
35 KiB
Markdown
931 lines
35 KiB
Markdown
# Mojo C System API
|
|
This document is a subset of the [Mojo documentation](/mojo/README.md).
|
|
|
|
[TOC]
|
|
|
|
## Overview
|
|
The Mojo C System API is a lightweight API (with an stable, forward-compatible
|
|
ABI) upon which all higher-level public Mojo APIs are built.
|
|
|
|
This API exposes the fundamental capabilities to: create, read from, and write
|
|
to **message pipes**; create, read from, and write to **data pipes**; create
|
|
**shared buffers** and generate sharable handles to them; wrap platform-specific
|
|
handle objects (such as **file descriptors**, **Windows handles**, and
|
|
**Mach ports**) for seamless transit over message pipes; and efficiently watch
|
|
handles for various types of state transitions. Finally, there are also APIs to
|
|
bootstrap Mojo IPC between two processes.
|
|
|
|
This document provides a brief guide to API usage with example code snippets.
|
|
For a detailed API references please consult the headers in
|
|
[//mojo/public/c/system](https://cs.chromium.org/chromium/src/mojo/public/c/system/).
|
|
|
|
### A Note About Multithreading
|
|
|
|
The Mojo C System API is entirely thread-agnostic. This means that all functions
|
|
may be called from any thread in a process, and there are no restrictions on how
|
|
many threads can use the same object at the same time.
|
|
|
|
Of course this does not mean you can completely ignore potential concurrency
|
|
issues -- such as a handle being closed on one thread while another thread is
|
|
trying to perform an operation on the same handle -- but there is nothing
|
|
fundamentally incorrect about using any given API or handle from multiple
|
|
threads.
|
|
|
|
### A Note About Synchronization
|
|
|
|
Every Mojo API call is non-blocking and synchronously yields some kind of status
|
|
result code, but the call's side effects -- such as affecting the state of
|
|
one or more handles in the system -- may or may not occur asynchronously.
|
|
|
|
Mojo objects can be observed for interesting state changes in a way that is
|
|
thread-agnostic and in some ways similar to POSIX signal handlers: *i.e.*
|
|
user-provided notification handlers may be invoked at any time on arbitrary
|
|
threads in the process. It is entirely up to the API user to take appropriate
|
|
measures to synchronize operations against other application state.
|
|
|
|
The higher level [system](/mojo/README.md#High-Level-System-APIs) and
|
|
[bindings](/mojo/README.md#High-Level-Bindings-APIs) APIs provide helpers to
|
|
simplify Mojo usage in this regard, at the expense of some flexibility.
|
|
|
|
## Result Codes
|
|
|
|
Most API functions return a value of type `MojoResult`. This is an integral
|
|
result code used to convey some meaningful level of detail about the result of a
|
|
requested operation.
|
|
|
|
See [//mojo/public/c/system/types.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/types.h)
|
|
for different possible values. See documentation for individual API calls for
|
|
more specific contextual meaning of various result codes.
|
|
|
|
## Handles
|
|
|
|
Every Mojo IPC primitive is identified by a generic, opaque integer handle of
|
|
type `MojoHandle`. Handles can be acquired by creating new objects using various
|
|
API calls, or by reading messages which contain attached handles.
|
|
|
|
A `MojoHandle` can represent a message pipe endpoint, a data pipe consumer,
|
|
a data pipe producer, a shared buffer reference, a wrapped native platform
|
|
handle such as a POSIX file descriptor or a Windows system handle, a trap object
|
|
(see [Signals & Traps](#Signals-Traps) below), or a process invitation (see
|
|
[Invitations](#Invitations) below).
|
|
|
|
Message pipes, data pipes, shared buffers, and platform handles can all be
|
|
attached to messages and sent over message pipes. Traps are an inherently
|
|
process-local concept, and invitations are transmitted using special dedicated
|
|
APIs.
|
|
|
|
Any `MojoHandle` may be closed by calling `MojoClose`:
|
|
|
|
``` c
|
|
MojoHandle x = DoSomethingToGetAValidHandle();
|
|
MojoResult result = MojoClose(x);
|
|
```
|
|
|
|
If the handle passed to `MojoClose` was a valid handle, it will be closed and
|
|
`MojoClose` returns `MOJO_RESULT_OK`. Otherwise it returns
|
|
`MOJO_RESULT_INVALID_ARGUMENT`.
|
|
|
|
Similar to native system handles on various popular platforms, `MojoHandle`
|
|
values may be reused over time. Thus it is important to avoid logical errors
|
|
which lead to misplaced handle ownership, double-closes, *etc.*
|
|
|
|
## Message Pipes
|
|
|
|
A message pipe is a bidirectional messaging channel which can carry arbitrary
|
|
unstructured binary messages with zero or more `MojoHandle` attachments to be
|
|
transferred from one end of a pipe to the other. Message pipes work seamlessly
|
|
across process boundaries or within a single process.
|
|
|
|
[Invitations](#Invitations) provide the means to bootstrap one or more
|
|
primordial cross-process message pipes between two processes. Once such a pipe
|
|
is established, additional handles -- including other message pipe handles --
|
|
may be sent to a remote process using that pipe (or in turn, over other pipes
|
|
sent over that pipe, or pipes sent over *that* pipe, and so on...)
|
|
|
|
The public C System API exposes the ability to read and write messages on pipes
|
|
and to create new message pipes.
|
|
|
|
See [//mojo/public/c/system/message_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/message_pipe.h)
|
|
for detailed message pipe API documentation.
|
|
|
|
### Creating Message Pipes
|
|
|
|
`MojoCreateMessagePipe` can be used to create a new message pipe:
|
|
|
|
``` c
|
|
MojoHandle a, b;
|
|
MojoResult result = MojoCreateMessagePipe(NULL, &a, &b);
|
|
```
|
|
|
|
After this snippet, `result` should be `MOJO_RESULT_OK` (it's really hard for
|
|
this to fail!), and `a` and `b` will contain valid Mojo handles, one for each
|
|
end of the new message pipe.
|
|
|
|
Any messages written to `a` are eventually readable from `b`, and any messages
|
|
written to `b` are eventually readable from `a`. If `a` is closed at any point,
|
|
`b` will eventually become aware of this fact; likewise if `b` is closed, `a`
|
|
will become aware of that.
|
|
|
|
The state of these conditions can be queried and watched asynchronously as
|
|
described in the [Signals & Traps](#Signals-Traps) section below.
|
|
|
|
### Creating Messages
|
|
|
|
Message pipes carry message objects which may or may not be serialized. You can
|
|
create a new message object as follows:
|
|
|
|
``` c
|
|
MojoMessageHandle message;
|
|
MojoResult result = MojoCreateMessage(nullptr, &message);
|
|
```
|
|
|
|
Note that we have a special `MojoMessageHandle` type for message objects.
|
|
|
|
Messages may be serialized with attached data or unserialized with an
|
|
opaque context value. Unserialized messages support lazy serialization, allowing
|
|
custom serialization logic to be invoked only if and when serialization is
|
|
required, e.g. when the message needs to cross a process or language boundary.
|
|
|
|
To make a serialized message, you might write something like:
|
|
|
|
``` c
|
|
void* buffer;
|
|
uint32_t buffer_size;
|
|
MojoResult result = MojoAppendMessageData(message, nullptr, 6, nullptr, 0,
|
|
&buffer, &buffer_size);
|
|
memcpy(buffer, "hello", 6);
|
|
```
|
|
|
|
This attaches a data buffer to `message` with at least `6` bytes of storage
|
|
capacity. The outputs returned in `buffer` and `buffer_size` can be used by the
|
|
caller to fill in the message contents.
|
|
|
|
Multiple calls to `MojoAppendMessageData` may be made on a single message
|
|
object, and each call appends to any payload and handles accumulated so far.
|
|
Before you can transmit a message carrying data you must commit to never calling
|
|
`MojoAppendMessageData` again. You do this by passing the
|
|
`MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE` flag:
|
|
|
|
``` c
|
|
MojoAppendMessageDataOptions options;
|
|
options.struct_size = sizeof(options);
|
|
options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE;
|
|
MojoResult result = MojoAppendMessageData(message, &options, 0, nullptr, 0,
|
|
&buffer, &buffer_size);
|
|
```
|
|
|
|
Creating lazily-serialized messages is also straightforward:
|
|
|
|
``` c
|
|
struct MyMessage {
|
|
// some interesting data...
|
|
};
|
|
|
|
void SerializeMessage(MojoMessageHandle message, uintptr_t context) {
|
|
struct MyMessage* my_message = (struct MyMessage*)context;
|
|
|
|
MojoResult result = MojoAppendMessageData(message, ...);
|
|
// Serialize however you like.
|
|
}
|
|
|
|
void DestroyMessage(uintptr_t context) {
|
|
free((void*)context);
|
|
}
|
|
|
|
MyMessage* data = malloc(sizeof(MyMessage));
|
|
// initialize *data...
|
|
|
|
MojoResult result = MojoSetMessageContext(
|
|
message, (uintptr_t)data, &SerializeMessage, &DestroyMessage, nullptr);
|
|
```
|
|
|
|
If we change our mind and decide not to send the message, we can destroy it:
|
|
|
|
``` c
|
|
MojoResult result = MojoDestroyMessage(message);
|
|
```
|
|
|
|
Note that attempting to write a message will transfer ownership of the message
|
|
object (and any attached handles) into the message pipe, and there is therefore
|
|
no need to subsequently call `MojoDestroyMessage` on that message.
|
|
|
|
### Writing Messages
|
|
|
|
``` c
|
|
result = MojoWriteMessage(a, message, nullptr);
|
|
```
|
|
|
|
`MojoWriteMessage` is a *non-blocking* call: it always returns
|
|
immediately. If its return code is `MOJO_RESULT_OK` the message will eventually
|
|
find its way to the other end of the pipe -- assuming that end isn't closed
|
|
first, of course. If the return code is anything else, the message is deleted
|
|
and not transferred.
|
|
|
|
In this case since we know `b` is still open, we also know the message will
|
|
eventually arrive at `b`. `b` can be queried or watched to become aware of when
|
|
the message arrives, but we'll ignore that complexity for now. See
|
|
[Signals & Traps](#Signals-Traps) below for more information.
|
|
|
|
*** aside
|
|
**NOTE**: Although this is an implementation detail and not strictly guaranteed
|
|
by the System API, it is true in the current implementation that the message
|
|
will arrive at `b` before the above `MojoWriteMessage` call even returns,
|
|
because `b` is in the same process as `a` and has never been transferred over
|
|
another pipe.
|
|
***
|
|
|
|
### Reading Messages
|
|
|
|
We can read a new message object from a pipe:
|
|
|
|
``` c
|
|
MojoMessageHandle message;
|
|
MojoResult result = MojoReadMessage(b, nullptr, &message);
|
|
```
|
|
|
|
and extract its data:
|
|
|
|
``` c
|
|
void* buffer = NULL;
|
|
uint32_t num_bytes;
|
|
MojoResult result = MojoGetMessageData(message, nullptr, &buffer, &num_bytes,
|
|
nullptr, nullptr);
|
|
printf("Pipe says: %s", (const char*)buffer);
|
|
```
|
|
|
|
`result` should be `MOJO_RESULT_OK` and this snippet should write `"hello"` to
|
|
`stdout`.
|
|
|
|
If we try were to try reading again now that there are no messages on `b`:
|
|
|
|
``` c
|
|
MojoMessageHandle message;
|
|
MojoResult result = MojoReadMessage(b, nullptr, &message);
|
|
```
|
|
|
|
We'll get a `result` of `MOJO_RESULT_SHOULD_WAIT`, indicating that the pipe is
|
|
not yet readable.
|
|
|
|
Note that message also may not have been serialized if it came from within the
|
|
same process, in which case it may have no attached data and
|
|
`MojoGetMessageData` will return `MOJO_RESULT_FAILED_PRECONDITION`. The
|
|
message's unserialized context can instead be retrieved using
|
|
`MojoGetMessageContext`.
|
|
|
|
Messages read from a message pipe are owned by the caller and must be
|
|
subsequently destroyed using `MojoDestroyMessage` (or, in theory, written to
|
|
another pipe using `MojoWriteMessage`.)
|
|
|
|
### Messages With Handles
|
|
|
|
Probably the most useful feature of Mojo IPC is that message pipes can carry
|
|
arbitrary Mojo handles, including other message pipes. This is also
|
|
straightforward.
|
|
|
|
Here's an example which creates two pipes, using the first pipe to transfer
|
|
one end of the second pipe. If you have a good imagination you can pretend the
|
|
first pipe spans a process boundary, which makes the example more practically
|
|
interesting:
|
|
|
|
``` c
|
|
MojoHandle a, b;
|
|
MojoHandle c, d;
|
|
MojoCreateMessagePipe(NULL, &a, &b);
|
|
MojoCreateMessagePipe(NULL, &c, &d);
|
|
|
|
// Allocate a message with an empty payload and handle |c| attached. Note that
|
|
// this takes ownership of |c|, effectively invalidating its handle value.
|
|
MojoMessageHandle message;
|
|
void* buffer;
|
|
uint32_t buffer_size;
|
|
MojoCreateMessage(nullptr, &message);
|
|
|
|
MojoAppendMessageDataOptions options;
|
|
options.struct_size = sizeof(options);
|
|
options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE;
|
|
MojoAppendMessageData(message, &options, 2, &c, 1, &buffer, &buffer_size);
|
|
memcpy(buffer, "hi", 2);
|
|
MojoWriteMessage(a, message, nullptr);
|
|
|
|
// Some time later...
|
|
MojoHandle e;
|
|
uint32_t num_handles = 1;
|
|
MojoReadMessage(b, nullptr, &message);
|
|
MojoGetMessageData(message, nullptr, &buffer, &buffer_size, &e, &num_handles);
|
|
```
|
|
|
|
At this point the handle in `e` is now referencing the same message pipe
|
|
endpoint which was originally referenced by `c`.
|
|
|
|
Note that `num_handles` above is initialized to 1 before we pass its address to
|
|
`MojoGetMessageData`. This is to indicate how much `MojoHandle` storage is
|
|
available at the output buffer we gave it (`&e` above).
|
|
|
|
If we didn't know how many handles to expect in an incoming message -- which is
|
|
often the case -- we can use `MojoGetMessageData` to query for this information
|
|
first:
|
|
|
|
``` c
|
|
MojoMessageHandle message;
|
|
void* buffer;
|
|
uint32_t num_bytes = 0;
|
|
uint32_t num_handles = 0;
|
|
MojoResult result = MojoGetMessageData(message, nullptr, &buffer, &num_bytes,
|
|
nullptr, &num_handles);
|
|
```
|
|
|
|
If `message` has some non-zero number of handles, `result` will be
|
|
`MOJO_RESULT_RESOURCE_EXHAUSTED`, and both `num_bytes` and `num_handles` will be
|
|
updated to reflect the payload size and number of attached handles in the
|
|
message.
|
|
|
|
## Data Pipes
|
|
|
|
Data pipes provide an efficient unidirectional channel for moving large amounts
|
|
of unframed data between two endpoints. Every data pipe has a fixed
|
|
**element size** and **capacity**. Reads and writes must be done in sizes that
|
|
are a multiple of the element size, and writes to the pipe can only be queued
|
|
up to the pipe's capacity before reads must be done to make more space
|
|
available.
|
|
|
|
Every data pipe has a single **producer** handle used to write data into the
|
|
pipe and a single **consumer** handle used to read data out of the pipe.
|
|
|
|
Finally, data pipes support both immediate I/O -- reading into and writing out
|
|
from user-supplied buffers -- as well as two-phase I/O, allowing callers to
|
|
temporarily lock some portion of the data pipe in order to read or write its
|
|
contents directly.
|
|
|
|
See [//mojo/public/c/system/data_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/data_pipe.h)
|
|
for detailed data pipe API documentation.
|
|
|
|
### Creating Data Pipes
|
|
|
|
Use `MojoCreateDataPipe` to create a new data pipe. The
|
|
`MojoCreateDataPipeOptions` structure is used to configure the new pipe, but
|
|
this can be omitted to assume the default options of a single-byte element size
|
|
and an implementation-defined default capacity (64 kB at the time of this
|
|
writing.)
|
|
|
|
``` c
|
|
MojoHandle producer, consumer;
|
|
MojoResult result = MojoCreateDataPipe(NULL, &producer, &consumer);
|
|
```
|
|
|
|
### Immediate I/O
|
|
|
|
Data can be written into or read out of a data pipe using buffers provided by
|
|
the caller. This is generally more convenient than two-phase I/O but is
|
|
also less efficient due to extra copying.
|
|
|
|
``` c
|
|
uint32_t num_bytes = 12;
|
|
MojoResult result = MojoWriteData(producer, "datadatadata", &num_bytes,
|
|
nullptr);
|
|
```
|
|
|
|
The above snippet will attempt to write 12 bytes into the data pipe, which
|
|
should succeed and return `MOJO_RESULT_OK`. If the available capacity on the
|
|
pipe was less than the amount requested (the input value of `*num_bytes`) this
|
|
will copy what it can into the pipe and return the number of bytes written in
|
|
`*num_bytes`. If no data could be copied this will instead return
|
|
`MOJO_RESULT_SHOULD_WAIT`.
|
|
|
|
Reading from the consumer is a similar operation.
|
|
|
|
``` c
|
|
char buffer[64];
|
|
uint32_t num_bytes = 64;
|
|
MojoResult result = MojoReadData(consumer, nullptr, buffer, &num_bytes);
|
|
```
|
|
|
|
This will attempt to read up to 64 bytes, returning the actual number of bytes
|
|
read in `*num_bytes`.
|
|
|
|
`MojoReadData` supports a number of interesting flags to change the behavior:
|
|
you can peek at the data (copy bytes out without removing them from the pipe),
|
|
query the number of bytes available without doing any actual reading of the
|
|
contents, or discard data from the pipe without bothering to copy it anywhere.
|
|
|
|
This also supports a `MOJO_READ_DATA_FLAG_ALL_OR_NONE` which ensures that the
|
|
call succeeds **only** if the exact number of bytes requested could be read.
|
|
Otherwise such a request will fail with `MOJO_READ_DATA_OUT_OF_RANGE`.
|
|
|
|
### Two-Phase I/O
|
|
|
|
Data pipes also support two-phase I/O operations, allowing a caller to
|
|
temporarily lock a portion of the data pipe's storage for direct memory access.
|
|
|
|
``` c
|
|
void* buffer;
|
|
uint32_t num_bytes = 1024;
|
|
MojoResult result = MojoBeginWriteData(producer, nullptr, &buffer, &num_bytes);
|
|
```
|
|
|
|
This requests write access to a region of up to 1024 bytes of the data pipe's
|
|
next available capacity. Upon success, `buffer` will point to the writable
|
|
storage and `num_bytes` will indicate the size of the buffer there.
|
|
|
|
The caller should then write some data into the memory region and release it
|
|
ASAP, indicating the number of bytes actually written:
|
|
|
|
``` c
|
|
memcpy(buffer, "hello", 6);
|
|
MojoResult result = MojoEndWriteData(producer, 6, nullptr);
|
|
```
|
|
|
|
Two-phase reads look similar:
|
|
|
|
``` c
|
|
void* buffer;
|
|
uint32_t num_bytes = 1024;
|
|
MojoResult result = MojoBeginReadData(consumer, nullptr, &buffer, &num_bytes);
|
|
// result should be MOJO_RESULT_OK, since there is some data available.
|
|
|
|
printf("Pipe says: %s", (const char*)buffer); // Should say "hello".
|
|
|
|
// Say we only consumed one byte.
|
|
result = MojoEndReadData(consumer, 1, nullptr);
|
|
|
|
num_bytes = 1024;
|
|
result = MojoBeginReadData(consumer, nullptr, &buffer, &num_bytes);
|
|
printf("Pipe says: %s", (const char*)buffer); // Should say "ello".
|
|
result = MojoEndReadData(consumer, 5, nullptr);
|
|
```
|
|
|
|
## Shared Buffers
|
|
|
|
Shared buffers are chunks of memory which can be mapped simultaneously by
|
|
multiple processes. Mojo provides a simple API to make these available to
|
|
applications.
|
|
|
|
See [//mojo/public/c/system/buffer.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/buffer.h)
|
|
for detailed shared buffer API documentation.
|
|
|
|
### Creating Buffer Handles
|
|
|
|
Usage is straightforward. You can create a new buffer:
|
|
|
|
``` c
|
|
// Allocate a shared buffer of 4 kB.
|
|
MojoHandle buffer;
|
|
MojoResult result = MojoCreateSharedBuffer(4096, NULL, &buffer);
|
|
```
|
|
|
|
You can also duplicate an existing shared buffer handle:
|
|
|
|
``` c
|
|
MojoHandle another_name_for_buffer;
|
|
MojoResult result = MojoDuplicateBufferHandle(buffer, NULL,
|
|
&another_name_for_buffer);
|
|
```
|
|
|
|
This is useful if you want to retain a handle to the buffer while also sharing
|
|
handles with one or more other clients. The allocated buffer remains valid as
|
|
long as at least one shared buffer handle exists to reference it.
|
|
|
|
### Mapping Buffers
|
|
|
|
You can map (and later unmap) a specified range of the buffer to get direct
|
|
memory access to its contents:
|
|
|
|
``` c
|
|
void* data;
|
|
MojoResult result = MojoMapBuffer(buffer, 0, 64, nullptr, &data);
|
|
|
|
*(int*)data = 42;
|
|
result = MojoUnmapBuffer(data);
|
|
```
|
|
|
|
A buffer may have any number of active mappings at a time, in any number of
|
|
processes.
|
|
|
|
### Read-Only Handles
|
|
|
|
An option can also be specified on `MojoDuplicateBufferHandle` to ensure
|
|
that the newly duplicated handle can only be mapped to read-only memory:
|
|
|
|
``` c
|
|
MojoHandle read_only_buffer;
|
|
MojoDuplicateBufferHandleOptions options;
|
|
options.struct_size = sizeof(options);
|
|
options.flags = MOJO_DUPLICATE_BUFFER_HANDLE_FLAG_READ_ONLY;
|
|
MojoResult result = MojoDuplicateBufferHandle(buffer, &options,
|
|
&read_only_buffer);
|
|
|
|
// Attempt to map and write to the buffer using the read-only handle:
|
|
void* data;
|
|
result = MojoMapBuffer(read_only_buffer, 0, 64, nullptr, &data);
|
|
*(int*)data = 42; // CRASH
|
|
```
|
|
|
|
*** note
|
|
**NOTE:** One important limitation of the current implementation is that
|
|
read-only handles can only be produced from a handle that was originally created
|
|
by `MojoCreateSharedBuffer` (*i.e.*, you cannot create a read-only duplicate
|
|
from a non-read-only duplicate), and the handle cannot have been transferred
|
|
over a message pipe first.
|
|
***
|
|
|
|
## Native Platform Handles (File Descriptors, Windows Handles, *etc.*)
|
|
|
|
Native platform handles to system objects can be wrapped as Mojo handles for
|
|
seamless transit over message pipes. Mojo currently supports wrapping POSIX
|
|
file descriptors, Windows handles, Mach ports, and Fuchsia zx_handles.
|
|
|
|
See [//mojo/public/c/system/platform_handle.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/platform_handle.h)
|
|
for detailed platform handle API documentation.
|
|
|
|
### Wrapping Basic Handle Types
|
|
|
|
Wrapping a POSIX file descriptor is simple:
|
|
|
|
``` c
|
|
MojoPlatformHandle platform_handle;
|
|
platform_handle.struct_size = sizeof(platform_handle);
|
|
platform_handle.type = MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR;
|
|
platform_handle.value = (uint64_t)fd;
|
|
MojoHandle handle;
|
|
MojoResult result = MojoWrapPlatformHandle(&platform_handle, nullptr, &handle);
|
|
```
|
|
|
|
Note that at this point `handle` effectively owns the file descriptor
|
|
and if you were to call `MojoClose(handle)`, the file descriptor would be closed
|
|
too; but we're not going to close it here! We're going to pretend we've sent it
|
|
over a message pipe, and now we want to unwrap it on the other side:
|
|
|
|
``` c
|
|
MojoPlatformHandle platform_handle;
|
|
platform_handle.struct_size = sizeof(platform_handle);
|
|
MojoResult result = MojoUnwrapPlatformHandle(handle, nullptr, &platform_handle);
|
|
int fd = (int)platform_handle.value;
|
|
```
|
|
|
|
The situation looks nearly identical for wrapping and unwrapping Windows handles
|
|
and Mach ports.
|
|
|
|
### Wrapping Shared Buffer Handles
|
|
|
|
Unlike other handle types, shared buffers have special meaning in Mojo, and it
|
|
may be desirable to wrap a native platform handle -- along with some extra
|
|
metadata -- such that be treated like a real Mojo shared buffer handle.
|
|
Conversely it can also be useful to unpack a Mojo shared buffer handle into
|
|
a native platform handle which references the buffer object. Both of these
|
|
things can be done using the `MojoWrapPlatformSharedBuffer` and
|
|
`MojoUnwrapPlatformSharedBuffer` APIs.
|
|
|
|
On Windows, the wrapped platform handle must always be a Windows handle to
|
|
a file mapping object.
|
|
|
|
On OS X, the wrapped platform handle must be a memory-object send right.
|
|
|
|
On all other POSIX systems, the wrapped platform handle must be a file
|
|
descriptor for a shared memory object.
|
|
|
|
## Signals & Traps
|
|
|
|
Message pipe and data pipe (producer and consumer) handles can change state in
|
|
ways that may be interesting to a Mojo API user. For example, you may wish to
|
|
know when a message pipe handle has messages available to be read or when its
|
|
peer has been closed. Such states are reflected by a fixed set of boolean
|
|
signals on each pipe handle.
|
|
|
|
### Signals
|
|
|
|
Every message pipe and data pipe handle maintains a notion of
|
|
**signaling state** which may be queried at any time. For example:
|
|
|
|
``` c
|
|
MojoHandle a, b;
|
|
MojoCreateMessagePipe(NULL, &a, &b);
|
|
|
|
MojoHandleSignalsState state;
|
|
MojoResult result = MojoQueryHandleSignalsState(a, &state);
|
|
```
|
|
|
|
The `MojoHandleSignalsState` structure exposes two fields: `satisfied_signals`
|
|
and `satisfiable_signals`. Both of these are bitmasks of the type
|
|
`MojoHandleSignals` (see [//mojo/public/c/system/types.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/types.h)
|
|
for more details.)
|
|
|
|
The `satisfied_signals` bitmask indicates signals which were satisfied on the
|
|
handle at the time of the call, while the `satisfiable_signals` bitmask
|
|
indicates signals which were still possible to satisfy at the time of the call.
|
|
It is thus by definition always true that:
|
|
|
|
``` c
|
|
(satisfied_signals | satisfiable_signals) == satisfiable_signals
|
|
```
|
|
|
|
In other words a signal obviously cannot be satisfied if it is no longer
|
|
satisfiable. Furthermore once a signal is unsatisfiable, *i.e.* is no longer
|
|
set in `sastisfiable_signals`, it can **never** become satisfiable again.
|
|
|
|
To illustrate this more clearly, consider the message pipe created above. Both
|
|
ends of the pipe are still open and neither has been written to yet. Thus both
|
|
handles start out with the same signaling state:
|
|
|
|
| Field | State |
|
|
|-----------------------|-------|
|
|
| `satisfied_signals` | `MOJO_HANDLE_SIGNAL_WRITABLE`
|
|
| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_WRITABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED`
|
|
|
|
Writing a message to handle `b` will eventually alter the signaling state of `a`
|
|
such that `MOJO_HANDLE_SIGNAL_READABLE` also becomes satisfied. If we were to
|
|
then close `b`, the signaling state of `a` would look like:
|
|
|
|
| Field | State |
|
|
|-----------------------|-------|
|
|
| `satisfied_signals` | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED`
|
|
| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED`
|
|
|
|
Note that even though `a`'s peer is known to be closed (hence making `a`
|
|
permanently unwritable) it remains readable because there's still an unread
|
|
received message waiting to be read from `a`.
|
|
|
|
Finally if we read the last message from `a` its signaling state becomes:
|
|
|
|
| Field | State |
|
|
|-----------------------|-------|
|
|
| `satisfied_signals` | `MOJO_HANDLE_SIGNAL_PEER_CLOSED`
|
|
| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_PEER_CLOSED`
|
|
|
|
and we know definitively that `a` can never be read from again.
|
|
|
|
### Trapping Signals
|
|
|
|
The ability to query a handle's signaling state can be useful, but it's not
|
|
sufficient to support robust and efficient pipe usage. Mojo traps empower users
|
|
with the ability to **trap** changes in a handle's signaling state and
|
|
automatically invoke a notification handler in response.
|
|
|
|
When a trap is created it must be bound to a function pointer matching
|
|
the following signature, defined in
|
|
[//mojo/public/c/system/trap.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/trap.h):
|
|
|
|
``` c
|
|
typedef void (*MojoTrapEventHandler)(const struct MojoTrapEvent* event);
|
|
```
|
|
|
|
The `event` parameter conveys details about why the event handler is being
|
|
invoked. The handler may be called **at any time** and **from any thread**, so
|
|
it is critical that handler implementations account for this.
|
|
|
|
It's also helpful to understand a bit about the mechanism by which the handler
|
|
can be invoked. Essentially, any Mojo C System API call may elicit a handle
|
|
state change of some kind. If such a change is relevant to conditions watched by
|
|
a trap, and that trap is in a state which allows it raise a corresponding
|
|
notification, its notification handler will be invoked synchronously some time
|
|
before the stack unwinds beyond the outermost System API call on the current
|
|
thread.
|
|
|
|
Handle state changes can also occur as a result of incoming IPC from an external
|
|
process. If a pipe in the current process is connected to an endpoint in another
|
|
process and the internal Mojo system receives an incoming message bound for the
|
|
local endpoint, the arrival of that message may trigger a state change on the
|
|
receiving handle and may therefore invoke one or more traps' notification
|
|
handlers as a result.
|
|
|
|
The `MOJO_TRAP_EVENT_FLAG_WITHIN_API_CALL` flag on the `flags` field of `event`
|
|
is used to indicate whether the handler was invoked due to such an internal
|
|
system IPC event (if the flag is unset), or if it was invoked synchronously due
|
|
to some local API call (if the flag is set.) This distinction can be useful to
|
|
make in certain cases to *e.g.* avoid accidental reentrancy in user code.
|
|
|
|
### Creating a Trap
|
|
|
|
Creating a trap is simple:
|
|
|
|
``` c
|
|
|
|
void OnNotification(const struct MojoTrapEvent* event) {
|
|
// ...
|
|
}
|
|
|
|
MojoHandle t;
|
|
MojoResult result = MojoCreateTrap(&OnNotification, NULL, &t);
|
|
```
|
|
|
|
Like all other `MojoHandle` types, traps may be destroyed by closing them with
|
|
`MojoClose`. Unlike most other `MojoHandle` types, trap handles are **not**
|
|
transferrable across message pipes.
|
|
|
|
In order for a trap to be useful, it has have at least one **trigger** attached
|
|
to it.
|
|
|
|
### Adding a Trigger to a Trap
|
|
|
|
Any given trap can watch any given (message or data pipe) handle for some set
|
|
of signaling conditions. A handle may be watched simultaneously by multiple
|
|
traps, and a single trap can watch multiple different handles simultaneously.
|
|
|
|
``` c
|
|
MojoHandle a, b;
|
|
MojoCreateMessagePipe(NULL, &a, &b);
|
|
|
|
// Watch handle |a| for readability.
|
|
const uintptr_t context = 1234;
|
|
MojoResult result = MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
|
|
MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
|
|
context, NULL);
|
|
```
|
|
|
|
We've successfully instructed trap `t` to begin watching pipe handle `a` for
|
|
readability. However, our recently created trap is still in a **disarmed**
|
|
state, meaning that it will never fire a notification pertaining to this
|
|
trigger. It must be **armed** before that can happen.
|
|
|
|
### Arming a Trap
|
|
|
|
In order for a trap to invoke its notification handler in response to a relevant
|
|
signaling state change on a watched handle, it must first be armed. A trap may
|
|
only be armed if none of its attached triggers would elicit a notification
|
|
immediately once armed.
|
|
|
|
In this case `a` is clearly not yet readable, so arming should succeed:
|
|
|
|
``` c
|
|
MojoResult result = MojoArmTrap(t, NULL, NULL, NULL);
|
|
```
|
|
|
|
Now we can write to `b` to make `a` readable:
|
|
|
|
``` c
|
|
MojoMessageHandle m;
|
|
MojoCreateMessage(nullptr, &m);
|
|
MojoWriteMessage(b, m, nullptr);
|
|
```
|
|
|
|
Eventually -- and in practice possibly before `MojoWriteMessage` even
|
|
returns -- this will cause `OnNotification` to be invoked on the calling thread
|
|
with the `context` value (*i.e.* 1234) that was given when the trigger was added
|
|
to the trap.
|
|
|
|
The `result` field of the event will be `MOJO_RESULT_OK` to indicate that the
|
|
trigger's condition has been met. If the handle's state had instead changed in
|
|
such a way that the trigger's condition could never be met again (*e.g.* if `b`
|
|
were instead closed), `result` would instead indicate
|
|
`MOJO_RESULT_FAILED_PRECONDITION`.
|
|
|
|
**NOTE:** Immediately before a trigger decides to invoke its event handler, it
|
|
automatically disarms itself to prevent another state change from eliciting
|
|
another notification. Therefore a trap must be repeatedly rearmed in order to
|
|
continue dispatching events.
|
|
|
|
As noted above, arming a watcher may fail if any of its triggers would be
|
|
activated immediately. In that case, the caller may provide buffers to
|
|
`MojoArmTrap` to receive information about a subset of the triggers which caused
|
|
it to fail:
|
|
|
|
``` c
|
|
// Provide some storage for information about triggers that would have been
|
|
// activated immediately.
|
|
uint32_t num_blocking_events = 2;
|
|
MojoTrapEvent blocking_events[2] = {{sizeof(MojoTrapEvent)},
|
|
{sizeof(MojoTrapEvent)}};
|
|
MojoResult result = MojoArmTrap(t, NULL, &num_blocking_events,
|
|
&blocking_events);
|
|
```
|
|
|
|
Because `a` is still readable this operation will now fail with
|
|
`MOJO_RESULT_FAILED_PRECONDITION`. The input value of `num_blocking_events`
|
|
informs `MojoArmTrap` that it may store information regarding up to 2 triggers
|
|
which have prevented arming. In this case of course there is only one active
|
|
trigger, so upon return we will see:
|
|
|
|
* `num_blocking_events` is `1`.
|
|
* `blocking_events[0].trigger_context` is `1234`.
|
|
* `blocking_events[0].result` is `MOJO_RESULT_OK`
|
|
* `blocking_events[0].signals_state` is the last known signaling state of handle
|
|
`a`.
|
|
|
|
In other words the stored information mirrors what would have been the resulting
|
|
event structure if the trap were allowed to arm and then notify immediately.
|
|
|
|
### Removing a Trigger
|
|
|
|
There are three ways a trigger can be removed:
|
|
|
|
* The handle being watched by the trigger is closed
|
|
* The trap handle is closed, in which case all of its attached triggers are
|
|
implicitly removed.
|
|
* `MojoRemoveTrigger` is called for a given `context`.
|
|
|
|
In the above example this means any of the following operations will cancel the
|
|
watch on `a`:
|
|
|
|
``` c
|
|
// Close the watched handle...
|
|
MojoClose(a);
|
|
|
|
// OR close the trap handle...
|
|
MojoClose(t);
|
|
|
|
// OR explicitly remove it.
|
|
MojoResult result = MojoRemoveTrigger(t, 1234, NULL);
|
|
```
|
|
|
|
In every case the trap's event handler is invoked for the cancelled trigger(es)
|
|
regardless of whether or not the trap was armed at the time. The event handler
|
|
receives a `result` of `MOJO_RESULT_CANCELLED` for each of these invocations,
|
|
and this is guaranteed to be the final event for any given trigger context.
|
|
|
|
### Practical Trigger Context Usage
|
|
|
|
It is common and probably wise to treat a trigger's `context` value as an opaque
|
|
pointer to some thread-safe state associated in some way with the handle being
|
|
watched. Here's a small example which uses a single trap to watch both ends of a
|
|
message pipe and accumulate a count of messages received at each end.
|
|
|
|
``` c
|
|
// NOTE: For the sake of simplicity this example code is not in fact
|
|
// thread-safe. As long as there's only one thread running in the process and
|
|
// no external process connections, this is fine.
|
|
|
|
struct WatchedHandleState {
|
|
MojoHandle trap;
|
|
MojoHandle handle;
|
|
int message_count;
|
|
};
|
|
|
|
void OnNotification(const struct MojoTrapEvent* event) {
|
|
struct WatchedHandleState* state =
|
|
(struct WatchedHandleState*)(event->trigger_context);
|
|
MojoResult rv;
|
|
|
|
if (event->result == MOJO_RESULT_CANCELLED) {
|
|
// Cancellation is always the last event and is guaranteed to happen for
|
|
// every context, assuming no handles are leaked. We treat this as an
|
|
// opportunity to free the WatchedHandleState.
|
|
free(state);
|
|
return;
|
|
}
|
|
|
|
if (result == MOJO_RESULT_FAILED_PRECONDITION) {
|
|
// No longer readable, i.e. the other handle must have been closed. Better
|
|
// cancel. Note that we could also just call MojoClose(state->trap) here
|
|
// since we know there's only one attached trigger.
|
|
MojoRemoveTrigger(state->trap, event->trigger_context, NULL);
|
|
return;
|
|
}
|
|
|
|
// This is the only handle watched by the trap, so as long as we can't arm
|
|
// the watcher we know something's up with this handle. Try to read messages
|
|
// until we can successfully arm again or something goes terribly wrong.
|
|
while (MojoArmTrap(state->trap, NULL NULL, NULL) ==
|
|
MOJO_RESULT_FAILED_PRECONDITION) {
|
|
rv = MojoReadMessageNew(state->handle, NULL, NULL, NULL,
|
|
MOJO_READ_MESSAGE_FLAG_MAY_DISCARD);
|
|
if (rv == MOJO_RESULT_OK) {
|
|
state->message_count++;
|
|
} else if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
|
|
MojoRemoveTrigger(state->trap, event->trigger_context, NULL);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
MojoHandle a, b;
|
|
MojoCreateMessagePipe(NULL, &a, &b);
|
|
|
|
MojoHandle a_trap, b_trap;
|
|
MojoCreateTrap(&OnNotification, NULL, &a_trap);
|
|
MojoCreateTrap(&OnNotification, NULL, &b_trap)
|
|
|
|
struct WatchedHandleState* a_state = malloc(sizeof(struct WatchedHandleState));
|
|
a_state->trap = a_trap;
|
|
a_state->handle = a;
|
|
a_state->message_count = 0;
|
|
|
|
struct WatchedHandleState* b_state = malloc(sizeof(struct WatchedHandleState));
|
|
b_state->trap = b_trap;
|
|
b_state->handle = b;
|
|
b_state->message_count = 0;
|
|
|
|
MojoAddTrigger(a_trap, a, MOJO_HANDLE_SIGNAL_READABLE,
|
|
MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, (uintptr_t)a_state,
|
|
NULL);
|
|
MojoAddTrigger(b_trap, b, MOJO_HANDLE_SIGNAL_READABLE,
|
|
MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, (uintptr_t)b_state,
|
|
NULL);
|
|
|
|
MojoArmTrap(a_trap, NULL, NULL, NULL);
|
|
MojoArmTrap(b_trap, NULL, NULL, NULL);
|
|
```
|
|
|
|
Now any writes to `a` will increment `message_count` in `b_state`, and any
|
|
writes to `b` will increment `message_count` in `a_state`.
|
|
|
|
If either `a` or `b` is closed, both watches will be cancelled - one because
|
|
watch cancellation is implicit in handle closure, and the other because its
|
|
watcher will eventually detect that the handle is no longer readable.
|
|
|
|
## Invitations
|
|
|
|
TODO.
|
|
|
|
For now see the
|
|
[C header](https://cs.chromium.org/src/mojo/public/c/system/invitation.h) and
|
|
the documentation for the equivalent
|
|
[C++ API](/mojo/public/cpp/system/README.md#Invitations).
|