Skip to content

TELCORE-248 / TEL-6872: Cherry-pick elastic jitter buffer fixes from SignalWire PR #2337 - #539

Merged
tajamulTelnyx merged 7 commits into
telnyx/telephony/deploy-developmentfrom
jira-tel-6872
Jul 16, 2026
Merged

TELCORE-248 / TEL-6872: Cherry-pick elastic jitter buffer fixes from SignalWire PR #2337#539
tajamulTelnyx merged 7 commits into
telnyx/telephony/deploy-developmentfrom
jira-tel-6872

Conversation

@dev-ryanc

@dev-ryanc dev-ryanc commented Mar 13, 2026

Copy link
Copy Markdown
Collaborator

Cherry-pick select bug fixes from signalwire#2337 (Julien Chavanton).

Fixes

  1. Sequence rollover in check_jb_size() — add MAX_DROPOUT guard to prevent
    hiding valid packets near seq 65535→0 wraparound
  2. decrement_seq() ordering — set last_target_seq after decrement so it
    points to the correct value
  3. Buffering skip gate — do not block elastic mode with the
    complete_frames < frame_len buffering check
  4. Consecutive miss safety valve — after MAX_CONSECUTIVE_MISS (100)
    consecutive missed frames, disable elastic mode and reset JB
  5. Unbounded expand protection — track net expansion via expand_frame_len
    and reset JB when it exceeds max_frame_len
  6. JB stats export — switch_jb_export_stats() snapshots JB fields under
    mutex, unlocks, then writes channel variables and logs (avoids lock-order
    inversion with channel-side locks from hangup/destroy/KILL_JB contexts)
  7. read_rtp_packet() retry bound — cache rtp_jitter_buffer_accelerate
    channel var at function entry, keep unconditional hard bound at tries > 100

Intentionally excluded

  • Debug log level changes (INFO→ALERT) — would flood production logs
  • Forced acceleration when above max size — needs separate A/B testing (potential
    uninitialized variable issue with packet_vad, drops frames during active speech)

Testing

  • A/B test on canary tankers
  • Monitor rtp_jb_* channel variables for regression
  • Focus on long-duration calls (seq rollover) and high-jitter scenarios

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@damirn damirn left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't complain on fixes from upstream ;)

@tajamulTelnyx tajamulTelnyx left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@jchavanton

Copy link
Copy Markdown

@dev-ryanc make sure you grab the second commit 60f8f34

This is preventing a race condition that can take place is someone extract the statistics in a hangup hook

jchavanton#4

@minhtuan1407-telnyx minhtuan1407-telnyx left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔬 Analyst review — TEL-6872 elastic JB cherry-pick

Verdict: APPROVE (with non-blocking notes)

Changes verified

  1. MAX_DROPOUT=3000 guard in check_jb_size() — hides "old" nodes only when udelta ∈ (1, 3000). Prevents seq-wraparound hiding valid packets. ✅
  2. decrement_seq() ordering fix — last_target_seq now set AFTER decrementing target_seq. Old code left both fields equal after expand, breaking PLC seq emission. ✅
  3. expand_frame_len tracking + RESET TOO EXPANDED — increments on PLC expand, decrements on acceleration drop, resets JB if expand_frame_len > max_frame_len. Prevents unbounded growth. ✅
  4. MAX_CONSECUTIVE_MISS=100 safety valve — falls back to non-elastic + reset after 100 consecutive misses. ✅
  5. !jb->elastic gate on BUFFERING path — elastic JB serves packets without waiting to refill frame_len. ✅
  6. consecutive_miss reset on successful read + buffering_skip counter. ✅

Non-blocking concerns

  • Subset of unmerged upstream. SignalWire PR signalwire#2337 is still OPEN. It also adds: switch_jb_export_stats(), switch_rtp_get_jitter_buffer_for_stats(), switch_core_media_export_jb_stats() (flush JB stats before hangup hooks), and a multi-tier forced/fast acceleration path (packets_in_buffer >= max_frame_len). The expand_frame_len cap is reactive (after PLC); upstream's proactive acceleration during reads is more robust. Recommend a follow-up ticket to track upstream.
  • Style: MAX_DROPOUT/MAX_CONSECUTIVE_MISS as file-scope #define vs upstream's function-local const int. No functional difference.

CI: Jenkins green. GH Actions cancelled (24h timeout) — not regressions. 3 existing approvals (2026-03-13).

Net: ✅ Ship it. Open follow-up for upstream re-cherry-pick or forced-acceleration port.

— 🔬 Analyst

@damirn

damirn commented Jun 23, 2026

Copy link
Copy Markdown

@dev-ryanc i see more commits in signalwire#2337 but i also wonder why is this still not merged in upstream

