simgear/props/props.cxx: use rmutex to protect SGPropertyNodeListeners.

Also added asserts to check _num_iterators always >= 0.
This commit is contained in:
Julian Smith 2020-11-12 14:33:10 +00:00
parent 79e1ea02ab
commit d98d893f3e

View File

@ -18,6 +18,8 @@
#include <iomanip> #include <iomanip>
#include <iterator> #include <iterator>
#include <exception> // can't use sg_exception becuase of PROPS_STANDALONE #include <exception> // can't use sg_exception becuase of PROPS_STANDALONE
#include <mutex>
#include <thread>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@ -51,11 +53,15 @@ using namespace simgear;
struct SGPropertyNodeListeners struct SGPropertyNodeListeners
{ {
/* Protect _num_iterators and _items. We use a recursive mutex to allow
nested access to work as normal. */
std::recursive_mutex _rmutex;
/* This keeps a count of the current number of nested invocations of /* This keeps a count of the current number of nested invocations of
forEachListener(). If non-zero, other code higher up the stack is iterating forEachListener(). If non-zero, other code higher up the stack is iterating
_items[] so for example code must not erase items in the vector. */ _items[] so for example code must not erase items in the vector. */
int _num_iterators = 0; int _num_iterators = 0;
std::vector<SGPropertyChangeListener *> _items; std::vector<SGPropertyChangeListener *> _items;
}; };
@ -2413,6 +2419,7 @@ SGPropertyNode::addChangeListener (SGPropertyChangeListener * listener,
// REVIEW: Memory Leak - 32 bytes in 1 blocks are indirectly lost // REVIEW: Memory Leak - 32 bytes in 1 blocks are indirectly lost
_listeners = new SGPropertyNodeListeners; _listeners = new SGPropertyNodeListeners;
std::lock_guard lock(_listeners->_rmutex);
/* If there's a nullptr entry (a listener that was unregistered), we /* If there's a nullptr entry (a listener that was unregistered), we
overwrite it. This ensures that listeners that routinely unregister+register overwrite it. This ensures that listeners that routinely unregister+register
themselves don't make _listeners->_items grow unnecessarily. Otherwise simply themselves don't make _listeners->_items grow unnecessarily. Otherwise simply
@ -2438,9 +2445,13 @@ SGPropertyNode::removeChangeListener (SGPropertyChangeListener * listener)
{ {
if (_listeners == 0) if (_listeners == 0)
return; return;
/* We use a std::unique_lock rather than a std::lock_guard because we may
need to unlock early. */
std::unique_lock lock(_listeners->_rmutex);
vector<SGPropertyChangeListener*>::iterator it = vector<SGPropertyChangeListener*>::iterator it =
find(_listeners->_items.begin(), _listeners->_items.end(), listener); find(_listeners->_items.begin(), _listeners->_items.end(), listener);
if (it != _listeners->_items.end()) { if (it != _listeners->_items.end()) {
assert(_listeners->_num_iterators >= 0);
if (_listeners->_num_iterators) { if (_listeners->_num_iterators) {
/* _listeners._items is currently being iterated further up the stack in /* _listeners._items is currently being iterated further up the stack in
this thread by one or more nested invocations of forEachListener(), so this thread by one or more nested invocations of forEachListener(), so
@ -2459,6 +2470,7 @@ SGPropertyNode::removeChangeListener (SGPropertyChangeListener * listener)
_listeners->_items.erase(it); _listeners->_items.erase(it);
listener->unregister_property(this); listener->unregister_property(this);
if (_listeners->_items.empty()) { if (_listeners->_items.empty()) {
lock.unlock();
delete _listeners; delete _listeners;
_listeners = 0; _listeners = 0;
} }
@ -2520,9 +2532,11 @@ static void forEachListener(
) )
{ {
if (!_listeners) return; if (!_listeners) return;
std::lock_guard lock(_listeners->_rmutex);
assert(_listeners->_num_iterators >= 0);
_listeners->_num_iterators += 1; _listeners->_num_iterators += 1;
/* We need to use an index here when iterating _listeners->_items, not an /* We need to use an index here when iterating _listeners->_items, not an
iterator. This is because a listener may add new listeners, causing the iterator. This is because a listener may add new listeners, causing the
vector to be reallocated, which would invalidate any iterator. */ vector to be reallocated, which would invalidate any iterator. */
@ -2537,10 +2551,12 @@ static void forEachListener(
} }
} }
} }
_listeners->_num_iterators -= 1; _listeners->_num_iterators -= 1;
assert(_listeners->_num_iterators >= 0);
if (_listeners->_num_iterators == 0) { if (_listeners->_num_iterators == 0) {
/* Remove any items that have been set to nullptr. */ /* Remove any items that have been set to nullptr. */
_listeners->_items.erase( _listeners->_items.erase(
std::remove(_listeners->_items.begin(), _listeners->_items.end(), (SGPropertyChangeListener*) nullptr), std::remove(_listeners->_items.begin(), _listeners->_items.end(), (SGPropertyChangeListener*) nullptr),
@ -2556,6 +2572,7 @@ static void forEachListener(
int SGPropertyNode::nListeners() const int SGPropertyNode::nListeners() const
{ {
if (!_listeners) return 0; if (!_listeners) return 0;
std::lock_guard lock(_listeners->_rmutex);
int n = 0; int n = 0;
for (auto listener: _listeners->_items) { for (auto listener: _listeners->_items) {
if (listener) n += 1; if (listener) n += 1;