rxdemo Android USB-fd path + autonomous Quest RX-ring harness (issue #330)#333
Merged
Conversation
…330) The RX demo could never run on Android: libusb's device enumeration is SELinux-denied under the untrusted_app domain, and only the TX demo had the wrap-a-pre-opened-fd path. Port it into examples/rx/main.cpp — argv[1] = a numeric USB fd, LIBUSB_OPTION_NO_DEVICE_DISCOVERY + WEAK_AUTHORITY before libusb_init, libusb_wrap_sys_device, and skip the reset/reopen (a wrapped app-owned fd can't be re-enumerated). CMakeLists links Android `log`. Autonomous Quest 3 test vehicle (tests/quest_apk/, hand-built with aapt/d8/apksigner — no gradle): a USB_DEVICE_ATTACHED intent-filter + device_filter auto-grants USB permission with no dialog (the PixelPilot mechanism), and a JNI fork()+execve() helper hands the UsbDeviceConnection fd to rxdemo — Android's ProcessBuilder closes fds >= 3 before exec, so the fd must be inherited across an in-process fork, not a spawned child. Driven headlessly from the desktop via `am start` extras; no in-headset interaction. Instrumentation the harness drives (all off by default; the async path is byte-unchanged when unset): rx.ring telemetry (posted-URB depth, cb_max_us consumer stall, resubmit_fail, completions), rx.seq per-frame delivery, DEVOURER_RX_MODE {async|sync|reorder-pool|spsc-fat} with a spare-buffer pool, and a demo-side sink-spin / TX keyframe-burst stimulus. Harnesses: tests/build_android.sh (NDK cross-build), tests/quest_rxq_run.sh + quest_rxq_analyze.py (per-mode delivery from the 802.11 seq_num + ring telemetry), tests/rxq_starve.sh + rxq_analyze.py + rxq_preempt.c (the desktop constrained-host repro). Findings in docs/experiments/issue-330-rx-ring-starvation.md: the residual is not attributable to ring-servicing strategy on either the desktop or the Quest — at load the ring absorbs the (real, large: up to ~15 ms cb_max_us on the Quest) consumer stalls, and loss is RF- or consumer-throughput-limited. The shippable product is the rx.ring telemetry as a field diagnostic. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
What
Makes
rxdemorunnable on Android and adds a fully autonomous Quest 3 harness for the issue #330 async-RX-ring residual-loss investigation.Android USB-fd path (the core fix)
The RX demo could never run on Android: libusb's device enumeration is SELinux-denied under the
untrusted_appdomain, and only the TX demo had the wrap-a-pre-opened-fd path. Ported intoexamples/rx/main.cpp:argv[1]= a numeric USB fd;LIBUSB_OPTION_NO_DEVICE_DISCOVERY+WEAK_AUTHORITYbeforelibusb_init;libusb_wrap_sys_device; skip the reset/reopen (a wrapped app-owned fd can't be re-enumerated).CMakeLists.txtlinks Androidlog(was previously unlinked).Autonomous Quest harness (
tests/quest_apk/)A minimal auto-grant APK, hand-built with
aapt/d8/apksigner(no gradle), modelled on how PixelPilot avoids the USB permission dialog:USB_DEVICE_ATTACHEDintent-filter +device_filterauto-grants USB permission on attach — no dialog;fork()+execve()helper hands theUsbDeviceConnectionfd torxdemo(Android'sProcessBuildercloses fds ≥3 before exec, so the fd must be inherited across an in-process fork).Driven headlessly from the desktop via
am startextras — no in-headset interaction.tests/quest_rxq_run.sh+quest_rxq_analyze.pyrun the per-mode matrix;tests/build_android.shcross-builds the arm64 bundle; runbook intests/quest_rxq.md.Instrumentation + modes (all off by default;
asyncbyte-unchanged when unset)rx.ringtelemetry (posted-URB depth,cb_max_usconsumer stall,resubmit_fail,completions),rx.seqper-frame delivery,DEVOURER_RX_MODE {async|sync|reorder-pool|spsc-fat}with a spare-buffer pool, and a demo-side sink-spin / TX keyframe-burst stimulus. Plus the desktop constrained-host repro (tests/rxq_starve.sh,rxq_analyze.py,rxq_preempt.c).Findings
Documented in
docs/experiments/issue-330-rx-ring-starvation.md. The ~1–2% residual is not attributable to ring-servicing strategy on either the desktop or a genuinely constrained XR2 host: at load the ring absorbs the (real, large — up to ~15 mscb_max_uson the Quest) consumer stalls, and delivery is RF- or consumer-throughput-limited. Across three load regimesasync/reorder-pool/spsc-fattie within run-to-run noise (±1.5 pts, no consistent ordering). The shippable product is therx.ringtelemetry as a field diagnostic a reporter runs on their own phone.Test
ctest37/37 pass.asyncpath unchanged when the new knobs are unset.🤖 Generated with Claude Code