fix event pool race condition in a hacky way

This commit is contained in:
altalk23 2024-06-20 16:17:02 +03:00
parent eb233f8482
commit 636be07435
3 changed files with 69 additions and 23 deletions
loader
include/Geode/loader
src/loader

View file

@ -31,7 +31,7 @@ namespace geode {
EventListenerPool* getPool() const override {
if (dispatchPools().count(m_id) == 0) {
dispatchPools()[m_id] = new DefaultEventListenerPool();
dispatchPools()[m_id] = DefaultEventListenerPool::create();
}
return dispatchPools()[m_id];
}
@ -48,7 +48,7 @@ namespace geode {
EventListenerPool* getPool() const {
if (dispatchPools().count(m_id) == 0) {
dispatchPools()[m_id] = new DefaultEventListenerPool();
dispatchPools()[m_id] = DefaultEventListenerPool::create();
}
return dispatchPools()[m_id];
}

View file

@ -5,6 +5,8 @@
#include <Geode/DefaultInclude.hpp>
#include <type_traits>
#include <mutex>
#include <deque>
#include <unordered_set>
namespace geode {
@ -29,12 +31,29 @@ namespace geode {
EventListenerPool(EventListenerPool const&) = delete;
EventListenerPool(EventListenerPool&&) = delete;
};
template <class... Args>
class DispatchEvent;
template <class... Args>
class DispatchFilter;
class GEODE_DLL DefaultEventListenerPool : public EventListenerPool {
protected:
std::atomic_size_t m_locked = 0;
std::vector<EventListenerProtocol*> m_listeners;
std::vector<EventListenerProtocol*> m_toAdd;
// fix this in Geode 4.0.0
struct Data {
std::atomic_size_t m_locked = 0;
std::mutex m_mutex;
std::deque<EventListenerProtocol*> m_listeners;
std::vector<EventListenerProtocol*> m_toAdd;
std::vector<EventListenerProtocol*> m_toRemove;
};
std::unique_ptr<Data> m_data;
DefaultEventListenerPool();
private:
static DefaultEventListenerPool* create();
public:
bool add(EventListenerProtocol* listener) override;
@ -42,6 +61,12 @@ namespace geode {
ListenerResult handle(Event* event) override;
static DefaultEventListenerPool* get();
template <class... Args>
friend class DispatchEvent;
template <class... Args>
friend class DispatchFilter;
};
class GEODE_DLL EventListenerProtocol {

View file

@ -4,50 +4,71 @@
using namespace geode::prelude;
DefaultEventListenerPool::DefaultEventListenerPool() : m_data(new Data) {}
bool DefaultEventListenerPool::add(EventListenerProtocol* listener) {
if (m_locked) {
m_toAdd.push_back(listener);
if (!m_data) m_data = std::make_unique<Data>();
std::unique_lock lock(m_data->m_mutex);
if (m_data->m_locked) {
m_data->m_toAdd.push_back(listener);
ranges::remove(m_data->m_toRemove, listener);
}
else {
// insert listeners at the start so new listeners get priority
m_listeners.insert(m_listeners.begin(), listener);
m_data->m_listeners.push_front(listener);
}
return true;
}
void DefaultEventListenerPool::remove(EventListenerProtocol* listener) {
for (size_t i = 0; i < m_listeners.size(); i++) {
if (m_listeners[i] == listener) {
m_listeners[i] = nullptr;
}
if (!m_data) m_data = std::make_unique<Data>();
std::unique_lock lock(m_data->m_mutex);
if (m_data->m_locked) {
m_data->m_toRemove.push_back(listener);
ranges::remove(m_data->m_toAdd, listener);
}
else {
ranges::remove(m_data->m_listeners, listener);
}
ranges::remove(m_toAdd, listener);
}
ListenerResult DefaultEventListenerPool::handle(Event* event) {
if (!m_data) m_data = std::make_unique<Data>();
auto res = ListenerResult::Propagate;
m_locked += 1;
for (auto h : m_listeners) {
// if an event listener gets destroyed in the middle of this loop, it
// gets set to null
m_data->m_locked += 1;
std::unique_lock lock(m_data->m_mutex);
for (auto h : m_data->m_listeners) {
lock.unlock();
if (h && h->handle(event) == ListenerResult::Stop) {
res = ListenerResult::Stop;
lock.lock();
break;
}
lock.lock();
}
m_locked -= 1;
m_data->m_locked -= 1;
// only mutate listeners once nothing is iterating
// (if there are recursive handle calls)
if (m_locked == 0) {
ranges::remove(m_listeners, nullptr);
for (auto listener : m_toAdd) {
m_listeners.insert(m_listeners.begin(), listener);
if (m_data->m_locked == 0) {
for (auto listener : m_data->m_toRemove) {
ranges::remove(m_data->m_listeners, listener);
}
m_toAdd.clear();
for (auto listener : m_data->m_toAdd) {
m_data->m_listeners.push_front(listener);
}
m_data->m_toAdd.clear();
m_data->m_toRemove.clear();
}
return res;
}
DefaultEventListenerPool* DefaultEventListenerPool::create() {
return new DefaultEventListenerPool();
}
DefaultEventListenerPool* DefaultEventListenerPool::get() {
static auto inst = new DefaultEventListenerPool();
return inst;