Skip to content

Allow memfd_create shared-memory backend on Linux, independent of Android#85

Merged
dallison merged 5 commits into
mainfrom
memfd_linux
Jun 10, 2026
Merged

Allow memfd_create shared-memory backend on Linux, independent of Android#85
dallison merged 5 commits into
mainfrom
memfd_linux

Conversation

@dallison

Copy link
Copy Markdown
Owner

Summary

Decouples the anonymous memfd_create shared-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.

  • Renames SUBSPACE_SHMEM_MODE_ANDROIDSUBSPACE_SHMEM_MODE_MEMFD (names the mechanism, not the platform) and renames the CreateAndroidBuffer helper to CreateMemfdBuffer.
  • Backend selection in common/channel.h:
    • Android → always MEMFD (no /dev/shm).
    • LinuxLINUX (named /dev/shm) by default; MEMFD when SUBSPACE_LINUX_USE_MEMFD is defined.
    • macOS / QNX / otherPOSIX (unchanged).
  • Build knobs:
    • Bazel: --config=linux_memfd (a global --copt, since the mode affects shared struct layout and must be ABI-consistent across every TU).
    • CMake: -DSUBSPACE_LINUX_USE_MEMFD=ON (Linux-only guard).
  • Test gates previously keyed on __ANDROID__ for shmem-mechanism reasons (anonymous memfds can't be reopened by name; shm_open/chmod only run in named modes) now key on SUBSPACE_SHMEM_MODE. Genuinely platform-specific gates (/data/local/tmp paths, O_DIRECT pipes, shm_open_fn struct membership) are left as __ANDROID__.

Note: in memfd mode, buffer creation/resize is brokered through the server (anonymous FDs passed via SCM_RIGHTS), whereas the named /dev/shm backend maps buffers directly by name without server round-trips. This trade-off is documented in docs/android.md.

CI

  • New memfd job: runs the full C++ test suite + split-buffer and bridge variants under --config=linux_memfd on ubuntu-latest (x86_64) and ubuntu-24.04-arm. The Rust client is excluded (-- //... -//rust_client/... -//rust_rpc/...) because it only implements the named /dev/shm backend — same reason Android CI doesn't run Rust.
  • New CMake Release-memfd variant 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_test
  • bridge_test with and without split buffers
  • stress_test
  • macOS POSIX-mode build unaffected
  • Default Linux (/dev/shm) suite still passes

dallison added 5 commits June 9, 2026 18:44
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).
@dallison dallison merged commit 734d108 into main Jun 10, 2026
59 of 60 checks passed
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