Repository navigation
Conversation
The audio thread checked the plugin's own output mixers on every callback and, when the input track was among them, wrote them back without it. When the outputs changed between that read and the write, the write stored the old value and the change was lost. - A handler on the plugin's own audio_mixers signal removes the input track from the value being set, so libobs never stores it, whoever sets the outputs. - A new input track is removed from the outputs before the plugin connects to it. - The audio thread's check stays, for a set that another thread made with the old input track and that is stored after the input track has changed. - The plugin's own writes (that check, and the removal on an input change) take the track out of the outputs the latest audio_mixers signal handed to libobs rather than out of the value they read, so a set made in between is kept. - A track number out of range leaves the outputs unchanged.
semnil
force-pushed
the
fix/loopback-guard-race
branch
from
October 25, 2026 05:29
b709530 to
d6ccb3b
Compare
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
The plugin keeps its input mix track out of its own outputs from the audio thread: every callback reads the plugin's
audio_mixersand, when the input track is among them, writes them back without it. That write is a read-modify-write. When the outputs change between the read and the write, for example when the user checks another track in Advanced Audio Properties, the write stores the value it read minus the input track, and the user's change is lost.Until the audio thread's next callback removes it, the input track also stays stored: after the user checks the input track as an output, after a collection loads saved outputs that include it, or after the input moves to a track that is an output. A handler of the
audio_mixerssignal sees the input track in the meantime.Fix
audio_mixerssignal removes the input track from the value being set. libobs stores the value the signal returns (obs_source_set_audio_mixersinlibobs/obs-source.c), so the input track is never stored, whoever sets the outputs.THREAD_LOCALvalue (libobs'sutil/threading.h), and the handler replaces the value with the outputs the latestaudio_mixerssignal handed to libobs, minus the track being removed. Signals run one at a time under the signal's lock, so a set handled before the plugin's write is kept.Impact
mt2s_create,mt2s_update,mt2s_avoid_loopback) and adds its ownown_mixersfield, recorded in its own handler; whichever of the two merges second needs the two handlers merged into one.signal_handler_signalinlibobs/callback/signal.c,obs_source_set_audio_mixers). No callback runs there, and any two writers share them (the frontend and obs-websocket, for example).Verification
A test program built against this file and run on libobs 31.1.1 and 33.0.0 (the latter from OBS.app) drives the plugin with libobs's real audio thread. It wraps the plugin's own setter calls to count the ones made off the main thread, and can hold the first of them after the plugin has read the value it writes from.
master(aff14c8)audio_mixersends at 32 (expected 48); 1 audio-thread write-fsanitize=undefinedon 31.1.1. Its checks include the held audio-thread check while the user sets tracks 5 and 6, every output mask on every input track (stored value and the value a later handler sees), every input-track change with outputs 63 (outputs when the plugin connects and afterwards), loading and saving with every input track, out-of-range input tracks, and the waveform, frame count and timestamp the plugin passes on.audio_mixerssignal for a source after its destroy callback and frees the source's signal handler with it.src/plugin-main.ccompiles for arm64 and x86_64 under-Werrorwith the project's warning flags and no warnings; onmasterthe same flags report a-Wshorten-64-to-32warning inmt2s_avoid_loopback, which this change removes. clang-format 23 reports no changes for this file and formaster's, which passes CI's clang-format 19.THREAD_LOCALon MSVC) and the clang-format 19 check pass.