Skip to content

Static channel config and the ASIL client - #136

Draft
dallison wants to merge 5 commits into
mainfrom
asil
Draft

dallison wants to merge 5 commits into
mainfrom
asil

Conversation

@dallison

@dallison dallison commented Oct 9, 2026

Copy link
Copy Markdown
Owner

Summary

This branch adds three things, one per commit:

  1. Static channel config (--channel_config=FILE). The server creates a fixed set of channels and multiplexers before it accepts clients. Only those channels can be used, and their layout is fixed.
  2. The ASIL client (asil_client/). A C++17 client for safety-related software, built with no exceptions or RTTI and no heap allocation. It shares channels with the standard clients through the same shared memory protocol. Its server handshake uses phaser messages in fixed buffers, sent in protobuf wire format.
  3. Map at open, and split buffers. ASIL publishers and subscribers acquire all of their shared memory in Open(), so publishing and reading make no mmap or shm_open calls. Split buffers are supported.

Server changes

  • The server creates the message buffers of every static channel and multiplexer at startup, so a subscriber can map them before any publisher exists.
  • use_split_buffers in a static channel or multiplexer config gives it split buffers, which the server creates. A publisher whose split setting differs from the config is rejected.
  • GetClientBuffersRequest can fetch one prefix or slot of a split buffer set (filter_slot, is_prefix, slot_id). This keeps each response inside the ASIL client's 16 KB wire buffer.
  • Publishers on different virtual channels of a multiplexer are checked against each other for fixed size and locality. Before this, a static multiplexer could only have publishers on one virtual channel.

ASIL client

  • Supports:
    • unreliable and reliable publishers and subscribers
    • virtual channels and whole-multiplexer subscribers
    • up to 64 held messages
    • subscriber queues
    • checksums, metadata, kReadNext/kReadNewest, drop counts and activation messages
    • POSIX shm, /dev/shm and memfd
  • Each publisher or subscriber maps the channel's newest buffer when it opens.
    • On a non-static channel with no buffers yet, a subscriber's Open() returns kNoBuffers.
    • A message in a buffer that a standard publisher created by resizing after the subscriber opened is skipped, and the read returns kBufferNotMapped.
  • Split buffers are mapped into a SplitSlot array that the caller provides. Slots from a standard publisher's custom allocator are mapped through C map/unmap callbacks with a context pointer.

See docs/asil-client.md for details.

Dependencies

The pinned phaser and cpp_toolbelt commits are in dallison/phaser#13 and dallison/cpp_toolbelt#29. MODULE.bazel uses git_override until those versions reach the Bazel Central Registry.

Testing

  • bazelisk test --config=macos_arm64 //...: 28/28 pass.
  • CMake: asil_layout_test and asil_client_test pass.
  • Linux (Ubuntu 24.04, arm64): //asil_client/... and //server/... pass with /dev/shm and with --config=linux_memfd.
  • asil_client_test (34 tests) exchanges messages between the ASIL and standard clients in both directions. It covers server-created static buffers, split buffers, custom allocators, resized channels and the rejected cases.
  • layout_test checks the ASIL shared memory layout against common/channel.h.

subspace_server --channel_config=FILE reads a StaticChannelConfig in
protobuf text format and creates its channels and multiplexers before the
server accepts any clients, so a subscriber that starts first gets the full
channel layout instead of a placeholder.

With a config loaded, clients can use only the configured channels and the
server's own /subspace/ChannelDirectory and /subspace/Statistics.  The
configured channels stay when they have no users, and their layout is
fixed: publishers must be fixed size and match the configured slot size,
number of slots, checksum size and metadata size.  Channels recovered from a
shadow must match the config.

A multiplexer with no publishers now reports its slot size.
asil_client/ is a C++17 client for safety-related software.  It shares
unreliable, fixed-size channels with the standard clients through the same
shared memory protocol, using only the standard library and POSIX.  It is
built without exceptions or RTTI, returns an error code from every
operation, and makes no heap allocations.

- Publishers support checksums, metadata and activation messages.
  Subscribers support kReadNext and kReadNewest, dropped message counts,
  checksum checking and activation messages.
- The server handshake is behind the ServerConnection interface.
  PhaserServerConnection implements the standard protocol with phaser
  messages in fixed buffers, sent and received in protobuf wire format, so
  the server sees the same bytes as from the other clients.
- Bazel, CMake and Soong build phaser and generate subspace_phaser from
  subspace.proto.  Android CMake cross-compiles take a host
  protoc-gen-phaser in PHASER_PLUGIN_EXECUTABLE.
- Bazel moves to Abseil 20250814.1, protobuf 33.4, cpp_toolbelt 2.1.6 and
  phaser 2.1.3, with cpp_toolbelt and phaser pinned by git_override until
  they are in the Bazel Central Registry.  CMake uses the same commits.
- layout_test checks the ASIL client's view of shared memory against
  common/channel.h.  asil_client_test exchanges messages with the standard
  client in both directions, checks that setup and teardown make no heap
  allocations, and checks that an oversized request leaves the connection
  usable.  It is skipped on memfd builds, which the ASIL client doesn't
  support yet.
The server now creates the message buffers of every static channel and
multiplexer when it starts, through the same code as a standard first
publisher, so subscribers can map them before any publisher exists.  A
static channel or multiplexer with use_split_buffers gets split buffers,
which the server creates, and publishers whose split setting differs
from the config are rejected.  GetClientBuffersRequest can ask for one
prefix or slot of a split buffer set.

ASIL publishers and subscribers map all of a channel's shared memory in
Open(), and publishing and reading make no shared memory calls.  They
map the channel's newest buffer; the resize, placeholder remap and
16-buffer table are gone.  A subscriber on a channel without buffers
gets kNoBuffers, and a message in a buffer created by a resize after the
subscriber opened returns kBufferNotMapped.

The ASIL client also supports split buffers, mapping each slot into an
array the caller provides.  Slots from a standard publisher's custom
allocator are mapped through C map/unmap callbacks with a context.

The ASIL client also gains reliable publishers and subscribers, virtual
channels, multiple held messages, subscriber queues and the memfd
backend, and the server checks fixed size and locality across the
virtual channels of a multiplexer.
The standard C++ client now builds its server requests and responses as
phaser messages, like the ASIL client. ServerWire in common/server_wire.h
serializes requests and decodes responses for both, with growable buffers
for the standard client and fixed buffers for the ASIL connection, which
still makes no heap allocations. The bytes on the socket are unchanged.

CMake now requires PHASER_PLUGIN_EXECUTABLE when cross-compiling, since
the standard client needs subspace_phaser.
A static channel's layout comes from the server's config, so its
publishers now set a server_defined option instead of the options that
shape the shared memory. The server rejects a publisher on a static
channel that leaves out server_defined or sets num_slots, slot_size,
fixed_size, checksum_size, metadata_size, subscriber_queue_arena_size or
use_split_buffers, and rejects server_defined on any other channel.

CreatePublisherResponse now reports the channel's slot size, checksum
size, metadata size, split buffer setting and fixed size, and a
server_defined publisher takes its layout from them. The C++, C, Python,
Rust and ASIL clients have the option and check it before asking the
server. The C++ and Rust default checksum_size is now 0, which means 4
bytes, and a bridge publisher on a static channel is server defined.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant