fix(provider): stop OVT pull re-DESCRIBE storm from a reachable-but-silent origin - #2199
fix(provider): stop OVT pull re-DESCRIBE storm from a reachable-but-silent origin#2199naanlizard wants to merge 1 commit into
Conversation
|
An aside, since this is a convenient way to ping y'all - @getroot @Keukhan I have been digging into color space (601/709, full/limited) and screenshots and how OME handles them. I'm in the final review phase of a PR to
I'll hopefully push that in a few hours or tomorrow for review, including a big page I've been using to compare screenshots generated by various versions of OME (master, 0.20.5, my fork, and the ffmpeg upgrade commit that is part of my work) |
|
@naanlizard A very large refactoring is going to land on the Transcoder side this week. It would be better to put up any PRs that touch the transcoder after that. |
|
Is there somewhere I can see that work in progress? |
|
Nice analysis, and the backoff works. Personally I'd prefer to fix this with a more minimal change, since you already noted last_recv only advances on real media. Instead of adding the backoff state, I'd just reset the no-input clock on a successful reconnect. The re-pull cadence then settles at NoInputFailoverTimeout on its own, with no new members and no lock, and existing viewers recover within the timeout instead of up to the 30s cap. For the implementation, my idea is to keep a collector-local "last reconnect" timestamp and measure no-input from max(last_recv, last_reconnect) rather than stamping the monitoring last_recv itself, so the stats stay honest. What do you think? |
|
|
I'd defer to your judgement, of course. That would be 3s (NoInputFailoverTimeout) between DESCRIBEs instead of the scaling-to-30s, right? The backoff seems better to me because 3s by default is still a bit tight, and if someone had a ton of edges, it could still cause a bunch of DESCRIBEs, but I guess that's an edge case. Your solution is certainly cleaner. An aside, PacketSilenceTimeoutMs defaults to 0 for RTMP, should that perhaps default to something like 5-10s? I'm not sure there are many situations where you have no media packets coming in for e.g. 5s and you should stay connected |
|
The root cause here is that a stream stays alive even when there is no data flow. As for changing the default value of PacketSilenceTimeoutMs, let's handle that separately in a Discussion. There was a reason I set it to 0, but I don't remember it well right now, so I'll think it over. |
6b51bae to
5773cda
Compare
|
Updated |
…ilent origin When an edge relays a stream and the source stops sending media while its TCP connection stays up, the origin keeps the stream alive, so WhiteElephantStreamCollector stops the pull stream on no-input and, on the next 100ms tick, re-pulls it - re-DESCRIBEing the origin. The reconnect succeeds (the stream still exists) but delivers no media, so last_recv stays old and the stream is stopped and re-pulled again every tick. Several edges relaying one such stream produce a continuous DESCRIBE storm at the origin's OVT publisher. Measure the no-input timeout from max(last_recv, last_reconnect) instead of last_recv alone. A successful reconnect restarts the no-input clock, so a silent stream is not re-stopped until NoInputFailoverTimeout has elapsed again; the re-pull cadence settles at NoInputFailoverTimeout rather than once per collector tick. Reconnect timestamps live in a collector-thread-local map (no PullStream state, no lock) stamped only on a successful reconnect, and the monitoring last_recv is left untouched so stats stay honest. Both collector resume sites (no-input retry and failback switch) reset the clock. Also surface the silent-origin lifecycle in the edge log: a reconnect (debug), the recurring no-input stop (warning, unchanged), and media resuming after a re-pull (info). Approach suggested by @getroot in review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5773cda to
8cf6316
Compare
|
Rebased onto current master (~ v0.21.0); the branch had gone stale against the src/projects flatten (#2248). The fix itself is unchanged — still the approach suggested above: no-input measured from max(last_recv, last_reconnect) via a collector-local map. Current master doesn't crash any more, which is good, but there's still a ton of noise from the flapping. |
The below is Claude-written (as is the change), from analysing crash dumps we've been seeing in prod, plus a back-and-forth with @getroot on the approach. The crash itself (the
OvtStream::_descriptionrace) is already fixed on master by #2165; this fixes the OVT connection storm that was driving it.Reworked to @getroot's suggested approach: instead of a re-pull backoff with new
PullStreamstate, reset the no-input clock on a successful reconnect.Problem
When an edge relays a stream from an origin and the source stops sending media while its TCP connection stays up (a stalled encoder whose host keeps answering at the socket level), the origin keeps the stream alive. The edge's
WhiteElephantStreamCollectorthen enters a tight re-pull loop:NoInputFailoverTimeoutisStop()-ed; on the next tick it isSTOPPED, so the collector re-pulls it — reconnecting and re-DESCRIBEing the origin, with no delay.last_recvstays old and the stream is stopped and re-pulled again on the very next tick.Three edges relaying one silent stream produce a continuous DESCRIBE storm (~25/s per stream) at the origin's OVT publisher.
(The collector re-pull loop — and thus this storm — only engages when
RetryCount > 0; with the defaultRetryCount = 0,ResumeInternalterminates a silent pull stream on its first re-pull instead of reconnecting. The reproduction and our deployment useRetryCount = 2.)Reproduction
Minimal Docker reproduction (origin + 3 edges + a publisher frozen with
docker pauseto model a silent-but-connected source): https://github.com/naanlizard/ome-ovt-flap-reproFix
Measure the no-input timeout from
max(last_recv, last_reconnect)instead oflast_recvalone:last_recvstays old; measuring frommax(last_recv, last_reconnect)means the stream is not stopped again until a fullNoInputFailoverTimeouthas elapsed since the reconnect. The re-pull (re-DESCRIBE) cadence therefore settles atNoInputFailoverTimeout(default 3 s) instead of once per 100 ms tick.PullStreammembers and no lock — the map is touched only by the collector thread and pruned each tick for streams that no longer exist. The monitoringlast_recvis never written, so reported stats stay honest. Both collector resume sites (the no-input retry and the failback URL switch) reset the clock.Logging
The silent-origin lifecycle is now visible in the edge log:
debug: each time the collector re-pulls and reconnects to the origin. Off at the default level; raise the edge todebugto watch the cadence.warning(unchanged): prints the true time since the last media packet, which keeps growing while the origin stays silent.info: once, when media starts flowing again after a re-pull.Behaviour (from the reproduction, 3 edges, one silent stream, 20 s window)
NoInputFailoverTimeoutper edge)no incoming packetsstopsNoInputFailoverTimeout/RetryCount/UnusedStreamDeletionTimeoutsemantics are unchanged.Behaviours to be aware of
NoInputFailoverTimeoutindefinitely; it does not reachTERMINATED. A successful reconnect resets_restart_count, so the relay is never abandoned while the origin is reachable — by design. This change caps the DESCRIBE rate at the timeout cadence; it does not end the relay. Tearing such a stream down is whatPacketSilenceTimeoutMson the origin is for — the origin-side fix is the root cause; this patches the storm in the window before the origin removes the silent stream.max(last_recv, last_reconnect), but the log prints the real time since the last media packet (so it can exceedNoInputFailoverTimeoutafter a silent reconnect). This keeps the logged number honest.last_recvadvances only on media packets, not on the OVT DESCRIBE/PLAY exchange, so a silent reconnect never resets it.