dep-protobuf/python/google/protobuf/pyext/weak_value_map.cc
Joshua Haberman feaa31c4d7 [Py/FreeThreading] Fixed remaining race conditions in Dealloc()
This change modifies all remaining `Dealloc()` functions to use `EraseIfEqual` if they were not already. This prevents the same race that was fixed for descriptors in cl/874084218.

PiperOrigin-RevId: 952273589
2026-07-22 12:43:18 -07:00

111 lines
3 KiB
C++

#include "google/protobuf/pyext/weak_value_map.h"
#include <utility>
#include "absl/log/absl_check.h"
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#ifdef Py_GIL_DISABLED
// In a free threaded build, we have to always be prepared for the possibility
// that an object is being deallocated, but has not yet been removed from the
// map. We accomplish this by using the unstable PyUnstable_TryIncRef() function
// which returns false if the object is dying.
PyObject* PyWeakValueMap::Get(const void* key, const PyTypeObject* type) {
absl::MutexLock lock(&mutex_);
auto it = cache_.find(key);
if (it != cache_.end()) {
ABSL_DCHECK(type == nullptr || Py_TYPE(it->second) == type);
if (PyUnstable_TryIncRef(it->second)) {
return it->second;
}
// Object is deallocating, remove it from the map.
cache_.erase(it);
}
return nullptr;
}
bool PyWeakValueMap::TrySet(const void* key, PyObject*& value) {
PyTypeObject* type = Py_TYPE(value);
PyObject* decref;
PyUnstable_EnableTryIncRef(value);
{
absl::MutexLock lock(&mutex_);
auto [it, inserted] = cache_.insert(std::make_pair(key, value));
if (inserted) return true;
// The object is already in the map. Try to use the existing object.
ABSL_DCHECK(type == nullptr || Py_TYPE(it->second) == type);
if (PyUnstable_TryIncRef(it->second)) {
// The existing object is valid, so we can deallocate our copy, but we
// should drop the lock first.
decref = value;
value = it->second;
// Fall through to the end of the function.
} else {
// The existing object is dying, replace it.
it->second = value;
return true;
}
}
Py_DECREF(decref);
return false;
}
bool PyWeakValueMap::EraseIfEqualImpl(const void* key, PyObject* value) {
absl::MutexLock lock(&mutex_);
auto it = cache_.find(key);
if (it != cache_.end() && it->second == value) {
cache_.erase(it);
return true;
}
return false;
}
bool PyWeakValueMap::IsEmpty() const {
absl::MutexLock lock(&mutex_);
return cache_.empty();
}
void PyWeakValueMap::Clear() {
absl::MutexLock lock(&mutex_);
cache_.clear();
}
#else // !Py_GIL_DISABLED
bool PyWeakValueMap::TrySet(const void* key, PyObject*& value) {
auto [it, inserted] = cache_.insert(std::make_pair(key, value));
if (inserted) return true;
Py_DECREF(value);
Py_INCREF(it->second);
value = it->second;
return false;
}
PyObject* PyWeakValueMap::Get(const void* key, const PyTypeObject* type) {
auto it = cache_.find(key);
if (it == cache_.end()) return nullptr;
Py_INCREF(it->second);
return it->second;
}
bool PyWeakValueMap::EraseIfEqualImpl(const void* key, PyObject* value) {
auto it = cache_.find(key);
// In a single-threaded build, the object is guaranteed to be in the map.
ABSL_CHECK(it != cache_.end() && it->second == value);
cache_.erase(it);
return true;
}
bool PyWeakValueMap::IsEmpty() const { return cache_.empty(); }
void PyWeakValueMap::Clear() { cache_.clear(); }
#endif