summaryrefslogtreecommitdiff
path: root/indra/newview/llvoicewebrtc.cpp
diff options
context:
space:
mode:
authorRoxanne Skelly <roxie@lindenlab.com>2026-08-18 10:19:03 -0700
committerGitHub <noreply@github.com>2026-08-18 10:19:03 -0700
commitae3e2ee100b0166525b149aecbde5f68ebc683b2 (patch)
tree85261cda141a3b03f1b700e2bfd98170e33a2dd3 /indra/newview/llvoicewebrtc.cpp
parentb33d4053eca36480098f764c7b7ad8f3516641ea (diff)
parent66570c6e4690ead5f9b9be5ef5f35eadb87db327 (diff)
Merge pull request #6129 from secondlife/roxie/fix-webrtc-terminate
Fix shutdown crash and cut WebRTC teardown time
Diffstat (limited to 'indra/newview/llvoicewebrtc.cpp')
-rw-r--r--indra/newview/llvoicewebrtc.cpp124
1 files changed, 114 insertions, 10 deletions
diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp
index 9b4a371cec..c82a005a77 100644
--- a/indra/newview/llvoicewebrtc.cpp
+++ b/indra/newview/llvoicewebrtc.cpp
@@ -208,6 +208,7 @@ LLSD LLVoiceWebRTCStats::read()
///////////////////////////////////////////////////////////////////////////////////////////////
bool LLWebRTCVoiceClient::sShuttingDown = false;
+bool LLWebRTCVoiceClient::sWebRTCTerminated = false;
LLWebRTCVoiceClient::LLWebRTCVoiceClient() :
mHidden(false),
@@ -233,6 +234,7 @@ LLWebRTCVoiceClient::LLWebRTCVoiceClient() :
mWebRTCDeviceInterface(nullptr)
{
sShuttingDown = false;
+ sWebRTCTerminated = false;
mSpeakerVolume = 0.0;
@@ -301,11 +303,81 @@ void LLWebRTCVoiceClient::terminate()
mVoiceEnabled = false;
sShuttingDown = true; // so that coroutines won't post more work.
+
+ drainConnections();
+
+ sWebRTCTerminated = true;
llwebrtc::terminate();
mWebRTCDeviceInterface = nullptr;
}
+// Close the live peer connections before handing control to
+// llwebrtc::terminate().
+//
+// Anything still open when terminate() runs gets closed inline and serially on
+// the signaling thread, under a single 10s budget that is also paying for the
+// audio device shutdown. An estate session can hold ten live connections --
+// the current region plus up to eight neighbours, plus any group or ad-hoc
+// session -- each needing a full DTLS/SCTP teardown, so that budget is not
+// generous. Closing them here lets the teardown proceed asynchronously on the
+// signaling thread while this thread keeps pumping.
+//
+// It also quiets the per-connection stats poll before terminate() runs. A
+// GetStats request left in flight makes PeerConnection::Close() block in
+// RTCStatsCollector::WaitForPendingRequest(), which waits on the network thread
+// with no timeout at all.
+//
+// Best effort: whatever hasn't closed by the deadline is left to
+// llwebrtc::terminate(), exactly as before.
+void LLWebRTCVoiceClient::drainConnections()
+{
+ // Marks every session and connection as shutting down. This also stops
+ // estateSessionState::processConnectionStates() from spinning up
+ // replacement connections to neighbouring regions while we drain.
+ sessionState::for_each(boost::bind(predShutdownSession, _1));
+
+ // Long enough for a local close to complete, short enough that a wedged
+ // connection doesn't noticeably delay quitting. The remaining budget in
+ // llwebrtc::terminate() is the real backstop.
+ constexpr F32 DRAIN_TIMEOUT_SECONDS = 3.0f;
+ constexpr U32 DRAIN_POLL_MS = 10;
+
+ // Wait on the peer connections being closed rather than on the sessions
+ // being reaped. A connection that still has an HTTP coroutine in flight
+ // holds its session alive until mOutstandingRequests unwinds, and those
+ // coroutines don't run from here -- but its peer connection has already
+ // been closed by then, which is all terminate() cares about.
+ LLTimer timer;
+ while (!sessionState::allSessionsClosed() && timer.getElapsedTimeF32() < DRAIN_TIMEOUT_SECONDS)
+ {
+ // OnPeerConnectionClosed comes back through the main queue, so it has
+ // to be pumped or the state machines never see connections finish.
+ if (auto main_queue = mMainQueue.lock())
+ {
+ main_queue->runFor(std::chrono::milliseconds(DRAIN_POLL_MS));
+ }
+ sessionState::processSessionStates();
+
+ if (!sessionState::allSessionsClosed())
+ {
+ ms_sleep(DRAIN_POLL_MS);
+ }
+ }
+
+ if (!sessionState::allSessionsClosed())
+ {
+ LL_WARNS("Voice") << "Timed out draining voice connections after "
+ << DRAIN_TIMEOUT_SECONDS
+ << "s; leaving the rest to llwebrtc::terminate()." << LL_ENDL;
+ }
+ else
+ {
+ LL_INFOS("Voice") << "Voice connections drained in "
+ << timer.getElapsedTimeF32() << "s." << LL_ENDL;
+ }
+}
+
//---------------------------------------------------
void LLWebRTCVoiceClient::cleanUp()
@@ -1890,6 +1962,7 @@ void LLWebRTCVoiceClient::userAuthorized(const std::string& user_id, const LLUUI
if (sShuttingDown)
{
sShuttingDown = false; // was terminated, restart
+ sWebRTCTerminated = false;
initWebRTC();
}
}
@@ -2173,6 +2246,30 @@ void LLWebRTCVoiceClient::sessionState::processSessionStates()
}
}
+bool LLWebRTCVoiceClient::sessionState::allConnectionsClosed() const
+{
+ for (const auto &connection : mWebRTCConnections)
+ {
+ if (!connection->isClosed())
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool LLWebRTCVoiceClient::sessionState::allSessionsClosed()
+{
+ for (const auto &session : sSessions)
+ {
+ if (session.second && !session.second->allConnectionsClosed())
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
// process the states on each connection associated with a session.
bool LLWebRTCVoiceClient::sessionState::processConnectionStates()
{
@@ -2425,12 +2522,18 @@ LLVoiceWebRTCConnection::LLVoiceWebRTCConnection(const LLUUID &regionID, const s
LLVoiceWebRTCConnection::~LLVoiceWebRTCConnection()
{
- if (LLWebRTCVoiceClient::isShuttingDown())
+ if (LLWebRTCVoiceClient::isWebRTCTerminated())
{
- // peer connection and observers will be cleaned up
- // by llwebrtc::terminate() on shutdown.
+ // peer connection and observers have already been cleaned up
+ // by llwebrtc::terminate().
return;
}
+ // Note this is deliberately keyed off isWebRTCTerminated() rather than
+ // isShuttingDown(): connections drained by drainConnections() are destroyed
+ // while the webrtc library is still fully alive, and must unregister
+ // themselves and release the peer connection like any other close. Leaving
+ // a freed observer registered would hand llwebrtc::terminate() a dangling
+ // pointer to call OnPeerConnectionClosed() on.
mWebRTCPeerConnectionInterface->unsetSignalingObserver(this);
llwebrtc::freePeerConnection(mWebRTCPeerConnectionInterface);
}
@@ -3092,8 +3195,10 @@ bool LLVoiceWebRTCConnection::connectionStateMachine()
}
else
{
- // llwebrtc::terminate() is already shuting down the connection.
- setVoiceConnectionState(VOICE_STATE_WAIT_FOR_CLOSE);
+ // Shutting down: skip the courtesy logout to the sim (the HTTP
+ // round trip would just delay quitting) and go straight to
+ // dropping the webrtc connection.
+ setVoiceConnectionState(VOICE_STATE_SESSION_EXIT);
}
break;
@@ -3104,11 +3209,10 @@ bool LLVoiceWebRTCConnection::connectionStateMachine()
{
setVoiceConnectionState(VOICE_STATE_WAIT_FOR_CLOSE);
mOutstandingRequests++;
- if (!LLWebRTCVoiceClient::isShuttingDown())
- {
- mWebRTCPeerConnectionInterface->shutdownConnection();
- }
- // else was already posted by llwebrtc::terminate().
+ // Always drop the connection ourselves, including during shutdown:
+ // drainConnections() runs before llwebrtc::terminate(), so nothing
+ // else has posted the close yet.
+ mWebRTCPeerConnectionInterface->shutdownConnection();
break;
}