Replaced TransientContextLock implementation with a more elaborate one which relies on locking a single mutex and thus avoids lock order inversion. Fixes #1165.

This commit is contained in:
binary1248 2016-11-27 18:31:21 +01:00
parent 022f1590d8
commit af5244d85d
4 changed files with 164 additions and 128 deletions

View File

@ -75,9 +75,6 @@ protected:
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
~TransientContextLock(); ~TransientContextLock();
private:
Context* m_context; ///< Temporary context, in case we needed to create one
}; };
}; };

View File

@ -26,6 +26,7 @@
// Headers // Headers
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
#include <SFML/Window/GlContext.hpp> #include <SFML/Window/GlContext.hpp>
#include <SFML/Window/Context.hpp>
#include <SFML/System/ThreadLocalPtr.hpp> #include <SFML/System/ThreadLocalPtr.hpp>
#include <SFML/System/Mutex.hpp> #include <SFML/System/Mutex.hpp>
#include <SFML/System/Lock.hpp> #include <SFML/System/Lock.hpp>
@ -131,18 +132,70 @@ namespace
// We need to make sure that no operating system context // We need to make sure that no operating system context
// or pixel format operations are performed simultaneously // or pixel format operations are performed simultaneously
// This mutex is also used to protect the shared context // This mutex is also used to protect the shared context
// from being locked on multiple threads // from being locked on multiple threads and for managing
// the resource count
sf::Mutex mutex; sf::Mutex mutex;
// OpenGL resources counter
unsigned int resourceCount = 0;
// This per-thread variable holds the current context for each thread // This per-thread variable holds the current context for each thread
sf::ThreadLocalPtr<sf::priv::GlContext> currentContext(NULL); sf::ThreadLocalPtr<sf::priv::GlContext> currentContext(NULL);
// The hidden, inactive context that will be shared with all other contexts // The hidden, inactive context that will be shared with all other contexts
ContextType* sharedContext = NULL; ContextType* sharedContext = NULL;
// This per-thread variable is set to point to the shared context // This structure contains all the state necessary to
// if we had to acquire it when a TransientContextLock was required // track TransientContext usage
sf::ThreadLocalPtr<sf::priv::GlContext> currentSharedContext(NULL); struct TransientContext : private sf::NonCopyable
{
////////////////////////////////////////////////////////////
/// \brief Constructor
///
////////////////////////////////////////////////////////////
TransientContext() :
referenceCount (0),
context (0),
sharedContextLock(0),
useSharedContext (false)
{
if (resourceCount == 0)
{
context = new sf::Context;
}
else if (!currentContext)
{
sharedContextLock = new sf::Lock(mutex);
useSharedContext = true;
sharedContext->setActive(true);
}
}
////////////////////////////////////////////////////////////
/// \brief Destructor
///
////////////////////////////////////////////////////////////
~TransientContext()
{
if (useSharedContext)
sharedContext->setActive(false);
delete sharedContextLock;
delete context;
}
///////////////////////////////////////////////////////////
// Member data
////////////////////////////////////////////////////////////
unsigned int referenceCount;
sf::Context* context;
sf::Lock* sharedContextLock;
bool useSharedContext;
};
// This per-thread variable tracks if and how a transient
// context is currently being used on the current thread
sf::ThreadLocalPtr<TransientContext> transientContext(NULL);
// Supported OpenGL extensions // Supported OpenGL extensions
std::vector<std::string> extensions; std::vector<std::string> extensions;
@ -154,12 +207,21 @@ namespace sf
namespace priv namespace priv
{ {
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void GlContext::globalInit() void GlContext::initResource()
{ {
// Protect from concurrent access
Lock lock(mutex); Lock lock(mutex);
// If this is the very first resource, trigger the global context initialization
if (resourceCount == 0)
{
if (sharedContext) if (sharedContext)
{
// Increment the resources counter
resourceCount++;
return; return;
}
// Create the shared context // Create the shared context
sharedContext = new ContextType(NULL); sharedContext = new ContextType(NULL);
@ -213,48 +275,70 @@ void GlContext::globalInit()
// Deactivate the shared context so that others can activate it when necessary // Deactivate the shared context so that others can activate it when necessary
sharedContext->setActive(false); sharedContext->setActive(false);
}
// Increment the resources counter
resourceCount++;
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void GlContext::globalCleanup() void GlContext::cleanupResource()
{ {
// Protect from concurrent access
Lock lock(mutex); Lock lock(mutex);
// Decrement the resources counter
resourceCount--;
// If there's no more resource alive, we can trigger the global context cleanup
if (resourceCount == 0)
{
if (!sharedContext) if (!sharedContext)
return; return;
// Destroy the shared context // Destroy the shared context
delete sharedContext; delete sharedContext;
sharedContext = NULL; sharedContext = NULL;
}
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void GlContext::acquireTransientContext() void GlContext::acquireTransientContext()
{ {
// If a capable context is already active on this thread // Protect from concurrent access
// there is no need to use the shared context for the operation Lock lock(mutex);
if (currentContext)
{
currentSharedContext = NULL;
return;
}
mutex.lock(); // If this is the first TransientContextLock on this thread
currentSharedContext = sharedContext; // construct the state object
sharedContext->setActive(true); if (!transientContext)
transientContext = new TransientContext;
// Increase the reference count
transientContext->referenceCount++;
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void GlContext::releaseTransientContext() void GlContext::releaseTransientContext()
{ {
if (!currentSharedContext) // Protect from concurrent access
return; Lock lock(mutex);
sharedContext->setActive(false); // Make sure a matching acquireTransientContext() was called
mutex.unlock(); assert(transientContext);
// Decrease the reference count
transientContext->referenceCount--;
// If this is the last TransientContextLock that is released
// destroy the state object
if (transientContext->referenceCount == 0)
{
delete transientContext;
transientContext = NULL;
}
} }

View File

@ -49,28 +49,25 @@ class GlContext : NonCopyable
public: public:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Perform the global initialization /// \brief Perform resource initialization
/// ///
/// This function is called once, before the very first OpenGL /// This function is called every time an OpenGL resource is
/// resource is created. It makes sure that everything is ready /// created. When the first resource is initialized, it makes
/// for contexts to work properly. /// sure that everything is ready for contexts to work properly.
/// Note: this function doesn't need to be thread-safe, as it
/// can be called only once.
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
static void globalInit(); static void initResource();
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Perform the global cleanup /// \brief Perform resource cleanup
/// ///
/// This function is called after the very last OpenGL resource /// This function is called every time an OpenGL resource is
/// is destroyed. It makes sure that everything that was /// destroyed. When the last resource is destroyed, it makes
/// created by initialize() is properly released. /// sure that everything that was created by initResource()
/// Note: this function doesn't need to be thread-safe, as it /// is properly released.
/// can be called only once.
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
static void globalCleanup(); static void cleanupResource();
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Acquires a context for short-term use on the current thread /// \brief Acquires a context for short-term use on the current thread

View File

@ -27,17 +27,6 @@
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
#include <SFML/Window/GlResource.hpp> #include <SFML/Window/GlResource.hpp>
#include <SFML/Window/GlContext.hpp> #include <SFML/Window/GlContext.hpp>
#include <SFML/Window/Context.hpp>
#include <SFML/System/Mutex.hpp>
#include <SFML/System/Lock.hpp>
namespace
{
// OpenGL resources counter and its mutex
unsigned int count = 0;
sf::Mutex mutex;
}
namespace sf namespace sf
@ -45,45 +34,20 @@ namespace sf
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
GlResource::GlResource() GlResource::GlResource()
{ {
// Protect from concurrent access priv::GlContext::initResource();
Lock lock(mutex);
// If this is the very first resource, trigger the global context initialization
if (count == 0)
priv::GlContext::globalInit();
// Increment the resources counter
count++;
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
GlResource::~GlResource() GlResource::~GlResource()
{ {
// Protect from concurrent access priv::GlContext::cleanupResource();
Lock lock(mutex);
// Decrement the resources counter
count--;
// If there's no more resource alive, we can trigger the global context cleanup
if (count == 0)
priv::GlContext::globalCleanup();
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
GlResource::TransientContextLock::TransientContextLock() : GlResource::TransientContextLock::TransientContextLock()
m_context(0)
{ {
Lock lock(mutex);
if (count == 0)
{
m_context = new Context;
return;
}
priv::GlContext::acquireTransientContext(); priv::GlContext::acquireTransientContext();
} }
@ -91,12 +55,6 @@ m_context(0)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
GlResource::TransientContextLock::~TransientContextLock() GlResource::TransientContextLock::~TransientContextLock()
{ {
if (m_context)
{
delete m_context;
return;
}
priv::GlContext::releaseTransientContext(); priv::GlContext::releaseTransientContext();
} }