Repository navigation
Conversation
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.
…ive Phaser payloads * Send native Phaser payloads between the clients and the server. The C++ clients, the server and the Rust client exchange the Phaser message buffer itself instead of transcoding to protobuf wire format. Phaser 3.1.1 adds copying and parsing that payload from C and Rust. Fixed-buffer errors in the ASIL connection keep their text without allocating, so an oversized request or response says why it failed. * Share the shared memory protocol between the ASIL and standard clients. common/ now holds the memory layout, the channel control block view, the slot search, publishing, taking, releasing and skipping messages, the message checksum and CRC32. Both clients use them, so they can't drift. The ASIL client keeps its no-exception, no-allocation build. Sharing the code fixed these bugs: - The ASIL publisher could reuse a slot with a stale retired bit, losing an unread message, and its checksum didn't mask transport flags. - A reliable subscriber reading the newest message in the standard client didn't mark the skipped messages seen, so a reliable publisher could no longer get a slot once the free slots ran out. - A standard subscriber ignoring an activation message kept its owner bit, so a crash later made the server drop another message's reference. - Retirement notification for a bridged message crashed if its original slot id was beyond this channel's slots.
- crc32.cc included <nmmintrin.h> inside namespace subspace, which broke libc++ on Android x86_64, where SSE4.2 is on by default. - The ASIL oversized response test bound a filesystem socket, but Connect() uses the abstract namespace on Linux. On a failed connect it also hung, as closing a socket does not wake accept() on Linux. - The CMake build of the Rust client had no Phaser bindings. It now generates them with phaser's C frontend and links their C++ code into one shared library for Cargo. The new phaser_c crate includes them. - Pin phaser to dallison/phaser#22, which brings phaser's CMake build up to date and adds the C and Rust output.
The plugins include the server headers, which include the generated subspace_phaser headers, but outside Android they don't link libserver and so could compile before the headers were generated.
The type of a channel in a StaticChannelConfig was a string, so it could not hold an opaque blob such as ROS metadata or a protobuf descriptor. Make it bytes, the same as the channel type on the wire. Quoted text still parses to the same bytes, and protobuf text escapes carry blobs. Add a test that a blob type is kept and that a publisher must match it.
- Pass -undefined dynamic_lookup when linking the plugin modules on macOS so server symbols can resolve at load time, matching Linux's --unresolved-symbols=ignore-all. - Build static nop_plugin_lib and split_buffer_free_test_plugin_lib targets on macOS in CMake (and split_buffer_free_test_plugin_lib in Bazel) and link them into client_test, so NOP_Create() resolves and SplitBufferPluginTest can load the split-buffer test plugin via LoadBuiltinPlugin instead of skipping on macOS.
The ASIL client had no stress test, unlike the standard client. Add asil_client/stress_test.cc, which covers threads, the active message limit, virtual channels, many channels, and mixing with standard clients on the same channels. The active message test checks that a held payload still holds the sequence it was read with, and the many channel test fails when a subscriber makes no progress for 10 seconds, rather than spinning when its fd stays readable. Add it to Bazel as a manual test, so it runs only when named, and to the CMake build as asil_stress_test. Document it in asil-client.md.
Checksums: the x86 SSE4.2 _mm_crc32_* instructions compute CRC-32C, not the IEEE 802.3 CRC32 used by the Rust client and by every other build. Builds with SSE4.2 enabled therefore produced checksums that did not verify against the rest. x86 now always uses the IEEE lookup table. ARM hardware CRC32 is IEEE and is unchanged. Note: this changes checksum values for x86 builds with SSE4.2 enabled. Such a build will not interoperate with builds from before this change. asil_client test: the sanity-check allocation was a direct operator new, which an optimized build can lower to malloc, so the counter stayed at 0 and HandshakeMakesNoHeapAllocations failed under -O2. Call operator new through a volatile function pointer so the allocation is always counted.
With a static channel config loaded, the server used to reject every channel the config doesn't list. Now: - Configured channels are shared by standard and ASIL clients. Publishers on them must set server_defined, as before. - A standard publisher can create an unconfigured channel by specifying its layout. The server rejects server_defined on such a channel. - Any subscriber can subscribe to an unconfigured channel, including an ASIL subscriber, which maps no buffers until a publisher creates them. - An ASIL publisher cannot create or join an unconfigured channel while a static config is loaded. The ASIL client sets InitRequest.asil to say so. The flag is advisory: the server trusts the client to set it. InitRequest gets a new field, asil = 5. Older clients don't set it and keep the standard behavior, and older servers ignore it.
PhaserServerConnection can be constructed with a larger wire buffer size for a channel whose handshake needs more room. The decoded message buffer was a fixed 32 KB, twice the wire buffer. That spare space is never needed: the wire buffer carries the message's ByteSizeLong(), and a message that fills the arena cannot be larger than that. So the message buffer now has the same size as the wire buffer. - The default object keeps both buffers inside the object, 16 KB each. - A larger size allocates both once in the constructor, with the message buffer rounded up to 8 bytes so the wire buffer stays aligned. - Responses that do not fit the wire buffer are still drained, so the stream stays in step. Allocation failure reports kCapacityExceeded. - Adds a test that a 64 KB connection sends and receives a request the default connection cannot hold, with no heap allocations.
- An ASIL publisher creates a channel's buffer only when it is the first publisher and the server has no static config. With a config loaded it publishes only on the configured channels. - The server handshake is a 4-byte length followed by the Phaser message buffer itself (Data() / ByteSizeLong()), the same bytes the standard C++ and Rust clients send. The ASIL connection keeps that message in fixed buffers, 16 KB by default. - The example gives each channel its layout, so it applies to a server with no static config, and points at server_defined for a configured channel.
Add a General Motors copyright line under the existing header in the ASIL client sources and the new common/ files that the ASIL client shares with the standard client. Rename "General Motors LLC" and "General Motors Inc." to "General Motors" everywhere in the repository. Only comments, copyright headers and documentation change.
--channel_config and --tunnel_config now read a protobuf wire-format file as well as text. The extension decides: a name ending in .pb or .binpb (ignoring case) is wire format, and any other name is text, so existing files keep working. The wire format is strict like the text format. A message with a field this build doesn't know, at any depth, is rejected rather than having the field dropped. Both files are read in binary mode. - server/config_format.h: ConfigFileIsText and HasUnknownFields. - Tests: the extension selects the format, a wire file is rejected when read as text and the reverse, and unknown fields are rejected. - Docs: README, docs/tunnel.md, CHANGELOG and the flag help.
- README and docs/tunnel.md: show protoc --encode for producing a .pb file from a text config, and say that unknown fields are rejected in either format. - docs/server-architecture.md: list static_config and config_format.h. - CHANGELOG: rewrap the tunnel_config entry.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This branch adds four things:
--channel_config=FILE). The server creates the configured channels and multiplexers before it accepts clients, and their layout is fixed. Publishers on them must setserver_defined. A standard publisher can still create a channel that isn't configured, by specifying its layout, and any subscriber can subscribe to it, including an ASIL subscriber. An ASIL publisher cannot create or join a channel that isn't configured while a static config is loaded.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 the Phaser zero-copy protocol below, in fixed buffers.Open(), so publishing and reading make nommaporshm_opencalls. Split buffers are supported.Data()/ByteSizeLong()), with no protobuf transcoding.ServerWireincommon/server_wire.hdoes this for both C++ clients. The standard client builds the messages in growable buffers. The ASIL client builds them in fixed buffers: 16 KB for the message and 16 KB for the wire payload, both insidePhaserServerConnection, so a handshake allocates nothing. A larger size passed to the constructor is allocated once and used for both buffers. Bridge, tunnel, discovery, the channel directory and statistics stay on protobuf.Server changes
use_split_buffersin 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.GetClientBuffersRequestcan 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.ASIL client
kReadNext/kReadNewest, drop counts and activation messages/dev/shmand memfdOpen()returnskNoBuffers.kBufferNotMapped.SplitSlotarray 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.mdfor details.Dependencies
The pinned phaser and cpp_toolbelt commits are in dallison/phaser#13 and dallison/cpp_toolbelt#29.
MODULE.bazelusesgit_overrideuntil those versions reach the Bazel Central Registry.Testing
bazelisk test --config=macos_arm64 //...: 28/28 pass.asil_layout_testandasil_client_testpass.bazelisk test --config=macos_arm64 //asil_client:asil_client_test: passes with the 16 KB message and wire buffers.//asil_client/...and//server/...pass with/dev/shmand with--config=linux_memfd.asil_client_test(40 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_testchecks the ASIL shared memory layout againstcommon/channel.h.