Fix volmeter teardown deadlock between audio thread and IPC - #1718
Merged
Conversation
OBSCallback runs while libobs holds the volmeter callback_mutex, then takes
the osn global mtx (and the manager's internal_mutex via find). The teardown
paths acquire those osn locks first and then take callback_mutex:
- Destroy holds mtx, then obs_volmeter_remove_callback -> callback_mutex
- ClearVolmeters' for_each holds internal_mutex, then
obs_volmeter_remove_callback -> callback_mutex
This is two AB-BA cycles (callback_mutex<->mtx, callback_mutex<->internal_mutex)
that can hang on shutdown while audio is still flowing.
- OBSCallback now try_locks mtx and drops the update if busy, so the audio
thread never blocks on mtx while holding callback_mutex.
- ClearVolmeters collects the volmeters under the manager lock, then removes
callbacks and clears outside for_each, so callback_mutex is never taken while
the manager lock is held.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR addresses a shutdown/teardown deadlock involving libobs volmeter callback_mutex and osn-side locks by changing lock acquisition behavior in the volmeter callback and adjusting teardown callback removal to avoid holding manager locks while touching libobs.
Changes:
- Update
osn::Volmeter::OBSCallbacktotry_lockthe globalmtxand drop a meter update frame if it can’t be acquired, preventing the audio thread from blocking whilecallback_mutexis held. - Rework
osn::Volmeter::ClearVolmetersto collect active volmeters under the manager lock, then remove callbacks outside the managerfor_eachto avoid lock-order inversion with libobs.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
aleksandr-voitenko
approved these changes
Jun 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
osn::Volmeter::OBSCallbackis invoked by libobs while the volmetercallback_mutexis held (signal_levels_updatedcalls it inside that lock). It then acquires the osn globalmtxand, viaManager::find, the manager'sinternal_mutex.The teardown paths take those osn locks first and then
callback_mutex:Destroyholdsmtx→obs_volmeter_remove_callback→callback_mutexClearVolmeters'for_eachholdsinternal_mutex→obs_volmeter_remove_callback→callback_mutexThat's two AB-BA cycles:
callback_mutex ↔ mtx(audio thread vsDestroy)callback_mutex ↔ internal_mutex(audio thread vsClearVolmeters)Both can hang on the shutdown path (
OBS_API::destroyOBS_API→ClearVolmeters) while audio capture threads are still delivering audio.Fix (osn-only, no libobs changes)
OBSCallbackacquiresmtxwithstd::try_to_lockand drops the update if it can't — the audio thread never blocks onmtxwhile holdingcallback_mutex. Volmeter updates are periodic and lossy, so a dropped frame during teardown is harmless. Breaks bothcallback_mutex ↔ mtxcycles.ClearVolmeterscollects the volmeters under the manager lock, then removes callbacks / clears outsidefor_each, socallback_mutexis never taken while the manager lock is held. Breaks thecallback_mutex ↔ internal_mutexcycle.shared_ptrownership already prevents use-after-free across these paths; this PR only fixes the lock ordering.Relationship to #1717
Independent and complementary. #1717 fixes the volmeter use-after-free (lifetime); this fixes the teardown deadlock (lock ordering). They touch different functions in the same file (
Attach/Detach/header member vsOBSCallback/ClearVolmeters) and merge tostagingwithout conflict.