Illusion of atomic reference counting

Regardless if an object's reference counter is atomic, there is one major problem when a single RefPtr holding it is being re-assigned and read concurrently.

Here I'll explain on a simple example. Note that all the time we are in a method of a single object, Type has ThreadSafeAutoRefCnt reference counter, when talking Mozilla code-base terms:

RefPtr<Type>; local = mMember; // mMember is RefPtr<mType>, holding an object

And another piece of code then, on a different thread:

mMember = new Type(); // mMember's value is rewritten with a new object

Usually, people believe this is perfectly safe. But it's far from it. Just break this to actual atomic operations and put the two threads side by side:

Thread 1

local.value = mMemeber.value;
/* context switch */
.
.
.
.
.
.
local.value->AddRef();

Thread 2

.
.
Type* temporary = new Type();
temporary->AddRef();
Type* old = mMember.value;
mMember.value = temporary;
old->Release();
/* context switch */
.

Similar for clearing a member (or a global, when we are here) while some other thread may try to grab a reference to it:

RefPtr<type>; service = sService; // sService is a RefPtr
if (!service) return; // service being null is our 'after shutdown' flag

And another thread doing, usually during shutdown:

sService = nullptr; // while sService was holding an object

And here is what actually happens:

Thread 1

local.value = sService.value;
/* context switch */
.
.
.
.
local.value->AddRef();

Thread 2

.
.
Type* old = sService.value;
sService.value = nullptr;
old->Release();
/* context switch */
.

And where is the problem? Clearly, if the Release() call on the second thread is the last one on the object, the AddRef() on the first thread will do its job on a dying or already dead object, not talking about further access to a bad pointer.

The only correct way is to have both in and out assignments protected by a mutex or ensure that anyone cannot be trying to grab a reference from a globally accessed RefPtr when it's being finally released or just being re-assigned. The latter may not always be easy or even possible.

Anyway, if somebody knows how to solve this universally without using an additional lock, I would be interested!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.