From e0d9588ba9e35da010987298399ada10a5743483 Mon Sep 17 00:00:00 2001 From: Richard Osborne Date: Fri, 22 May 2026 14:20:12 -0500 Subject: [PATCH 1/2] add strong ref to resolve race condition * there was a narrow window between calling find() and OBSSource where a race condition could still happen triggering a use-and-free situation. * add findAndRef to resolve this crash dump. * remove the js test it does not trigger the issue. In another PR, it properly spins up parallel threads to validate the fix. --- obs-studio-server/source/osn-source.cpp | 11 ++++----- obs-studio-server/source/osn-source.hpp | 13 +++++++++++ tests/osn-tests/src/test_osn_source.ts | 31 ------------------------- 3 files changed, 18 insertions(+), 37 deletions(-) diff --git a/obs-studio-server/source/osn-source.cpp b/obs-studio-server/source/osn-source.cpp index f4dda8525..3d6db1b43 100644 --- a/obs-studio-server/source/osn-source.cpp +++ b/obs-studio-server/source/osn-source.cpp @@ -250,11 +250,10 @@ void osn::Source::IsConfigurable(void *data, const int64_t id, const std::vector void osn::Source::GetProperties(void *data, const int64_t id, const std::vector &args, std::vector &rval) { - // Attempt to find the source asked to load. Get a strong reference to help - // guarantee the source is not destroyed while we are attempting to retrieve - // properties. - OBSSource src = osn::Source::Manager::GetInstance().find(args[0].value_union.ui64); - if (src == nullptr) { + // Atomically find and acquire a strong reference under the manager lock, + // preventing the source from being destroyed between find() and obs_source_get_ref(). + OBSSourceAutoRelease src = osn::Source::Manager::GetInstance().findAndRef(args[0].value_union.ui64); + if (!src) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Source reference is not valid."); } @@ -483,7 +482,7 @@ void osn::Source::GetStatus(void *data, const int64_t id, const std::vector #include +#include #include "utility.hpp" #undef strtoll #include "nlohmann/json.hpp" @@ -39,6 +40,18 @@ class Source { public: static Manager &GetInstance(); + + // Atomically finds the source and acquires a strong reference under the + // manager lock, preventing destruction between find() and obs_source_get_ref(). + // Returns null (as OBSSourceAutoRelease) if not found or already destroyed. + OBSSourceAutoRelease findAndRef(utility::unique_id::id_t id) + { + std::lock_guard lock(internal_mutex); + auto iter = object_map.find(id); + if (iter == object_map.end()) + return nullptr; + return obs_source_get_ref(iter->second); + } }; static void initialize_global_signals(); diff --git a/tests/osn-tests/src/test_osn_source.ts b/tests/osn-tests/src/test_osn_source.ts index a5d0a8421..fbbac72c0 100644 --- a/tests/osn-tests/src/test_osn_source.ts +++ b/tests/osn-tests/src/test_osn_source.ts @@ -591,37 +591,6 @@ describe(testName, () => { }); }); - it('Get properties of browser source while releasing concurrently does not crash', async function() { - if (obs.skipSource(EOBSInputTypes.BrowserSource)) { this.skip(); } // Skip if browser source is not supported - const iterations = 20; - const promises: Promise[] = []; - - for (let i = 0; i < iterations; i++) { - const input = osn.InputFactory.create(EOBSInputTypes.BrowserSource, 'browser_concurrent_' + i); - expect(input).to.not.equal(undefined, GetErrorMessage(ETestErrorMsg.CreateInput, EOBSInputTypes.BrowserSource)); - - // Race getProperties against release on separate async ticks - const getProps = new Promise(resolve => { - setImmediate(() => { - try { input.properties; } catch (_) {} - resolve(); - }); - }); - - const releaseSource = new Promise(resolve => { - setImmediate(() => { - try { input.release(); } catch (_) {} - resolve(); - }); - }); - - promises.push(getProps, releaseSource); - } - - // If the race condition is present, one of these will crash the OBS server process - await Promise.all(promises); - }); - it('Set enabled and get it for all filter types', () => { obs.filterTypes.forEach(function(filterType) { logInfo(testName, 'Testing filter type: ' + filterType); From 1a83a001172ecddb4d1db4c1908887df0de66da1 Mon Sep 17 00:00:00 2001 From: Richard Osborne Date: Fri, 22 May 2026 14:27:40 -0500 Subject: [PATCH 2/2] fix clang-format error --- obs-studio-server/source/osn-source.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obs-studio-server/source/osn-source.cpp b/obs-studio-server/source/osn-source.cpp index 3d6db1b43..4cd249ce9 100644 --- a/obs-studio-server/source/osn-source.cpp +++ b/obs-studio-server/source/osn-source.cpp @@ -482,7 +482,7 @@ void osn::Source::GetStatus(void *data, const int64_t id, const std::vector