Ryan Colobong and others added 6 commits July 2, 2026 01:57
…alwire#2337

Cherry-picked fixes from signalwire#2337 (Julien Chavanton):

1. Sequence rollover bug in check_jb_size() - add MAX_DROPOUT guard
   to distinguish old packets from seq 65535->0 wraparound
2. decrement_seq() ordering - set last_target_seq after decrement
3. Buffering skip gate - do not block elastic mode with the
   complete_frames < frame_len check
4. Consecutive miss safety valve - after MAX_CONSECUTIVE_MISS (100)
   consecutive missed frames, disable elastic mode and reset JB
Track net buffer expansion via expand_frame_len (incremented on PLC
expand, decremented on acceleration). Reset the jitter buffer when
expansion exceeds max_frame_len to prevent unbounded growth during
sustained packet loss.

Cherry-picked from signalwire#2337 (Julien Chavanton).
…e PR signalwire#2337

Consolidate inline channel variable exports from the jitter buffer hot
path into switch_jb_export_stats(), called at hangup before hangup
hooks run (via switch_core_media_export_jb_stats) and as a safety net
before every switch_jb_destroy() in both switch_rtp_destroy() and the
KILL_JB path in read_rtp_packet().

This prevents a race condition where hangup hooks read stale/missing
JB stats because the jitter buffer was already destroyed or the inline
exports hadn't run recently enough.

Adapted from upstream commit 0388398 — excluded
fast_acceleration, forced_acceleration, and packets_in_buffer fields
which don't exist in our switch_jb struct.

Cherry-picked from signalwire#2337 (Julien Chavanton).
…nd boundary

[1] Reset jb->frame_len to jb->min_frame_len when disabling elastic mode
    after consecutive miss safety valve triggers. Without this, the JB
    stays at expanded size after elastic mode is disabled.

[2] Add SWITCH_LOG_ALERT debug log when elastic mode is disabled after
    MAX_CONSECUTIVE_MISS, matching upstream.

[5] Add expand_frame_len >= max_frame_len boundary check (third else-if
    branch) to catch the exact boundary case and reset via reset_error.
…signalwire#2337

1. Add switch_rtp_ready() guard in the JB poll loop to prevent
   polling after the session is being destroyed.

2. Add rtp_jitter_buffer_accelerate channel variable check to the
   tries > 20 condition, allowing JB reset to be skipped when
   acceleration is explicitly enabled.


Add elastic algorithm activity tracking and enhanced statistics
export from upstream commit af83338:

- New stats fields: grew, shrunk, grow_to_max, at_min_ms, at_max_ms,
  edge_since, reset_setup, reset_runtime
- New jb fields: media_started, packets_total
- Track frame_len edge transitions in jb_frame_inc_line()
- Split reset count into setup vs runtime in switch_jb_reset()
- Flush at-edge timer on set_frames()
- Initialize edge_since on JB creation
- Export all new metrics in switch_jb_export_stats() with a
  comprehensive summary log line
- Set media_started and increment packets_total on each put_packet

Cherry-picked from signalwire#2337 (Julien Chavanton).
@dev-ryanc

Copy link
Copy Markdown
Collaborator Author

@dev-ryanc i see more commits in signalwire#2337 but i also wonder why is this still not merged in upstream

@damirn Not sure why as well, but I don't see any issues with the implementation.

@baloeng baloeng left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes.

Comment thread src/switch_jitterbuffer.c Outdated
Comment thread src/switch_jitterbuffer.c
Comment thread src/switch_rtp.c Outdated
@tajamulTelnyx tajamulTelnyx changed the title TEL-6872: Cherry-pick elastic jitter buffer fixes from SignalWire PR #2337 TELCORE-248 / TEL-6872: Cherry-pick elastic jitter buffer fixes from SignalWire PR #2337 Jul 16, 2026
Fix double-decrement in decrement_seq() — last_target_seq was
over-decremented by one, shifting PLC sequence numbers an extra
packet behind.

Fix lock-order inversion in switch_jb_export_stats() — snapshot
JB fields under mutex, unlock before channel writes to avoid
holding JB mutex while acquiring channel-side locks from
hangup/destroy/KILL_JB contexts.

Cache rtp_jitter_buffer_accelerate channel var at function entry
instead of per-iteration in the retry loop, and restore an
unconditional hard retry bound (tries > 100) so the read loop
cannot spin indefinitely in bad-jitter conditions.
@tajamulTelnyx
tajamulTelnyx requested a review from baloeng July 16, 2026 16:43
@tajamulTelnyx
tajamulTelnyx merged commit 0ed80ca into telnyx/telephony/deploy-development Jul 16, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

6 participants