Greenfield Steps 1-6 baseline for the autopilot rewrite from legacy Qt/C++ to a Rust workspace. - Remove legacy Qt/C++ tree (ai_controller, drone_controller, misc/camera, python_scaffold, root Dockerfile, autopilot.pro, legacy main.py / requirements.txt). - Add _docs/00_problem (problem, restrictions, acceptance criteria, security approach, input data + fixtures). - Add _docs/01_solution/solution_draft01. - Add _docs/02_document (architecture, system-flows, data_model, glossary, decision-rationale, deployment, 13 component descriptions, tests/ specs, FINAL_report, module-layout). - Add _docs/02_tasks/todo with 47 task specs (AZ-640..AZ-686, one bootstrap + 46 component tasks) and _dependencies_table.md. - Add .cursor/rules/artifact-srp.mdc (single-responsibility rule for canonical _docs artifacts). - Track autodev state in _docs/_autodev_state.md (Step 6 completed, ready for Step 7 Implement). Jira: bootstrap AZ-626; component epics AZ-627..AZ-639; tasks AZ-640..AZ-686. Total complexity 173 points across 12 epics. Co-authored-by: Cursor <cursoragent@cursor.com>
3.2 KiB
Multi-Consumer Frame Publisher + Back-Pressure Drops
Task: AZ-659_frame_ingest_publisher
Name: Tokio broadcast publisher + per-consumer drop counters + zero-copy Arc<Bytes>
Description: Publish Frames through a single multi-consumer channel using Arc<Bytes> for pixel data so consumers do not copy. Drop frames when downstream consumers fall behind beyond a configured queue depth; record per-consumer drop counters with reason tags.
Complexity: 3 points
Dependencies: AZ-640_initial_structure, AZ-657_frame_ingest_rtsp_session, AZ-658_frame_ingest_decoder
Component: frame_ingest
Tracker: AZ-659
Epic: AZ-627
Problem
Three downstream consumers (detection_client, movement_detector, telemetry_stream) all need the same frames at the same rate. A single-consumer queue would serialise the slowest; a per-consumer fan-out with cloned pixel buffers would multiply memory. The right structure is a Tokio broadcast channel (or equivalent) carrying Arc<Bytes> so pixels are shared by reference. Slow consumers drop their oldest frame, with the drop counted (and reason-tagged) — never silently coalesced.
Outcome
FramePublisher::subscribe() -> FrameReceiverreturns a per-consumer receiver.FramecarriesArc<Bytes>forpixelsso consumers do not copy.- When a consumer falls behind beyond
channel_depth(configurable, default 4), the oldest frame is dropped for THAT consumer; per-consumer counters increment with reason tag ({detection_client_slow, movement_detector_slow, telemetry_slow}). - Health surface: per-consumer drop counters, total publish count.
Scope
Included
tokio::sync::broadcast(or equivalent) withArc<Bytes>payload.- Per-consumer drop counter (statically known three consumer ids; future-extensible).
- Channel-depth config.
Excluded
- RTSP session (task 18).
- Decoder (task 19).
Acceptance Criteria
AC-1: Three consumers receive every frame at nominal rate Given three subscribers consuming at 30 fps and source at 30 fps When the publisher runs for 10 s Then each consumer observes ~300 frames; per-consumer drop counters = 0.
AC-2: Slow consumer drops, fast consumers unaffected
Given a slow consumer that yields every 200 ms while source is 30 fps and channel_depth = 4
When the publisher runs for 5 s
Then the slow consumer's drop counter increments and fast consumers continue to receive every frame.
AC-3: Zero-copy under load
Given a publisher emitting at 30 fps for 60 s with three subscribers
When peak memory is sampled
Then memory does not scale linearly with consumer count (i.e. Arc<Bytes> is correctly shared).
Non-Functional Requirements
Performance
- Publish-to-consumer p99 ≤5 ms (helps keep total RTSP-rx-to-publish under the 30 ms p99 budget).
Reliability
- Drops are counted with reason; never silent.
- No unbounded memory growth on slow consumer.
Runtime Completeness
- Named capability: lossy multi-consumer frame fan-out with
Arc<Bytes>. - Production code that must exist: real broadcast channel; real per-consumer drop accounting.
- Unacceptable substitutes: cloning pixel buffers per consumer is unacceptable (multiplies memory); blocking the publisher on a slow consumer is unacceptable (gates the whole pipeline).