Browse Source

pgraph: fix double free if weak ptr to state is locked while being gc'ed

Fixes #499
rdb 5 years ago
parent
commit
c5c1d4557b

+ 15 - 0
panda/src/express/referenceCount.I

@@ -317,6 +317,21 @@ ref_if_nonzero() const {
   return true;
   return true;
 }
 }
 
 
+/**
+ * Atomically decreases the reference count of this object if it is one.
+ * Do not use this.  This exists only to implement a special case with the
+ * state cache.
+ * @return true if the reference count was decremented to zero.
+ */
+INLINE bool ReferenceCount::
+unref_if_one() const {
+#ifdef _DEBUG
+  nassertr(test_ref_count_integrity(), 0);
+  nassertr(_ref_count > 0, 0);
+#endif
+  return (AtomicAdjust::compare_and_exchange(_ref_count, 1, 0) == 1);
+}
+
 /**
 /**
  * This global helper function will unref the given ReferenceCount object, and
  * This global helper function will unref the given ReferenceCount object, and
  * if the reference count reaches zero, automatically delete it.  It can't be
  * if the reference count reaches zero, automatically delete it.  It can't be

+ 1 - 0
panda/src/express/referenceCount.h

@@ -64,6 +64,7 @@ public:
   INLINE void weak_unref();
   INLINE void weak_unref();
 
 
   INLINE bool ref_if_nonzero() const;
   INLINE bool ref_if_nonzero() const;
+  INLINE bool unref_if_one() const;
 
 
 protected:
 protected:
   bool do_test_ref_count_integrity() const;
   bool do_test_ref_count_integrity() const;

+ 4 - 3
panda/src/pgraph/renderAttrib.cxx

@@ -215,14 +215,15 @@ garbage_collect() {
 
 
   do {
   do {
     RenderAttrib *attrib = (RenderAttrib *)_attribs->get_key(si);
     RenderAttrib *attrib = (RenderAttrib *)_attribs->get_key(si);
-    if (attrib->get_ref_count() == 1) {
+    if (attrib->unref_if_one()) {
       // This attrib has recently been unreffed to 1 (the one we added when
       // This attrib has recently been unreffed to 1 (the one we added when
       // we stored it in the cache).  Now it's time to delete it.  This is
       // we stored it in the cache).  Now it's time to delete it.  This is
       // safe, because we're holding the _attribs_lock, so it's not possible
       // safe, because we're holding the _attribs_lock, so it's not possible
       // for some other thread to find the attrib in the cache and ref it
       // for some other thread to find the attrib in the cache and ref it
-      // while we're doing this.
+      // while we're doing this.  Also, we've just made sure to unref it to 0,
+      // to ensure that another thread can't get it via a weak pointer.
       attrib->release_new();
       attrib->release_new();
-      unref_delete(attrib);
+      delete attrib;
 
 
       // When we removed it from the hash map, it swapped the last element
       // When we removed it from the hash map, it swapped the last element
       // with the one we just removed.  So the current index contains one we
       // with the one we just removed.  So the current index contains one we

+ 5 - 3
panda/src/pgraph/renderState.cxx

@@ -934,15 +934,17 @@ garbage_collect() {
       }
       }
     }
     }
 
 
-    if (state->get_ref_count() == 1) {
+    if (state->unref_if_one()) {
       // This state has recently been unreffed to 1 (the one we added when
       // This state has recently been unreffed to 1 (the one we added when
       // we stored it in the cache).  Now it's time to delete it.  This is
       // we stored it in the cache).  Now it's time to delete it.  This is
       // safe, because we're holding the _states_lock, so it's not possible
       // safe, because we're holding the _states_lock, so it's not possible
       // for some other thread to find the state in the cache and ref it
       // for some other thread to find the state in the cache and ref it
-      // while we're doing this.
+      // while we're doing this.  Also, we've just made sure to unref it to 0,
+      // to ensure that another thread can't get it via a weak pointer.
+
       state->release_new();
       state->release_new();
       state->remove_cache_pointers();
       state->remove_cache_pointers();
-      state->cache_unref();
+      state->cache_unref_only();
       delete state;
       delete state;
 
 
       // When we removed it from the hash map, it swapped the last element
       // When we removed it from the hash map, it swapped the last element

+ 4 - 3
panda/src/pgraph/transformState.cxx

@@ -1204,15 +1204,16 @@ garbage_collect() {
       }
       }
     }
     }
 
 
-    if (state->get_ref_count() == 1) {
+    if (state->unref_if_one()) {
       // This state has recently been unreffed to 1 (the one we added when
       // This state has recently been unreffed to 1 (the one we added when
       // we stored it in the cache).  Now it's time to delete it.  This is
       // we stored it in the cache).  Now it's time to delete it.  This is
       // safe, because we're holding the _states_lock, so it's not possible
       // safe, because we're holding the _states_lock, so it's not possible
       // for some other thread to find the state in the cache and ref it
       // for some other thread to find the state in the cache and ref it
-      // while we're doing this.
+      // while we're doing this.  Also, we've just made sure to unref it to 0,
+      // to ensure that another thread can't get it via a weak pointer.
       state->release_new();
       state->release_new();
       state->remove_cache_pointers();
       state->remove_cache_pointers();
-      state->cache_unref();
+      state->cache_unref_only();
       delete state;
       delete state;
 
 
       // When we removed it from the hash map, it swapped the last element
       // When we removed it from the hash map, it swapped the last element