Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions obs-studio-server/source/osn-source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ipc::value> &args, std::vector<ipc::value> &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.");
}

Expand Down
13 changes: 13 additions & 0 deletions obs-studio-server/source/osn-source.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#pragma once
#include <ipc-server.hpp>
#include <obs.h>
#include <obs.hpp>
#include "utility.hpp"
#undef strtoll
#include "nlohmann/json.hpp"
Expand All @@ -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<std::recursive_mutex> 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();
Expand Down
31 changes: 0 additions & 31 deletions tests/osn-tests/src/test_osn_source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>[] = [];

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<void>(resolve => {
setImmediate(() => {
try { input.properties; } catch (_) {}
resolve();
});
});

const releaseSource = new Promise<void>(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', () => {
Comment thread
sandboxcoder marked this conversation as resolved.
obs.filterTypes.forEach(function(filterType) {
logInfo(testName, 'Testing filter type: ' + filterType);
Expand Down
Loading