# 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).