A one-way message queue between two programs (or two parts of one) over shared memory.
A channel has one reader and one writer. The reader creates it by name with reader_create_channel, the writer attaches
with writer_connect. The writer sends a message by reserving room (writer_reserve_message), filling it in place and
committing it (writer_commit_message); the reader finds messages in order with reader_poll_for_message, reads them
in place and finishes each with reader_done_with_message. Nothing copies the payload and nothing blocks: a full ring
makes a reserve fail, an empty one makes a poll find nothing, and the program decides how to wait. The ring keeps messages
whole, 8-byte aligned, so a message must fit in the ring in one piece.
The pieces are lock-free and safe for exactly one reader and one writer, possibly in different processes. The other end
is not trusted to be well-behaved: a damaged header or cursor is reported as a fatal error instead of being followed.
Needs Linux, macOS or Windows. Names and the lifetime of the shared object are described on reader_create_channel.
Creates the channel called name with a region of bytes bytes and returns it as (success, channel). The caller becomes
its reader and owns the name: nobody can connect until it exists, and the name goes away with reader_release_channel.
name := tprint("jai-doc-%", to_nanoseconds(current_time_monotonic()));
ok, reader := reader_create_channel(name, 1024);
assert(ok);
assert(reader.header.data_size_in_bytes == 768);
taken, again := reader_create_channel(name, 1024);
assert(!taken); // the name belongs to the first readerassert(reader_release_channel(*reader));
The Message_Queue_Header of a channel, given its Header: a pointer into the shared region, valid until the channel is released.
Handy for asking how much is waiting (write_cursor - read_cursor).
Claims room for a message of data_bytes bytes and returns (success, reserved); write the payload into
reserved.user_write_pointer, then call writer_commit_message. Nothing is visible to the reader until the commit.
Publishes a reserved message: from this call on the reader can find it. Returns false if the reservation is not valid
any more (it was not made by this writer on this channel, the record was changed, or the reader has disconnected), and
the message is then not published.
Marks the message as read and frees its space for the writer; the payload must not be used afterwards. Messages have to be
finished in order, so this works on the message reader_poll_for_message returned last. Returns false, and frees nothing,
for a message that was already finished (the record is cleared on success) or whose bookkeeping does not match the channel.
The platform half of reader_create_channel, which calls it: creates the shared-memory object (shm_open with O_EXCL
on Unix, a file mapping on Windows), maps it and writes the header. Call reader_create_channel instead.
Disconnects the writer: the channel marks the writer as gone, the mapping is closed and channel.header is cleared. Messages
already committed stay readable by the reader. A new writer may then connect. Returns false for a writer that is not connected
(including a second call) or if the system reports an error while unmapping or closing.
Closes the reading end and removes the channel's name, so no writer can connect afterwards; a connected writer sees the
reader as gone and its reserves fail. The mapping is closed and channel.header is cleared. Unread messages are lost.
Returns false for a channel that is not open or if the system reports an error. Always call it, or the name stays taken on
Unix targets until the system is restarted.
What the reader keeps for the operating system: the file descriptor of the shared-memory object on Unix targets, the mapping
handle on Windows. Only reader_release_channel should close it.
What the writer keeps for the operating system: the file descriptor of the shared-memory object on Unix targets, the mapping
handle on Windows. Only writer_disconnect should close it.
The reading end of a channel, from reader_create_channel. It points at the shared region through header. Give it back with
reader_release_channel; the pointer is not valid after that. Keep the record where it is while in use, and do not copy it for
a second reader: a channel has exactly one reader.
The writing end of a channel, from writer_connect. It points at the shared region through header. Give it back with
writer_disconnect. A channel has exactly one writer at a time.
The two cursors of the message ring, in bytes counted from the start of the channel's life: read_cursor is how much the
reader has finished with and write_cursor how much the writer has committed. They only grow; the position in the ring is the
cursor modulo data_size_in_bytes, and write_cursor - read_cursor is what is waiting. They sit in different cache lines.
Get the record with get_message_queue_header.
A message the reader has found with reader_poll_for_message: user_read_pointer and user_read_bytes give its payload,
which is read in place in the shared region. Pass it to reader_done_with_message when finished, which frees the space for
the writer; the payload must not be used afterwards. Messages arrive in the order they were committed.