diff --git a/indra/llaudio/llaudioengine.h b/indra/llaudio/llaudioengine.h index 2a2355b019..aa0617f936 100644 --- a/indra/llaudio/llaudioengine.h +++ b/indra/llaudio/llaudioengine.h @@ -39,6 +39,10 @@ #include "llframetimer.h" #include "llassettype.h" #include "llextendedstatus.h" +// [FIRE-36022] - Removing my USB headset crashes entire viewer +// Need to include for audio device mutex shared with other audio/voice systems. +#include "inlinemutexs.h" +// [FIRE-36022] #include "lllistener.h" diff --git a/indra/llaudio/llaudioengine_fmodstudio.cpp b/indra/llaudio/llaudioengine_fmodstudio.cpp index e9996b93e0..7daaea1c76 100644 --- a/indra/llaudio/llaudioengine_fmodstudio.cpp +++ b/indra/llaudio/llaudioengine_fmodstudio.cpp @@ -44,6 +44,9 @@ #include "sound_ids.h" +// [FIRE-36022] - Removing my USB headset crashes entire viewer +using namespace std::chrono_literals; // Needed for shared timed mutex to use time +// [FIRE-36022] constexpr U32 EXTRA_SOUND_CHANNELS = 10; FMOD_RESULT F_CALL windCallback(FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int *outchannels); @@ -113,8 +116,14 @@ static void set_device(FMOD::System* system, const LLUUID& device_uuid) } } -FMOD_RESULT F_CALL systemCallback(FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACK_TYPE type, void *commanddata1, void *commanddata2, void* userdata) +// [FIRE-36022] - Removing my USB headset crashes entire viewer +// According to FMOD, not having this method static is very bad. +//FMOD_RESULT F_CALL systemCallback(FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACK_TYPE type, void *commanddata1, void *commanddata2, void* userdata) +static FMOD_RESULT F_CALL systemCallback(FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACK_TYPE type, void *commanddata1, void *commanddata2, void* userdata) { + try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid + { +// [FIRE-36022] FMOD::System* sys = (FMOD::System*)system; LLAudioEngine_FMODSTUDIO* audio_engine = (LLAudioEngine_FMODSTUDIO*)userdata; @@ -124,6 +133,17 @@ FMOD_RESULT F_CALL systemCallback(FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACK_TYPE LL_DEBUGS("FMOD") << "FMOD system callback FMOD_SYSTEM_CALLBACK_DEVICELISTCHANGED" << LL_ENDL; if (sys && audio_engine) { + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Attempt to lock the access to the audio device, wait up to 1 second for other threads to unlock. + std::unique_lock lock(gAudioDeviceMutex, 1s); + // If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass. + // Prevents threads from interacting with the hardware at the same time as other audio/voice threads. + if (!lock.owns_lock()) + { + LL_INFOS() << "Could not access the audio device mutex, trying again later" << LL_ENDL; + return FMOD_OK; // Could be a FMOD_ERR_ALREADY_LOCKED; + } + // [FIRE-36022] set_device(sys, audio_engine->getSelectedDeviceUUID()); audio_engine->OnOutputDeviceListChanged(audio_engine->getDevices()); } @@ -132,6 +152,34 @@ FMOD_RESULT F_CALL systemCallback(FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACK_TYPE break; } return FMOD_OK; + // [FIRE-36022] - Removing my USB headset crashes entire viewer + } + // There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur + catch (const std::system_error& e) + { + if (e.code() == std::errc::resource_deadlock_would_occur) + { + LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL; + return FMOD_ERR_ALREADY_LOCKED; + } + else if (e.code() == std::errc::operation_not_permitted) + { + LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL; + return FMOD_ERR_ALREADY_LOCKED; + } + else + { + LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL; + } + + return FMOD_ERR_ALREADY_LOCKED; + } + catch (const std::exception& e) + { + LL_WARNS() << "Exception FMOD: " << " " << e.what() << LL_ENDL; + return FMOD_ERR_ALREADY_LOCKED; + } + // [FIRE-36022] } LLAudioEngine_FMODSTUDIO::LLAudioEngine_FMODSTUDIO(bool enable_profiler, U32 resample_method) @@ -311,6 +359,11 @@ bool LLAudioEngine_FMODSTUDIO::init(void* userdata, const std::string &app_title LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init() FMOD Studio initialized correctly" << LL_ENDL; FMOD_ADVANCEDSETTINGS settings_dump = { }; + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // With the FMOD debug library used, turns out this object needs to have a size assigned to it otherwise it will fail. + // So the viewer never got any advanced settings for the info below. + settings_dump.cbSize = sizeof(FMOD_ADVANCEDSETTINGS); + // [FIRE-36022] mSystem->getAdvancedSettings(&settings_dump); LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): resampler=" << settings_dump.resamplerMethod << " bytes" << LL_ENDL; @@ -373,7 +426,47 @@ LLAudioEngine_FMODSTUDIO::output_device_map_t LLAudioEngine_FMODSTUDIO::getDevic void LLAudioEngine_FMODSTUDIO::setDevice(const LLUUID& device_uuid) { mSelectedDeviceUUID = device_uuid; + // [FIRE-36022] - Removing my USB headset crashes entire viewer + try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid + { + // Attempt to lock the access to the audio device, wait up to 1 second for other threads to unlock. + std::unique_lock lock(gAudioDeviceMutex, 1s); + // If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass. + // Prevents threads from interacting with the hardware at the same time as other audio/voice threads. + if (!lock.owns_lock()) + { + LL_INFOS() << "Could not access the audio device mutex, trying again later" << LL_ENDL; + return; + } + // [FIRE-36022] set_device(mSystem, device_uuid); + // [FIRE-36022] - Removing my USB headset crashes entire viewer + } + // There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur + catch (const std::system_error& e) + { + if (e.code() == std::errc::resource_deadlock_would_occur) + { + LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL; + } + else if (e.code() == std::errc::operation_not_permitted) + { + LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL; + } + else + { + LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL; + } + + return; + } + catch (const std::exception& e) + { + LL_WARNS() << "Exception FMOD: " << " " << e.what() << LL_ENDL; + return; + } + // [FIRE-36022] + } std::string LLAudioEngine_FMODSTUDIO::getDriverName(bool verbose) diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 0d88dec885..1ba315b11a 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -122,6 +122,7 @@ set(llcommon_HEADER_FILES fsyspath.h function_types.h indra_constants.h + inlinemutexs.h lazyeventapi.h linden_common.h llalignedarray.h diff --git a/indra/llcommon/inlinemutexs.h b/indra/llcommon/inlinemutexs.h new file mode 100644 index 0000000000..3b8ab5c4ef --- /dev/null +++ b/indra/llcommon/inlinemutexs.h @@ -0,0 +1,39 @@ +/** +* @file inlinemutexs.h +* @brief Declaration of inline mutexs +* @author minerjr@firestorm +* + * $LicenseInfo:firstyear=2025&license=fsviewerlgpl$ + * Phoenix Firestorm Viewer Source Code + * Copyright (C) 2025, Minerjr + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA + * http://www.firestormviewer.org + * $/LicenseInfo$ +*/ + +#ifndef INLINE_MUTEXS_HEADER +#define INLINE_MUTEXS_HEADER +#include +#include +#include + +// Audio device mutex to be shared between audio engine and Voice systems to +// syncronize on when audio hardware accessed for disconnected/connecting hardware +// Uses Timed Mutex so as to not lockup the threads forever. +inline std::timed_mutex gAudioDeviceMutex; +#endif diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp index d686082878..860f619885 100644 --- a/indra/llwebrtc/llwebrtc.cpp +++ b/indra/llwebrtc/llwebrtc.cpp @@ -27,6 +27,11 @@ #include "llwebrtc_impl.h" #include #include +// [FIRE-36022] - Removing my USB headset crashes entire viewer +// Needed for accessing the inline timed mutex for accessing audio hardware. +#include +#include +// [FIRE-36022] #include "api/audio_codecs/audio_decoder_factory.h" #include "api/audio_codecs/audio_encoder_factory.h" @@ -39,6 +44,14 @@ #include "modules/audio_mixer/audio_mixer_impl.h" #include "api/environment/environment_factory.h" +// [FIRE-36022] - Removing my USB headset crashes entire viewer +// Audio device mutex to be shared between audio engine and Voice systems to +// syncronize on when audio hardware accessed for disconnected/connecting hardware +// Uses Timed Mutex so as to not lockup the threads forever. +inline std::timed_mutex gAudioDeviceMutex; +// Need to use to access the 3 second timeout for the lock. +using namespace std::chrono_literals; +// [FIRE-36022] namespace llwebrtc { #if WEBRTC_WIN @@ -269,7 +282,11 @@ void LLWebRTCImpl::init() webrtc::InitializeSSL(); // Normal logging is rather spammy, so turn it off. + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Turn on more verbose logging as we are looking for crashes. webrtc::LogMessage::LogToDebug(webrtc::LS_NONE); + //webrtc::LogMessage::LogToDebug(webrtc::LS_VERBOSE); + // [FIRE-36022] webrtc::LogMessage::SetLogToStderr(true); webrtc::LogMessage::AddLogToStream(mLogSink, webrtc::LS_VERBOSE); @@ -457,8 +474,24 @@ void LLWebRTCImpl::unsetDevicesObserver(LLWebRTCDevicesObserver *observer) // must be run in the worker thread. void LLWebRTCImpl::workerDeployDevices() { + // [FIRE-36022] - Removing my USB headset crashes entire viewer + try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid + { + // Attempt to lock the access to the audio device, wait up to 1 second for other threads to unlock. + std::unique_lock lock(gAudioDeviceMutex, 1s); + // If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass. + // Prevents threads from interacting with the hardware at the same time as other audio/voice threads. + if (!lock.owns_lock()) + { + return; + } + // [FIRE-36022] if (!mDeviceModule) { + // [FIRE-36022] + // If the device is not avaiable, then make sure the flag for the WebRTC updated devices flag is turned off for the co-routine + gWebRTCUpdateDevices = false; + // [FIRE-36022] - Removing my USB headset crashes entire viewer return; } @@ -483,6 +516,10 @@ void LLWebRTCImpl::workerDeployDevices() } } + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Flag the device is being interacted with for the Co-routine in case something goes wrong. + gWebRTCUpdateDevices = true; + // [FIRE-36022] mDeviceModule->StopPlayout(); mDeviceModule->ForceStopRecording(); #if WEBRTC_WIN @@ -546,6 +583,10 @@ void LLWebRTCImpl::workerDeployDevices() { mDeviceModule->StartPlayout(); } + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Finally signal to the co-routine everyting is OK. + gWebRTCUpdateDevices = false; + // [FIRE-36022] mSignalingThread->PostTask( [this] { @@ -566,6 +607,38 @@ void LLWebRTCImpl::workerDeployDevices() mWorkerThread->PostTask([this] { workerDeployDevices(); }); } }); + // [FIRE-36022] - Removing my USB headset crashes entire viewer + } + // There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur + catch (const std::system_error& e) + { + if (e.code() == std::errc::resource_deadlock_would_occur) + { + // Another thead may have alreayd called this method + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + else if (e.code() == std::errc::operation_not_permitted) + { + // This should not be reached + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + else + { + // Log any other message + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + // Device no longer being interacted with + gWebRTCUpdateDevices = false; + return; + } + catch (const std::exception& e) + { + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + // Device no longer being interacted with + gWebRTCUpdateDevices = false; + return; + } + // [FIRE-36022] } void LLWebRTCImpl::setCaptureDevice(const std::string &id) @@ -584,11 +657,27 @@ void LLWebRTCImpl::setRenderDevice(const std::string &id) // updateDevices needs to happen on the worker thread. void LLWebRTCImpl::updateDevices() { + // [FIRE-36022] - Removing my USB headset crashes entire viewer + try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid + { + // Attempt to lock the access to the audio device, wait up to 1 second for other threads to unlock. + std::unique_lock lock(gAudioDeviceMutex, 1s); + // If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass. + // Prevents threads from interacting with the hardware at the same time as other audio/voice threads. + if (!lock.owns_lock()) + { + return; + } + // [FIRE-36022] if (!mDeviceModule) { return; } + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Flag the device is being interacted with for the Co-routine in case something goes wrong. + gWebRTCUpdateDevices = true; + // [FIRE-36022] int16_t renderDeviceCount = mDeviceModule->PlayoutDevices(); mPlayoutDeviceList.clear(); @@ -625,10 +714,46 @@ void LLWebRTCImpl::updateDevices() mRecordingDeviceList.emplace_back(name, guid); } + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Flag the device is no longer being interacted with for the Co-routine in case something goes wrong. + gWebRTCUpdateDevices = false; + // [FIRE-36022] for (auto &observer : mVoiceDevicesObserverList) { observer->OnDevicesChanged(mPlayoutDeviceList, mRecordingDeviceList); } + // [FIRE-36022] - Removing my USB headset crashes entire viewer + } + // There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur + catch (const std::system_error& e) + { + if (e.code() == std::errc::resource_deadlock_would_occur) + { + // Another thead may have alreayd called this method + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + else if (e.code() == std::errc::operation_not_permitted) + { + // This should not be reached + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + else + { + // Log any other message + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + // Device no longer being interacted with + gWebRTCUpdateDevices = false; + return; + } + catch (const std::exception& e) + { + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + // Device no longer being interacted with + gWebRTCUpdateDevices = false; + return; + } + // [FIRE-36022] } void LLWebRTCImpl::OnDevicesUpdated() @@ -726,7 +851,56 @@ void LLWebRTCImpl::intSetMute(bool mute, int delay_ms) { if (mDeviceModule) { + // [FIRE-36022] - Removing my USB headset crashes entire viewer + try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is + // invalid + { + // Attempt to lock the access to the audio device, wait up to 1 second for other threads to unlock. + std::unique_lock lock(gAudioDeviceMutex, 1s); + // If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass. + // Prevents threads from interacting with the hardware at the same time as other audio/voice threads. + if (!lock.owns_lock()) + { + return; + } + // Flag the device is being interacted with for the Co-routine in case something goes wrong. + gWebRTCUpdateDevices = true; + // [FIRE-36022] mDeviceModule->ForceStopRecording(); + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Finally signal to the co-routine everyting is OK. + gWebRTCUpdateDevices = false; + } + // There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur + catch (const std::system_error& e) + { + if (e.code() == std::errc::resource_deadlock_would_occur) + { + // Another thead may have alreayd called this method + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + else if (e.code() == std::errc::operation_not_permitted) + { + // This should not be reached + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + else + { + // Log any other message + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + // Device no longer being interacted with + gWebRTCUpdateDevices = false; + return; + } + catch (const std::exception& e) + { + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + // Device no longer being interacted with + gWebRTCUpdateDevices = false; + return; + } + // [FIRE-36022] } }, webrtc::TimeDelta::Millis(delay_ms)); @@ -738,8 +912,57 @@ void LLWebRTCImpl::intSetMute(bool mute, int delay_ms) { if (mDeviceModule) { + // [FIRE-36022] - Removing my USB headset crashes entire viewer + try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is + // invalid + { + // Attempt to lock the access to the audio device, wait up to 1 second for other threads to unlock. + std::unique_lock lock(gAudioDeviceMutex, 1s); + // If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass. + // Prevents threads from interacting with the hardware at the same time as other audio/voice threads. + if (!lock.owns_lock()) + { + return; + } + // Flag the device is being interacted with for the Co-routine in case something goes wrong. + gWebRTCUpdateDevices = true; + // [FIRE-36022] mDeviceModule->InitRecording(); mDeviceModule->ForceStartRecording(); + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Finally signal to the co-routine everyting is OK. + gWebRTCUpdateDevices = false; + } + // There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur + catch (const std::system_error& e) + { + if (e.code() == std::errc::resource_deadlock_would_occur) + { + // Another thead may have alreayd called this method + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + else if (e.code() == std::errc::operation_not_permitted) + { + // This should not be reached + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + else + { + // Log any other message + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + // Device no longer being interacted with + gWebRTCUpdateDevices = false; + return; + } + catch (const std::exception& e) + { + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + // Device no longer being interacted with + gWebRTCUpdateDevices = false; + return; + } + // [FIRE-36022] } }); } diff --git a/indra/llwebrtc/llwebrtc.h b/indra/llwebrtc/llwebrtc.h index 7d06b7d2b4..acd3b686fa 100644 --- a/indra/llwebrtc/llwebrtc.h +++ b/indra/llwebrtc/llwebrtc.h @@ -40,6 +40,10 @@ #include #include +// [FIRE-36022] - Removing my USB headset crashes entire viewer +// Needed for inline variable for the crash check +#include +// [FIRE-36022] #ifdef LL_MAKEDLL #ifdef WEBRTC_WIN @@ -53,6 +57,12 @@ #define LLSYMEXPORT /**/ #endif // LL_MAKEDLL +// [FIRE-36022] - Removing my USB headset crashes entire viewer +// Create an atomic inline flag that will be shared between the various WebRTC threads and co-routines +// to track of when the audio hardware is being talked to. The co-routine can use it to +// exit if it too many iterations with the hardware locked indicating that the worker thread died. +inline std::atomic gWebRTCUpdateDevices = false; +// [FIRE-36022] namespace llwebrtc { diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index fbdb48cf0c..0ffc53c411 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -1016,6 +1016,11 @@ bool LLAppViewerWin32::cleanup() gDXHardware.cleanup(); + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Need to unilitialize connection to COM, otherwise it will be treated as a memroy leak. + CoUninitialize(); + // [FIRE-36022] + if (mIsConsoleAllocated) { FreeConsole(); @@ -1056,6 +1061,19 @@ bool LLAppViewerWin32::initWindow() LL_WARNS("AppInit") << "Unable to set WindowWidth and WindowHeight for FullScreen mode" << LL_ENDL; } } + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Acccording to the FMOD spec, you are suppose to initalize COM on the thead that will talk to FMOD. IE the main thread. + // There is a coorisponding CoUninitialize in the shutdown code. Otherwise, FMOD will force the initalize with a warning, but does not clean up COM + HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + if (SUCCEEDED(hr)) + { + LL_INFOS() << "WIN32: CoInitializeEx COM as COINIT_APARTMENTTHREADED Successful" << LL_ENDL; + } + else + { + LL_INFOS() << "WIN32: CoInitializeEx COM as COINIT_APARTMENTTHREADED Failed" << LL_ENDL; + } + // [FIRE-36022] return LLAppViewer::initWindow(); } diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h index 33ed6b969d..aea9893686 100644 --- a/indra/newview/llvoiceclient.h +++ b/indra/newview/llvoiceclient.h @@ -38,6 +38,10 @@ class LLVOAvatar; #include "llcallingcard.h" // for LLFriendObserver #include "llsecapi.h" #include "llcontrol.h" +// [FIRE-36022] - Removing my USB headset crashes entire viewer +// Need to include for audio device mutex shared with other audio/voice systems. +#include "inlinemutexs.h" +// [FIRE-36022] // devices diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index 0f57e50228..e45ab3d3bd 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -82,6 +82,9 @@ extern void handle_voice_morphing_subscribe(); const std::string VIVOX_VOICE_SERVER_TYPE = "vivox"; +// [FIRE-36022] - Removing my USB headset crashes entire viewer +using namespace std::chrono_literals; // Needed for shared timed mutex to use time +// [FIRE-36022] namespace { const F32 VOLUME_SCALE_VIVOX = 0.01f; @@ -2413,6 +2416,19 @@ void LLVivoxVoiceClient::sendCaptureAndRenderDevices() { if (mCaptureDeviceDirty || mRenderDeviceDirty) { + // [FIRE-36022] - Removing my USB headset crashes entire viewer + try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid + { + // Attempt to lock the access to the audio device, wait up to 1 second for other threads to unlock. + std::unique_lock lock(gAudioDeviceMutex, 1s); + // If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass. + // Prevents threads from interacting with the hardware at the same time as other audio/voice threads. + if (!lock.owns_lock()) + { + LL_INFOS() << "Could not access the audio device mutex, trying again later" << LL_ENDL; + return; + } + // [FIRE-36022] std::ostringstream stream; buildSetCaptureDevice(stream); @@ -2424,6 +2440,35 @@ void LLVivoxVoiceClient::sendCaptureAndRenderDevices() } llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); + // [FIRE-36022] - Removing my USB headset crashes entire viewer + } + // There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur + catch (const std::system_error& e) + { + if (e.code() == std::errc::resource_deadlock_would_occur) + { + // When trying to lock the same lock a second time + LL_WARNS() << "Exception Vinvox: " << e.code() << " " << e.what() << LL_ENDL; + } + else if (e.code() == std::errc::operation_not_permitted) + { + // When the mutex is invalid + LL_WARNS() << "Exception Vinvox: " << e.code() << " " << e.what() << LL_ENDL; + } + else + { + // Everything else + LL_WARNS() << "Exception Vinvox: " << e.code() << " " << e.what() << LL_ENDL; + } + + return; + } + catch (const std::exception& e) + { + LL_WARNS() << "Exception Vinvox: " << " " << e.what() << LL_ENDL; + return; + } + // [FIRE-36022] } } diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp index acda5a5a44..1633f7a957 100644 --- a/indra/newview/llvoicewebrtc.cpp +++ b/indra/newview/llvoicewebrtc.cpp @@ -80,6 +80,9 @@ const std::string WEBRTC_VOICE_SERVER_TYPE = "webrtc"; +// [FIRE-36022] - Removing my USB headset crashes entire viewer +using namespace std::chrono_literals; // Needed for shared timed mutex to use time +// [FIRE-36022] namespace { const F32 MAX_AUDIO_DIST = 50.0f; @@ -506,6 +509,12 @@ void LLWebRTCVoiceClient::voiceConnectionCoro() try { LLMuteList::getInstance()->addObserver(this); + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Add a counter to check if the main thread locked up + // to prevent this thread/corutine form filling up + // the mMainQueue. + static U32 crash_check = 0; + // [FIRE-36022] while (!sShuttingDown) { LL_PROFILE_ZONE_NAMED_CATEGORY_VOICE("voiceConnectionCoroLoop") @@ -591,6 +600,26 @@ void LLWebRTCVoiceClient::voiceConnectionCoro() // to send position updates. updatePosition(); } + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // If the device locked, count up by 1 + if (gWebRTCUpdateDevices) + { + crash_check++; + } + // Else if the device is not locked, then reset the counter back to 0 + else + { + crash_check = 0; + } + // If there are over 10 cycles of the devices being locked, there is a good + // chance that the thread failed due to hardware/audio engine issue. + if (crash_check > 10) + { + LL_WARNS() << "WebRTC detected locked worker thread, will shutdown to prevent total viewer lockup." << LL_ENDL; + // Exit out of the thread and flag WebRTC to shutdown, hopefully clearing the lock and allowing the viewer to continue. + sShuttingDown = true; + } + // [FIRE-36022] } LL::WorkQueue::postMaybe(mMainQueue, [=, this] { @@ -731,6 +760,19 @@ void LLWebRTCVoiceClient::OnDevicesChanged(const llwebrtc::LLWebRTCVoiceDeviceLi void LLWebRTCVoiceClient::OnDevicesChangedImpl(const llwebrtc::LLWebRTCVoiceDeviceList &render_devices, const llwebrtc::LLWebRTCVoiceDeviceList &capture_devices) { + // [FIRE-36022] - Removing my USB headset crashes entire viewer + try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid + { + // Attempt to lock the access to the audio device, wait up to 1 second for other threads to unlock. + std::unique_lock lock(gAudioDeviceMutex, 1s); + // If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass. + // Prevents threads from interacting with the hardware at the same time as other audio/voice threads. + if (!lock.owns_lock()) + { + LL_INFOS() << "Could not access the audio device mutex, trying again later" << LL_ENDL; + return; + } + // [FIRE-36022] if (sShuttingDown) { return; @@ -774,6 +816,34 @@ void LLWebRTCVoiceClient::OnDevicesChangedImpl(const llwebrtc::LLWebRTCVoiceDevi } setDevicesListUpdated(true); + // [FIRE-36022] - Removing my USB headset crashes entire viewer + } + catch (const std::system_error& e) + { + if (e.code() == std::errc::resource_deadlock_would_occur) + { + // When trying to lock the same lock a second time + LL_WARNS() << "Exception WebRTC: " << e.code() << " " << e.what() << LL_ENDL; + } + else if (e.code() == std::errc::operation_not_permitted) + { + // When the mutex is invalid + LL_WARNS() << "Exception WebRTC: " << e.code() << " " << e.what() << LL_ENDL; + } + else + { + // Everything else + LL_WARNS() << "Exception WebRTC: " << e.code() << " " << e.what() << LL_ENDL; + } + + return; + } + catch (const std::exception& e) + { + LL_WARNS() << "Exception WebRTC: " << " " << e.what() << LL_ENDL; + return; + } + // [FIRE-36022] } void LLWebRTCVoiceClient::clearRenderDevices() @@ -848,6 +918,11 @@ void LLWebRTCVoiceClient::tuningSetSpeakerVolume(float volume) float LLWebRTCVoiceClient::tuningGetEnergy(void) { + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // This can cause an error if device interface can be NULL. + if (!mWebRTCDeviceInterface) + return 1.0f; + // [FIRE-36022] float rms = mWebRTCDeviceInterface->getTuningAudioLevel(); return TUNING_LEVEL_START_POINT - TUNING_LEVEL_SCALE * rms; }