From beaffdda716e2063d1112cb09956d44d948f40b5 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 30 Dec 2022 20:56:37 -0800 Subject: Use a simple spinlock to protect the current global context This will be much for efficient than a recursive mutex, given the amount of contention will be very low. --- alc/alc.cpp | 24 +++++++++++++++++------- alc/context.cpp | 8 ++++++++ alc/context.h | 1 + 3 files changed, 26 insertions(+), 7 deletions(-) (limited to 'alc') diff --git a/alc/alc.cpp b/alc/alc.cpp index 401a2ab6..db862a36 100644 --- a/alc/alc.cpp +++ b/alc/alc.cpp @@ -2492,9 +2492,14 @@ ContextRef GetContextRef(void) context->add_ref(); else { - std::lock_guard _{ListLock}; + while(ALCcontext::sGlobalContextLock.exchange(true, std::memory_order_acquire)) { + /* Wait to make sure another thread isn't trying to change the + * current context and bring its refcount to 0. + */ + } context = ALCcontext::sGlobalContext.load(std::memory_order_acquire); - if(context) context->add_ref(); + if(context) [[likely]] context->add_ref(); + ALCcontext::sGlobalContextLock.store(false, std::memory_order_release); } return ContextRef{context}; } @@ -3385,13 +3390,18 @@ START_API_FUNC } /* Release this reference (if any) to store it in the GlobalContext * pointer. Take ownership of the reference (if any) that was previously - * stored there. + * stored there, and let the reference go. */ - ctx = ContextRef{ALCcontext::sGlobalContext.exchange(ctx.release())}; + while(ALCcontext::sGlobalContextLock.exchange(true, std::memory_order_acquire)) { + /* Wait to make sure another thread isn't getting or trying to change + * the current context as its refcount is decremented. + */ + } + ContextRef{ALCcontext::sGlobalContext.exchange(ctx.release())}; + ALCcontext::sGlobalContextLock.store(false, std::memory_order_release); - /* Reset (decrement) the previous global reference by replacing it with the - * thread-local context. Take ownership of the thread-local context - * reference (if any), clearing the storage to null. + /* Take ownership of the thread-local context reference (if any), clearing + * the storage to null. */ ctx = ContextRef{ALCcontext::getThreadContext()}; if(ctx) ALCcontext::setThreadContext(nullptr); diff --git a/alc/context.cpp b/alc/context.cpp index 906a160e..f9aec221 100644 --- a/alc/context.cpp +++ b/alc/context.cpp @@ -84,6 +84,7 @@ constexpr ALchar alExtList[] = } // namespace +std::atomic ALCcontext::sGlobalContextLock{false}; std::atomic ALCcontext::sGlobalContext{nullptr}; thread_local ALCcontext *ALCcontext::sLocalContext{nullptr}; @@ -203,7 +204,14 @@ bool ALCcontext::deinit() ALCcontext *origctx{this}; if(sGlobalContext.compare_exchange_strong(origctx, nullptr)) + { + while(sGlobalContextLock.load()) { + /* Wait to make sure another thread didn't get the context and is + * trying to increment its refcount. + */ + } dec_ref(); + } bool ret{}; /* First make sure this context exists in the device's list. */ diff --git a/alc/context.h b/alc/context.h index 58a70184..d93d63d6 100644 --- a/alc/context.h +++ b/alc/context.h @@ -148,6 +148,7 @@ struct ALCcontext : public al::intrusive_ref, ContextBase { void setError(ALenum errorCode, const char *msg, ...); /* Process-wide current context */ + static std::atomic sGlobalContextLock; static std::atomic sGlobalContext; private: -- cgit v1.2.3