diff --git a/src/lib/app/RvApp/RvSession.cpp b/src/lib/app/RvApp/RvSession.cpp index 526e696fe..42de5a048 100644 --- a/src/lib/app/RvApp/RvSession.cpp +++ b/src/lib/app/RvApp/RvSession.cpp @@ -6,6 +6,7 @@ // // #include +#include #include #include #include @@ -243,6 +244,22 @@ namespace Rv // new Shader::Function -> initGLSLVersion() -> glGetString // -> crashes because no context IPCore::Application::instance()->loadOptionNodeDefinitions(); + + // Set TwkMovie's blocking callbacks to release (and re-acquire) the Python GIL while it waits for its + // plugins to initialize in separate threads. If any of them need Python, they will try to acquire the + // GIL and deadlock with the main thread. + // + thread_local PyThreadState* preloadGilState = nullptr; + TwkMovie::GenericIO::setBlockingWaitCallbacks([]() { preloadGilState = PyGILState_Check() ? PyEval_SaveThread() : nullptr; }, + []() + { + if (preloadGilState) + { + PyEval_RestoreThread(preloadGilState); + preloadGilState = nullptr; + } + }); + firstTime = false; } diff --git a/src/lib/image/TwkMovie/MovieIO.cpp b/src/lib/image/TwkMovie/MovieIO.cpp index 1766188da..eebb1c904 100644 --- a/src/lib/image/TwkMovie/MovieIO.cpp +++ b/src/lib/image/TwkMovie/MovieIO.cpp @@ -212,8 +212,14 @@ namespace TwkMovie if (reader->isFinishedLoading()) return; + if (GenericIO::m_onBlockingWaitRelease) + GenericIO::m_onBlockingWaitRelease(); + std::unique_lock lock(m_mainThread_mutex); m_mainThread_cv.wait(lock, [&reader] { return reader->isFinishedLoading(); }); + + if (GenericIO::m_onBlockingWaitAcquire) + GenericIO::m_onBlockingWaitAcquire(); } MovieReader* GenericIO::Preloader::getReader(std::string_view filename, const MovieInfo& info, Movie::ReadRequest& request) @@ -534,6 +540,14 @@ namespace TwkMovie bool GenericIO::m_loadedAll = false; bool GenericIO::m_dnxhdDecodingAllowed = true; GenericIO::Preloader GenericIO::m_preloader; + GenericIO::WaitCallback GenericIO::m_onBlockingWaitRelease; + GenericIO::WaitCallback GenericIO::m_onBlockingWaitAcquire; + + void GenericIO::setBlockingWaitCallbacks(WaitCallback onRelease, WaitCallback onAcquire) + { + m_onBlockingWaitRelease = std::move(onRelease); + m_onBlockingWaitAcquire = std::move(onAcquire); + } void GenericIO::init() { diff --git a/src/lib/image/TwkMovie/TwkMovie/MovieIO.h b/src/lib/image/TwkMovie/TwkMovie/MovieIO.h index 698aec93e..1d8b58cb1 100644 --- a/src/lib/image/TwkMovie/TwkMovie/MovieIO.h +++ b/src/lib/image/TwkMovie/TwkMovie/MovieIO.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -413,6 +414,21 @@ namespace TwkMovie static void init(); + /// + /// Optional callbacks invoked in Preloader::waitForFinishedLoading + /// immediately before and after the blocking condition variable wait. + /// + /// Applications that hold resouces they shared with workers (e.g. Python's + /// GIL) should set these to release their lock before the wait and reacquire + /// it afterward, so that background threads can avoid deadlocking the main thread + /// when they access the shared resources. + /// + /// Both callbacks default to nullptr (no-op). + /// + + using WaitCallback = std::function; + static void setBlockingWaitCallbacks(WaitCallback onRelease, WaitCallback onAcquire); + /// /// Shutdown and free generic movieio plugins. /// This needs to be called in the application main thread in main. @@ -539,6 +555,8 @@ namespace TwkMovie static bool m_loadedAll; static bool m_dnxhdDecodingAllowed; static Preloader m_preloader; + static WaitCallback m_onBlockingWaitRelease; + static WaitCallback m_onBlockingWaitAcquire; }; } // namespace TwkMovie