Add detailed tracing for backpressure investigation

Investigation revealed the root cause of premature streaming termination:

1. MPSC Channel Size Issue:
   - DEFAULT_CHANNEL_SIZE = 16 chunks × 50ms = 800ms capacity
   - TimerNode max_lead_time = 3.0 seconds
   - The channel fills up in 0.8s while TimerNode wants 3s buffer
   - This creates stop-and-go pattern instead of smooth backpressure

2. Channel Closure Issue:
   - When RadioParadiseStreamSource::process() returns, the Node
     automatically closes output channels
   - TimerNode receives EOF and terminates immediately
   - Remaining chunks in MPSC buffer (up to 16) are never sent to sink

Added comprehensive tracing:
- RadioParadiseStreamSource: Track backpressure blocking, chunk counts,
  decode timing
- TimerNode: Log all pacing decisions, sleep durations, lead time
- Both use trace! for high-frequency events, debug! for blocking

Next steps:
- Option A: Increase channel size to match max_lead_time
  (60 chunks for 3s @ 50ms)
- Option B: Wait for channels to drain before closing
  (use tx.closed().await)
- Option C: Both A and B for optimal behavior

The previous "wait for playback duration" fix is a valid workaround
but doesn't address the architectural issue.
This commit is contained in:
Claude
2025-11-12 10:27:35 +00:00
parent b3f22d1b61
commit 215b097f4b
2 changed files with 43 additions and 5 deletions

View File

@@ -153,18 +153,26 @@ impl NodeLogic for TimerNodeLogic {
let elapsed = start.elapsed().as_secs_f64();
let lead_time = chunk_timestamp - elapsed;
tracing::trace!(
"TimerNodeLogic: chunk received (ts={:.3}s, elapsed={:.3}s, lead_time={:.3}s, max_lead={:.1}s)",
chunk_timestamp, elapsed, lead_time, self.max_lead_time_sec
);
if lead_time > self.max_lead_time_sec {
// On est trop en avance, attendre
let sleep_duration = lead_time - self.max_lead_time_sec;
tracing::trace!(
"TimerNodeLogic: SLEEPING {:.3}s (lead_time={:.3}s > max={:.1}s)",
tracing::debug!(
"TimerNodeLogic: SLEEPING {:.3}s (lead_time={:.3}s > max={:.1}s, chunk_ts={:.3}s)",
sleep_duration,
lead_time,
self.max_lead_time_sec
self.max_lead_time_sec,
chunk_timestamp
);
tokio::select! {
_ = tokio::time::sleep(Duration::from_secs_f64(sleep_duration)) => {}
_ = tokio::time::sleep(Duration::from_secs_f64(sleep_duration)) => {
tracing::trace!("TimerNodeLogic: woke up from sleep");
}
_ = stop_token.cancelled() => {
tracing::debug!("TimerNodeLogic cancelled during sleep");
break;
@@ -178,6 +186,11 @@ impl NodeLogic for TimerNodeLogic {
chunk_timestamp,
elapsed
);
} else {
tracing::trace!(
"TimerNodeLogic: chunk on time (lead_time={:.3}s within tolerance)",
lead_time
);
}
} else {
// Pas encore de TopZeroSync reçu, passthrough sans pacing