diff --git a/obs-studio-server/source/osn-source.cpp b/obs-studio-server/source/osn-source.cpp index f4dda8525..4cd249ce9 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."); } diff --git a/obs-studio-server/source/osn-source.hpp b/obs-studio-server/source/osn-source.hpp index 3a302ee56..0f2b4f99c 100644 --- a/obs-studio-server/source/osn-source.hpp +++ b/obs-studio-server/source/osn-source.hpp @@ -19,6 +19,7 @@ #pragma once #include #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);