diff --git a/libs/image/3rdparty/lock_free_map/qsbr.h b/libs/image/3rdparty/lock_free_map/qsbr.h index cc711987db..6acc6b8440 100644 --- a/libs/image/3rdparty/lock_free_map/qsbr.h +++ b/libs/image/3rdparty/lock_free_map/qsbr.h @@ -1,123 +1,130 @@ /*------------------------------------------------------------------------ Junction: Concurrent data structures in C++ Copyright (c) 2016 Jeff Preshing Distributed under the Simplified BSD License. Original location: https://github.com/preshing/junction This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the LICENSE file for more information. ------------------------------------------------------------------------*/ #ifndef QSBR_H #define QSBR_H #include #include #include #include #define CALL_MEMBER(obj, pmf) ((obj).*(pmf)) class QSBR { private: struct Action { void (*func)(void*); quint64 param[4]; // Size limit found experimentally. Verified by assert below. Action() = default; Action(void (*f)(void*), void* p, quint64 paramSize) : func(f) { KIS_ASSERT(paramSize <= sizeof(param)); // Verify size limit. memcpy(¶m, p, paramSize); } void operator()() { func(¶m); } }; QAtomicInt m_rawPointerUsers; KisLocklessStack m_pendingActions; KisLocklessStack m_migrationReclaimActions; std::atomic_flag m_isProcessing = ATOMIC_FLAG_INIT; void releasePoolSafely(KisLocklessStack *pool) { Action action; while (pool->pop(action)) { action(); } } public: template void enqueue(void (T::*pmf)(), T* target, bool migration = false) { struct Closure { void (T::*pmf)(); T* target; static void thunk(void* param) { Closure* self = (Closure*) param; CALL_MEMBER(*self->target, self->pmf)(); } }; Closure closure = {pmf, target}; if (migration) { m_migrationReclaimActions.push(Action(Closure::thunk, &closure, sizeof(closure))); } else { m_pendingActions.push(Action(Closure::thunk, &closure, sizeof(closure))); } } void update(bool migrationInProgress) { if (m_rawPointerUsers.testAndSetAcquire(0, 1)) { + + /// TODO: theoretically, there is a race condition: + /// if the user's code enters critical section and + /// releases an object *after* we started purging + /// garbage. In such a case we can free still used + /// tile. I didn't check this hypothesis yet though. + releasePoolSafely(&m_pendingActions); if (!migrationInProgress) { releasePoolSafely(&m_migrationReclaimActions); } m_rawPointerUsers.deref(); } else if (m_pendingActions.size() > 4098) { // TODO: make pool size limit configurable! while (!m_rawPointerUsers.testAndSetAcquire(0, 1)); releasePoolSafely(&m_pendingActions); m_rawPointerUsers.deref(); } } void flush() { while (!m_rawPointerUsers.testAndSetAcquire(0, 1)); releasePoolSafely(&m_pendingActions); releasePoolSafely(&m_migrationReclaimActions); m_rawPointerUsers.deref(); } void lockRawPointerAccess() { m_rawPointerUsers.ref(); } void unlockRawPointerAccess() { m_rawPointerUsers.deref(); } }; #endif // QSBR_H