Remove workaround to avoid race conditions

This commit is contained in:
WarmUpTill 2023-01-25 20:06:35 +01:00 committed by WarmUpTill
parent 5366a4a6ed
commit 4a3d019f06
6 changed files with 72 additions and 48 deletions

View File

@ -155,6 +155,7 @@ void SwitcherData::Thread()
while (true) { while (true) {
std::unique_lock<std::mutex> lock(m); std::unique_lock<std::mutex> lock(m);
mainLoopLock = &lock;
bool match = false; bool match = false;
OBSWeakSource scene; OBSWeakSource scene;
@ -230,19 +231,6 @@ void SwitcherData::Thread()
ClearWebsocketMessages(); ClearWebsocketMessages();
// After this point we will call frontend functions like
// obs_frontend_set_current_scene() and
// obs_frontend_set_current_transition()
//
// During this time SaveSceneSwitcher() could be called
// leading to a deadlock with the frontend function being stuck
// in QMetaObject::invokeMethod() holding the mutex and
// OBS being stuck in SaveSceneSwitcher().
//
// So we have to unlock() risking race conditions as these are
// less frequent than the above described deadlock
lock.unlock();
if (match) { if (match) {
if (macroMatch) { if (macroMatch) {
runMacros(); runMacros();

View File

@ -11,36 +11,39 @@ bool MacroActionSwitchScene::_registered = MacroActionFactory::Register(
{MacroActionSwitchScene::Create, MacroActionSwitchSceneEdit::Create, {MacroActionSwitchScene::Create, MacroActionSwitchSceneEdit::Create,
"AdvSceneSwitcher.action.switchScene"}); "AdvSceneSwitcher.action.switchScene"});
void waitForTransitionChange(OBSWeakSource &transition) static void waitForTransitionChange(OBSWeakSource &transition,
std::unique_lock<std::mutex> *lock,
Macro *macro)
{ {
const auto time = 100ms; const auto time = 100ms;
obs_source_t *source = obs_weak_source_get_source(transition); obs_source_t *source = obs_weak_source_get_source(transition);
std::unique_lock<std::mutex> lock(switcher->m);
bool stillTransitioning = true; bool stillTransitioning = true;
while (stillTransitioning && !switcher->abortMacroWait) { while (stillTransitioning && !switcher->abortMacroWait &&
switcher->macroTransitionCv.wait_for(lock, time); !macro->GetStop()) {
switcher->macroTransitionCv.wait_for(*lock, time);
float t = obs_transition_get_time(source); float t = obs_transition_get_time(source);
stillTransitioning = t < 1.0f && t > 0.0f; stillTransitioning = t < 1.0f && t > 0.0f;
} }
obs_source_release(source); obs_source_release(source);
} }
void waitForTransitionChangeFixedDuration(int duration) static void waitForTransitionChangeFixedDuration(
int duration, std::unique_lock<std::mutex> *lock, Macro *macro)
{ {
duration += 200; // It seems to be necessary to add a small buffer duration += 200; // It seems to be necessary to add a small buffer
auto time = std::chrono::high_resolution_clock::now() + auto time = std::chrono::high_resolution_clock::now() +
std::chrono::milliseconds(duration); std::chrono::milliseconds(duration);
std::unique_lock<std::mutex> lock(switcher->m); while (!switcher->abortMacroWait && !macro->GetStop()) {
while (!switcher->abortMacroWait) { if (switcher->macroTransitionCv.wait_until(*lock, time) ==
if (switcher->macroTransitionCv.wait_until(lock, time) ==
std::cv_status::timeout) { std::cv_status::timeout) {
break; break;
} }
} }
} }
int getTransitionOverrideDuration(OBSWeakSource &scene) static int getTransitionOverrideDuration(OBSWeakSource &scene)
{ {
int duration = 0; int duration = 0;
obs_source_t *source = obs_weak_source_get_source(scene); obs_source_t *source = obs_weak_source_get_source(scene);
@ -54,7 +57,7 @@ int getTransitionOverrideDuration(OBSWeakSource &scene)
return duration; return duration;
} }
bool isUsingFixedLengthTransition(const OBSWeakSource &transition) static bool isUsingFixedLengthTransition(const OBSWeakSource &transition)
{ {
obs_source_t *source = obs_weak_source_get_source(transition); obs_source_t *source = obs_weak_source_get_source(transition);
bool ret = obs_transition_fixed(source); bool ret = obs_transition_fixed(source);
@ -62,7 +65,7 @@ bool isUsingFixedLengthTransition(const OBSWeakSource &transition)
return ret; return ret;
} }
OBSWeakSource getOverrideTransition(OBSWeakSource &scene) static OBSWeakSource getOverrideTransition(OBSWeakSource &scene)
{ {
OBSWeakSource transition; OBSWeakSource transition;
obs_source_t *source = obs_weak_source_get_source(scene); obs_source_t *source = obs_weak_source_get_source(scene);
@ -74,8 +77,8 @@ OBSWeakSource getOverrideTransition(OBSWeakSource &scene)
return transition; return transition;
} }
int getExpectedTransitionDuration(OBSWeakSource &scene, OBSWeakSource &t, static int getExpectedTransitionDuration(OBSWeakSource &scene, OBSWeakSource &t,
double duration) double duration)
{ {
OBSWeakSource transition = t; OBSWeakSource transition = t;
if (!switcher->transitionOverrideOverride) { if (!switcher->transitionOverrideOverride) {
@ -96,6 +99,37 @@ int getExpectedTransitionDuration(OBSWeakSource &scene, OBSWeakSource &t,
return obs_frontend_get_transition_duration(); return obs_frontend_get_transition_duration();
} }
bool MacroActionSwitchScene::WaitForTransition(OBSWeakSource &scene,
OBSWeakSource &transition)
{
const int expectedTransitionDuration = getExpectedTransitionDuration(
scene, transition, _duration.seconds);
switcher->abortMacroWait = false;
bool isInMainLoop = QThread::currentThread() == switcher->th;
if (isInMainLoop) {
if (expectedTransitionDuration < 0) {
waitForTransitionChange(transition, switcher->GetLock(),
GetMacro());
} else {
waitForTransitionChangeFixedDuration(
expectedTransitionDuration, switcher->GetLock(),
GetMacro());
}
} else {
std::mutex temp;
std::unique_lock<std::mutex> lock(temp);
if (expectedTransitionDuration < 0) {
waitForTransitionChange(transition, &lock, GetMacro());
} else {
waitForTransitionChangeFixedDuration(
expectedTransitionDuration, &lock, GetMacro());
}
}
return !switcher->abortMacroWait;
}
bool MacroActionSwitchScene::PerformAction() bool MacroActionSwitchScene::PerformAction()
{ {
auto scene = _scene.GetScene(); auto scene = _scene.GetScene();
@ -103,17 +137,7 @@ bool MacroActionSwitchScene::PerformAction()
switchScene({scene, transition, (int)(_duration.seconds * 1000)}, switchScene({scene, transition, (int)(_duration.seconds * 1000)},
obs_frontend_preview_program_mode_active()); obs_frontend_preview_program_mode_active());
if (_blockUntilTransitionDone && scene) { if (_blockUntilTransitionDone && scene) {
const int expectedTransitionDuration = return WaitForTransition(scene, transition);
getExpectedTransitionDuration(scene, transition,
_duration.seconds);
switcher->abortMacroWait = false;
if (expectedTransitionDuration < 0) {
waitForTransitionChange(transition);
} else {
waitForTransitionChangeFixedDuration(
expectedTransitionDuration);
}
return !switcher->abortMacroWait;
} }
return true; return true;
} }

View File

@ -26,7 +26,7 @@ public:
bool _blockUntilTransitionDone = true; bool _blockUntilTransitionDone = true;
private: private:
const char *getType() { return "MacroActionSwitchScene"; } bool WaitForTransition(OBSWeakSource &scene, OBSWeakSource &transition);
static bool _registered; static bool _registered;
static const std::string id; static const std::string id;

View File

@ -19,6 +19,17 @@ static std::map<WaitType, std::string> waitTypes = {
static std::random_device rd; static std::random_device rd;
static std::default_random_engine re(rd()); static std::default_random_engine re(rd());
static void waitHelper(std::unique_lock<std::mutex> *lock, Macro *macro,
std::chrono::high_resolution_clock::time_point &time)
{
while (!switcher->abortMacroWait && !macro->GetStop()) {
if (switcher->macroWaitCv.wait_until(*lock, time) ==
std::cv_status::timeout) {
break;
}
}
}
bool MacroActionWait::PerformAction() bool MacroActionWait::PerformAction()
{ {
double sleepDuration; double sleepDuration;
@ -39,14 +50,15 @@ bool MacroActionWait::PerformAction()
auto time = std::chrono::high_resolution_clock::now() + auto time = std::chrono::high_resolution_clock::now() +
std::chrono::milliseconds((int)(sleepDuration * 1000)); std::chrono::milliseconds((int)(sleepDuration * 1000));
auto macro = GetMacro();
switcher->abortMacroWait = false; switcher->abortMacroWait = false;
std::unique_lock<std::mutex> lock(switcher->m); bool isInMainLoop = QThread::currentThread() == switcher->th;
while (!switcher->abortMacroWait && !macro->GetStop()) { if (isInMainLoop) {
if (switcher->macroWaitCv.wait_until(lock, time) == waitHelper(switcher->GetLock(), GetMacro(), time);
std::cv_status::timeout) { } else {
break; std::mutex temp;
} std::unique_lock<std::mutex> lock(temp);
waitHelper(&lock, GetMacro(), time);
} }
return !switcher->abortMacroWait; return !switcher->abortMacroWait;

View File

@ -741,9 +741,6 @@ bool SwitcherData::checkMacros()
bool SwitcherData::runMacros() bool SwitcherData::runMacros()
{ {
// TODO: Don't rely on creating a copy of each macro once new frontend
// events are available - see:
// https://github.com/obsproject/obs-studio/commit/feda1aaa283e8a99f6ba1159cfe6b9c1f2934a61
for (auto m : macros) { for (auto m : macros) {
if (m->Matched()) { if (m->Matched()) {
vblog(LOG_INFO, "running macro: %s", m->Name().c_str()); vblog(LOG_INFO, "running macro: %s", m->Name().c_str());

View File

@ -90,6 +90,8 @@ struct SwitcherData {
std::condition_variable cv; std::condition_variable cv;
std::mutex m; std::mutex m;
std::unique_lock<std::mutex> *mainLoopLock;
bool transitionActive = false; bool transitionActive = false;
bool waitForTransition = false; bool waitForTransition = false;
bool stop = false; bool stop = false;
@ -251,6 +253,7 @@ struct SwitcherData {
void Thread(); void Thread();
void Start(); void Start();
void Stop(); void Stop();
std::unique_lock<std::mutex> *GetLock() { return mainLoopLock; }
void setWaitScene(); void setWaitScene();
bool sceneChangedDuringWait(); bool sceneChangedDuringWait();