Repository navigation
Conversation
|
Noted as a draft, so I am holding off on a full review until it is marked ready for |
59a689c to
5ad1449
Compare
5ad1449 to
148cbe9
Compare
Both files were past the 1500-line threshold. This is a move: no item is
renamed, no public path changes, and each test moves with the code it
covers.
blocks/builtin/whip/
mod.rs block builders and re-exports
slot_chain.rs per-slot output chains (build_whipserversrc) and their
caps, decodebin and liveness-stamp helpers
watchdog.rs per-session inactivity watchdog
session.rs per-session whipserversrc pipeline and the
appsink-to-slot bridge
output.rs WHIP Output (whipclientsink, whipsink)
definition.rs block definitions
whip_session_manager/
mod.rs WhipSessionManager, session registry and cleanup
endpoint.rs WhipEndpointConfig and slot allocation
activity.rs ActivityStamp, SessionActivity, StallSide, SlotOutput
and WhipSlotLiveness
takeover.rs allocate_slot_or_take_over and its constants
test_support.rs fixtures shared by the manager's tests
Generated, not hand-edited: a manifest places every item of the two
files, the compiler decides which items become pub(super), and unused
imports are pruned from its warnings. Three comments that named whip.rs
by path now name the file the item moved to.
Regenerated on the integration branch with wagenet/whip-split-tool's
manifest plus placements for the items Eyevinn#792 and Eyevinn#805 add.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
148cbe9 to
b14db91
Compare
d859136 to
6b139d6
Compare
Eyevinn#756, Eyevinn#792 and Eyevinn#805 drop AtomicU64 from whip.rs's imports once nothing else there uses it. Naming it by path keeps this struct compiling in either merge order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Written by Claude (AI) on behalf of @srperens. Not in #957. The per-medium report goes through the block-health hook from #786/#796, which is not on main, and we want to settle the block-health and failure-policy design first. This PR stays open for that discussion. #957 does keep the per-slot output stamp from #756 that this builds on. |
6b139d6 to
71d7dc1
Compare
|
Claude (AI), on behalf of @wagenet. Rebuilt on main now that #756 has merged: main, then the #796 merge, then the per-medium commit. Conflicts were with #957 in Ran: clippy |
A pipeline branch can stop dead while the flow still reports Playing. When a downstream push fails with a non-fatal flow return such as not-negotiated, GstAggregator marks its sink pads flushing and pauses its source pad task, and video encoders pause their output loop. Neither posts to the bus, and pausing a pad task is not a state change, so every element stays PLAYING and nothing surfaces through gst_state or the API. GstBaseSrc is the exception - it posts a flow error before pausing - so until now only source-side failures were visible. Scan the running pipeline every two seconds for pads whose task is paused on an element that is itself PLAYING, attribute each to its owning block via the block_id:internal_id element namespace, and expose the result as Flow::block_health. Log once per transition and emit BlockHealthChanged so the failure is not limited to GST_DEBUG output. The flow list marks an affected flow as Failed rather than Running. Elements held below PLAYING are skipped: a WebRTC session bin paused mid-setup has paused pad tasks legitimately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gst_base_src_loop pauses its pad task on the way out for every reason, end of stream included, and leaves the element in PLAYING. Any flow with a finite source would therefore report that block as failed forever once it played out. Skip pads carrying the EOS flag: a pad that has seen EOS is finished by definition, and the stall this detects pushes no EOS. Require a block to look stalled on two consecutive scans before reporting it. Pad tasks pause briefly for legitimate reasons - a flushing seek, a dynamic bin being linked in - and a single sample turned those into a logged error and a UI flip. Costs one poll interval of latency. Drop blocks that disappear from the scan, rather than leaving them in the failed set where no later iteration could clear them. Handle BlockHealthChanged in the frontend. block_health rides on the Flow payload, and nothing refetched the flow on this event, so the indicator only appeared if the user happened to refresh. The reported detail said "pad task stopped" while the condition detected is paused; Stopped is explicitly not reportable here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The scan walks the flow pipeline and the bins beneath it. Blocks that build an isolated gst::Pipeline - WHIP ingest sessions, a Media Player's decode chain - are siblings of that pipeline rather than children, so iterate_recurse never enters them. Without that written down an Ok reads as "this flow is passing data", which it does not mean. The guard that skips elements below PLAYING cited a WHIP session bin as its example. Those live in a separate pipeline and are never scanned; a webrtcbin added for a new WHEP consumer is one that actually occurs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A queue whose source pad is left unconnected takes not-linked from its loop and parks the task there for good, with the element still PLAYING. Blocks build optional outputs this way - the vision mixer's multiview_out is one - so any flow wiring only the program output reported its mixer as permanently failed. Found on a real WHIP -> vision mixer -> WHEP rig, where a healthy flow reported "pad task paused on mixer:queue_mv_out:src" seconds after start. An unlinked pad has no branch to report on, so require a peer. The regression test reproduces it without a rig: a tee with one queued branch left unconnected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A block that stops passing data is logged at error by the health scan; the structured event log now matches it instead of logging at info. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A link the flow declares and construction cannot make is deferred to pending_links, and the only thing that retries one is the pad-added handler on its source element. A pad that is already there is never added again, so a deferred link whose named source pad exists and has no peer will not form for the life of the pipeline. Nothing reported that. The stalled-pad-task scan looks for a task that was paused, and where no link exists there is no task, while the allow-not-linked tee every block output bus ends in absorbs the not-linked return that would otherwise surface upstream. A vision mixer whose output caps cannot cross gldownload leaves both video branches unlinked exactly this way: the flow reaches Playing, carries audio, and reports every block healthy with no video at all. Scan pending_links alongside the pad tasks and fail the block owning the source element. A source pad that has not appeared yet is untouched, which is the case the deferral exists for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A vision mixer that loses both its distribution and multiview branches reports two, and the pad-task scan above already names the first one it finds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Record each link the linker gives up on, as the flow declares it, and report those. Construction records a refused link whose source pad exists or can be requested, since nothing retries it. The pad-added handler records every attempt it makes on a pad that appears later, and one pad that links is enough to form the link. A pad's own state cannot answer this. A refused branch of a fan-out starts from an auto-tee pad: it carries the tee's name, and a refused request pad is released again. A late pad the handler cannot link is given an auto-tee as its peer, so it has a peer like any linked pad. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A slot's audio and video are separate chains from the publisher's RTP session to the flow's consumers, but one output `ActivityStamp` covered both. Either medium stamping it kept the whole seat looking alive, so a commentator whose microphone stopped reaching the mix held its slot for the rest of the session with every liveness signal green. Split the slot's output record per medium (`SlotOutput`) and report the difference. `WhipSlotLiveness` names an occupied slot medium that has stopped producing while its counterpart has not, and the flow's block health scan polls it through a new `BlockLiveness` hook: an unfed chain pauses no pad task, so the scan cannot find it on its own. The reap paths are unchanged. `SessionActivity::idle` reads the output on its newest medium, so one dead medium does not cost a publisher the slot its working medium is still filling, and the decode grace and stall-side label behave as before. Ingress stays a single stamp: the session-level readings already want the newest arrival of either medium. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
71d7dc1 to
599270c
Compare
|
#983 has landed the same per-medium 🤖 Generated with Claude Code |
|
Comment by Claude on behalf of @wagenet. Replaced by #1025, which rebuilds this on the per-medium stamps #983 added. It only reports; reaping and takeover stay with #983, so this PR's |
Written by Claude (Claude Code) on behalf of @wagenet.
Stacked on #796, which carries #786. It is merged in because the report reaches operators through their block health scan. Reviewable content is the last commit only:
Problem
A WHIP slot's audio and video are separate chains from the publisher's RTP session to the flow's consumers, but #756 gives the slot one output
ActivityStampfor both. Either medium stamping it keeps the whole seat looking alive.On the five-seat mix-minus rig a seat delivered no audio to the audio mixer, the vision mixer or the return router for its whole session while its video kept flowing, and every liveness signal reported it healthy: a commentator whose microphone has silently stopped reaching the mix.
Change
SlotOutputreplaces the per-slot output stamp with one stamp per medium, written by the same tee probes.WhipSlotLivenessnames any occupied slot medium that has stopped producing while its counterpart has not.The verdict reaches block health through a new
BlockLivenesshook onBlockBuildContext. Neither existing scan can see this failure: the pad-task scan needs a task that paused, the unformed-link scan needs a link that never formed, and a chain that is linked and never pushed to has neither. The scan applies a block's own verdict last, so a more specific failure still wins. The operator gets aBlockHealthChangedevent, an entry inflow.block_health, and:Against #756's design. Only the output side is split. #756's
ingressstays one stamp:idle()wants the newest arrival of either medium and the first arrival of any, which a single stamp already gives.idle()readsSlotOutputon its newest medium, so the decode grace, the takeover tail handling and theStallSidelabel behave exactly as in #756.has_delivered_audiostays oningress_audio.Reap policy: unchanged, deliberately. Reaping on one dead medium drops the video the publisher is still delivering and throws a client that renegotiated its microphone away into a reconnect loop. A per-medium idle in takeover would also reopen the eviction of a healthy video-only publisher at 3.1 s:
TAKEOVER_IDLE_THRESHOLDis 2 s because audio has a ~20 ms cadence, and it is only sound as a whole-session test becausehas_delivered_audiogates it. What to do about a dead microphone is a production decision.Thresholds.
A connected publisher sends audio every ~20 ms even when muted (WebRTC keeps the RTP stream running with silence), so two seconds of nothing is a broken chain. A screen share of an idle window legitimately goes many seconds between frames.
A medium is reported only when its counterpart outlived it by that medium's own budget. Without that, a seat losing its transport reads as half dead for the difference between the budgets; the rig showed a killed publisher reported as an audio failure for 5.6 s before the watchdog reaped it.
Evidence
a_slot_reports_the_medium_that_died_while_the_other_kept_flowingbuilds a realaudio_videoblock, pushes buffers into the real per-slot appsrcs and reads the real reporter. Wiring both probes to the audio stamp (output.touch(Medium::Audio)) makes it fail. Run on the head before the rebase onto main.a_seat_that_stopped_altogether_is_not_reported_as_half_deadfails when the counterpart term is dropped (silent >= thresholds.stopped(medium)). Run on the head before the rebase onto main.an_empty_or_wholly_idle_slot_is_not_reported.a_blocks_own_failure_report_reaches_its_health.a_session_whose_audio_died_keeps_its_slot. Passes before the change too, so it guards the decision, not the fix.a_predecessors_trailing_frames_do_not_cut_the_decode_grace_shortanda_reaped_session_names_which_side_of_the_chain_stopped.ok.Tests
Ran on this head, macOS:
cargo clippy --workspace --all-targets -- -D warnings,cargo fmt --all -- --check,cargo test --test block_health_test --test whip_idle_slot_test --test whip_session_branch_test --test whip_slot_caps_reuse_test --test whip_slot_decoder_reuse_test --test whip_video_damage_test --test whip_whep_ice_transport_policy_test --test pipeline_lifecycle_test(27 passed, 0 failed), and the backend unit tests filtered towhip,healthandstats::(65 passed, 0 failed). The full workspace suite last ran on the head before the rebase (998 passed).Not run: the headless rig and the full mix-minus rig with tone capture, the
nvidiafeature, the WASM clippy, the full workspace suite on this head. CI has not run on this head.For the reviewer
audio_videoendpoint is reported after ~14 s and stays reported. It is the same code path as the rig failure, where audio never arrived either, so it cannot be suppressed without losing that case. The endpoint mode is the operator's declaration of what the seat carries, so I think reporting is right; a second opinion is welcome.whip.rsandwhip_session_manager.rs) touches the same files. Whichever lands second has to be rebased.Out of scope
🤖 Generated with Claude Code