McRogueFace/src/PythonObjectCache.cpp
John McCardle 394e79ce88 Fix PythonObjectCache race and document die() iteration (closes #269, closes #273)
Add mutex lock to PythonObjectCache::lookup() - cache.find() was
unprotected against concurrent modification. Document that entity.die()
must not be called during iteration over grid.entities.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:33:05 -05:00

91 lines
No EOL
2.3 KiB
C++

#include "PythonObjectCache.h"
#include <iostream>
PythonObjectCache* PythonObjectCache::instance = nullptr;
PythonObjectCache& PythonObjectCache::getInstance() {
if (!instance) {
instance = new PythonObjectCache();
}
return *instance;
}
PythonObjectCache::~PythonObjectCache() {
clear();
}
uint64_t PythonObjectCache::assignSerial() {
return next_serial.fetch_add(1, std::memory_order_relaxed);
}
void PythonObjectCache::registerObject(uint64_t serial, PyObject* weakref) {
if (!weakref || serial == 0) return;
std::lock_guard<std::mutex> lock(serial_mutex);
// Clean up any existing entry
auto it = cache.find(serial);
if (it != cache.end()) {
Py_DECREF(it->second);
}
// Store the new weak reference
Py_INCREF(weakref);
cache[serial] = weakref;
}
PyObject* PythonObjectCache::lookup(uint64_t serial) {
if (serial == 0) return nullptr;
std::lock_guard<std::mutex> lock(serial_mutex);
auto it = cache.find(serial);
if (it != cache.end()) {
PyObject* obj = nullptr;
int result = PyWeakref_GetRef(it->second, &obj);
if (result == 1 && obj) {
// obj is already a strong reference from PyWeakref_GetRef
return obj;
}
// result == 0: dead reference, result == -1: error
}
return nullptr;
}
void PythonObjectCache::remove(uint64_t serial) {
if (serial == 0) return;
std::lock_guard<std::mutex> lock(serial_mutex);
auto it = cache.find(serial);
if (it != cache.end()) {
Py_DECREF(it->second);
cache.erase(it);
}
}
void PythonObjectCache::cleanup() {
std::lock_guard<std::mutex> lock(serial_mutex);
auto it = cache.begin();
while (it != cache.end()) {
PyObject* obj = nullptr;
int result = PyWeakref_GetRef(it->second, &obj);
if (result <= 0) {
// Dead reference or error - remove from cache
Py_DECREF(it->second);
it = cache.erase(it);
} else {
// Still alive - release the strong reference we obtained
Py_DECREF(obj);
++it;
}
}
}
void PythonObjectCache::clear() {
std::lock_guard<std::mutex> lock(serial_mutex);
for (auto& pair : cache) {
Py_DECREF(pair.second);
}
cache.clear();
}