Conversation
…nprivileged range Devin review feedback on PR livekit#811: 1. inbound drainOnHangup: a remote CANCEL before the call is answered hit the 'cancelled' branch and slept the drain window with no media bridged. Gate the drain on the media actually being bridged (c.started broken) and drop 'cancelled' from the local-hangup set. Remote CANCEL is not a local hangup. 2. newTestMediaPort bound a random even port in 1-65535, which can land on a privileged port (<1024) and fail in CI with 'bind: permission denied' (seen on TestSetOfferReportsUnknownProvider). Constrain the test range to 10000-20000.
|
Addressed the Devin review finding (cancelled inbound call delayed): |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #811 +/- ##
==========================================
+ Coverage 65.25% 67.67% +2.41%
==========================================
Files 51 43 -8
Lines 6588 8563 +1975
==========================================
+ Hits 4299 5795 +1496
- Misses 1915 2242 +327
- Partials 374 526 +152 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…nprivileged range Devin review feedback on PR livekit#811: 1. inbound drainOnHangup: a remote CANCEL before the call is answered hit the 'cancelled' branch and slept the drain window with no media bridged. Gate the drain on the media actually being bridged (c.started broken) and drop 'cancelled' from the local-hangup set. Remote CANCEL is not a local hangup. 2. newTestMediaPort bound a random even port in 1-65535, which can land on a privileged port (<1024) and fail in CI with 'bind: permission denied' (seen on TestSetOfferReportsUnknownProvider). Constrain the test range to 10000-20000.
a871b74 to
16668d2
Compare
Review feedback on livekit#811: - inbound/outbound drain now uses select{ctx.Done, time.After} instead of time.Sleep so a context cancellation cuts the drain window short (inbound close() runs under WithoutCancel, so this preserves the full drain for clean hangups while allowing cancellation to pre-empt it). - outboundCall.close() releases c.mu for the drain window: Close, CloseWith and CloseWithTimeout invoke close() while holding c.mu, and sleeping under the lock would block Participant() readers and a concurrent shutdown for the whole drain duration.
|
Pushed cab53c5 addressing both review comments:
Verified: |
|
Thanks @genseric-ghiro for the review. I merged the latest
The latest head ( |
…ping
When an agent ends a call (EndCall RPC, end_call tool, or participant
removed from the room) right after wait_for_playout() returns, the last
word of the utterance is clipped on the callee's end. The audio is
already past the agent — it is buffered in the room mixer (~100ms input
buffer), the encoder, and in-flight RTP — but the SIP bridge sends BYE
and closes media immediately, so the tail never clears the wire (issue
Add a configurable hangup drain window (hangup_drain_time, default
500ms): on a locally-initiated clean hangup, keep feeding media for the
window before sending BYE. Remote BYE, errors, timeouts, and
pre-connect failures are not delayed.
Regression test: an EndCall RPC on an established outbound call must not
emit BYE before the drain window has elapsed. Fails on the old code
('BYE sent before the media drain window elapsed'), passes with the
fix.
…nprivileged range Devin review feedback on PR livekit#811: 1. inbound drainOnHangup: a remote CANCEL before the call is answered hit the 'cancelled' branch and slept the drain window with no media bridged. Gate the drain on the media actually being bridged (c.started broken) and drop 'cancelled' from the local-hangup set. Remote CANCEL is not a local hangup. 2. newTestMediaPort bound a random even port in 1-65535, which can land on a privileged port (<1024) and fail in CI with 'bind: permission denied' (seen on TestSetOfferReportsUnknownProvider). Constrain the test range to 10000-20000.
6bc104d to
bac9dc1
Compare
There was a problem hiding this comment.
Devin Review found 1 new potential issue.
2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)
| if end.Term.Result != stats.ResultSuccess { | ||
| return 0 | ||
| } |
There was a problem hiding this comment.
🟡 Pre-connect hangups delay cancellation
Before media starts, drainOnHangup delays local RPC and room-removal terminations. CANCEL then waits despite having no media to drain, extending ringing.
Learn more
Outbound calls set started only after SIP negotiation succeeds and media is connected in connectSIP. A local close can occur before that point, but the new helper classifies it from the termination reason alone. The close path then sleeps before sipOutbound.Close sends the CANCEL needed for an unanswered INVITE.
Example: An EndCall RPC reaches a newly created outbound call before its INVITE is answered. With hangup_drain_time: 2s, the callee keeps ringing for two extra seconds before receiving CANCEL, although no media has started.
Recommended fix: Require c.started.IsBroken() before returning HangupDrainTime, matching the inbound guard.
| if end.Term.Result != stats.ResultSuccess { | |
| return 0 | |
| } | |
| if end.Term.Result != stats.ResultSuccess || !c.started.IsBroken() { | |
| return 0 | |
| } |
Was this helpful? React with 👍 or 👎 to provide feedback.
…nprivileged range Devin review feedback on PR livekit#811: 1. inbound drainOnHangup: a remote CANCEL before the call is answered hit the 'cancelled' branch and slept the drain window with no media bridged. Gate the drain on the media actually being bridged (c.started broken) and drop 'cancelled' from the local-hangup set. Remote CANCEL is not a local hangup. 2. newTestMediaPort bound a random even port in 1-65535, which can land on a privileged port (<1024) and fail in CI with 'bind: permission denied' (seen on TestSetOfferReportsUnknownProvider). Constrain the test range to 10000-20000.
bac9dc1 to
fa26463
Compare
Fixes livekit/livekit#4737
Problem
When an agent ends a call right after
wait_for_playout()returns — EndCall RPC,end_calltool, or removing the SIP participant — the last word of the utterance is clipped on the callee's end (voicemail scenario). The audio is already past the agent: it is buffered in the room mixer (5-frame ~100ms input buffer), the codec encoder, and in-flight RTP.wait_for_playout()only tracks the local queue, so it cannot see this tail.The SIP bridge then sends BYE and closes media immediately, so whatever is still buffered never clears the wire. A trailing pause masks the bug because it gives the tail time to flush; an abrupt end-of-speech hangup clips the last word. The reporter's workaround (2s pre-hangup sleep) confirms the mechanism.
Fix
Add a configurable drain window
hangup_drain_time(default500ms): on a locally-initiated clean hangup, keep feeding media to the peer for the window before sending BYE. The window is bounded (default 500ms) and only applies to:EndCallRPC (reasonrpc)hangup)removed)cancelled)Remote BYE (the peer hung up), errors, timeouts, media failures, and pre-connect failures are not delayed. Set
hangup_drain_time: -1to disable.Test
TestOutboundHangupDrainsMediaBeforeBYE: on an established outbound call, anEndCallRPC must not emit BYE before the drain window has elapsed. Fails on the old code (BYE sent before the media drain window elapsed), passes with the fix. All media-port and outbound tests pass; the pre-existing auth-test failures on clean main are unrelated (they fail identically without this change).