Allow memfd_create shared-memory backend on Linux, independent of Android#85
Merged
Conversation
Rename the anonymous fd-backed shared-memory mode from
SUBSPACE_SHMEM_MODE_ANDROID to SUBSPACE_SHMEM_MODE_MEMFD so it names the
mechanism rather than the platform, and make it selectable on Linux:
- Android always uses memfd (it has no /dev/shm).
- Linux defaults to the named /dev/shm backend (SUBSPACE_SHMEM_MODE_LINUX)
and opts into memfd via SUBSPACE_LINUX_USE_MEMFD.
- macOS/QNX (POSIX) are unaffected.
Build knobs: Bazel `--config=linux_memfd` (global copt, since the mode
affects shared struct layout) and the CMake option SUBSPACE_LINUX_USE_MEMFD.
The CreateAndroidBuffer helper is renamed CreateMemfdBuffer.
Test gates that were keyed on __ANDROID__ for shmem-mechanism reasons
(memfd cannot be reopened by name; shm_open/chmod only run in named modes)
now key on SUBSPACE_SHMEM_MODE; platform-specific gates are left as-is.
CI: add a `memfd` job that runs the full C++ suite (plus split-buffer and
bridge variants) under --config=linux_memfd on x86_64 and arm64, excluding
the Rust client which only supports the named backend; add a Release-memfd
CMake variant to guard the CMake option.
rust_rpc has no Bazel targets on this branch, so `-//rust_rpc/...` matched nothing and Bazel failed the memfd build with "no targets found beneath 'rust_rpc'". Only the Rust client (//rust_client/...) needs excluding; it is the one with Bazel test targets that map shared memory by name.
The full C++ memfd suite (//... minus the Rust client) passes under --config=linux_memfd, but client_test --use_split_buffers fails: split buffers backed by anonymous memfds currently leave subscribers reading empty buffers in the publish/subscribe path. This combination is also untested on Android (the other memfd platform, which runs client_test and bridge_test without --use_split_buffers), so it is a pre-existing latent bug rather than a regression here. Drop the --use_split_buffers steps from the memfd job so it covers the validated memfd configuration. Split buffers remain covered by the named /dev/shm `test` job. Re-add the memfd split-buffer step once the split-buffer-over-memfd data path is fixed.
Split buffers over anonymous memfds had no cross-publisher dedup. Named backends use shm_open(O_CREAT|O_EXCL) so the first publisher creates the split buffers and the rest open the same named objects. memfd has no name, so every publisher created fresh anonymous memfds and re-registered them with the server, the later registration clobbering the earlier one. A subscriber then mapped the wrong (zero-filled) memfd and read an empty payload even though the message length (carried in the shared prefix/CCB) was correct. Fix it the same way the non-split memfd path already works: the server keeps the first registration (first-wins), and CreateSplitBufferSet now registers its memfd and then adopts the server's authoritative descriptor for each prefix/slot. All publishers and subscribers on a channel converge on a single shared set of split buffers per (channel, buffer_index), and this is race-safe because the server serializes registrations. Also re-enable the --use_split_buffers variant in the memfd CI job (client and bridge tests) so this path stays covered, and force the non-split layout in the FailedRegistrationRollsBackNumBuffers unit test, which targets the non-split rollback path and only wires a registration callback.
Add a shadow recovery test that publishes a message through a split buffer, crashes and restarts the server, and reads the same payload back from a fresh subscriber on the recovered channel. With split buffers the payload lives in a client-allocated buffer whose fd is handed to the server and replicated to the shadow. In the memfd backend that buffer is an anonymous memfd that only survives the restart because the shadow holds its fd and hands it back during recovery, so this exercises the fd-based recovery path end to end rather than just checking replicated metadata. The test is guarded to SUBSPACE_SHMEM_MODE_MEMFD: in the named backends (POSIX /tmp shadow files, Linux /dev/shm) the split-buffer object is mapped by name and that name is unlinked at creation, so a restarted server cannot re-open it -- payload recovery via the shadow is inherently an fd/memfd capability. Verified passing under --config=linux_memfd (and the existing suite still passes in the default backend, where the new test compiles out).
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
Decouples the anonymous
memfd_createshared-memory backend from the Android platform so it can be selected on Linux, while leaving the default Linux (/dev/shm) and macOS/QNX (POSIX) backends unchanged.SUBSPACE_SHMEM_MODE_ANDROID→SUBSPACE_SHMEM_MODE_MEMFD(names the mechanism, not the platform) and renames theCreateAndroidBufferhelper toCreateMemfdBuffer.common/channel.h:MEMFD(no/dev/shm).LINUX(named/dev/shm) by default;MEMFDwhenSUBSPACE_LINUX_USE_MEMFDis defined.POSIX(unchanged).--config=linux_memfd(a global--copt, since the mode affects shared struct layout and must be ABI-consistent across every TU).-DSUBSPACE_LINUX_USE_MEMFD=ON(Linux-only guard).__ANDROID__for shmem-mechanism reasons (anonymous memfds can't be reopened by name;shm_open/chmodonly run in named modes) now key onSUBSPACE_SHMEM_MODE. Genuinely platform-specific gates (/data/local/tmppaths,O_DIRECTpipes,shm_open_fnstruct membership) are left as__ANDROID__.CI
memfdjob: runs the full C++ test suite + split-buffer and bridge variants under--config=linux_memfdonubuntu-latest(x86_64) andubuntu-24.04-arm. The Rust client is excluded (-- //... -//rust_client/... -//rust_rpc/...) because it only implements the named/dev/shmbackend — same reason Android CI doesn't run Rust.Release-memfdvariant guarding-DSUBSPACE_LINUX_USE_MEMFD=ON.Test plan
Verified on an arm64 Linux container under
--config=linux_memfd(and the default LINUX mode for no regression):client_test,c_client:client_test,server_test,split_buffer_test,syscall_failure_testbridge_testwith and without split buffersstress_test/dev/shm) suite still passes