diff --git a/obs-studio-server/source/osn-source.cpp b/obs-studio-server/source/osn-source.cpp index 48ce2a20c..f4dda8525 100644 --- a/obs-studio-server/source/osn-source.cpp +++ b/obs-studio-server/source/osn-source.cpp @@ -250,8 +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. - obs_source_t *src = osn::Source::Manager::GetInstance().find(args[0].value_union.ui64); + // 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) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Source reference is not valid."); } diff --git a/tests/osn-tests/src/test_osn_source.ts b/tests/osn-tests/src/test_osn_source.ts index fbbac72c0..a5d0a8421 100644 --- a/tests/osn-tests/src/test_osn_source.ts +++ b/tests/osn-tests/src/test_osn_source.ts @@ -591,6 +591,37 @@ 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); diff --git a/tests/osn-tests/util/obs_handler.ts b/tests/osn-tests/util/obs_handler.ts index a3b8de811..078a5e373 100644 --- a/tests/osn-tests/util/obs_handler.ts +++ b/tests/osn-tests/util/obs_handler.ts @@ -527,7 +527,7 @@ export class OBSHandler { } skipSource(inputType: string) { - if (process.platform === 'darwin') { + if (process.platform === 'darwin' && this.isCI()) { if (inputType === 'browser_source' || inputType === 'window_capture' || inputType === 'monitor_capture' ||