I wonder are weak variables really not thread safe wrt object destruction as stated there
So if I read weak variable XYZ in thread A at the same moment thread B leaving scope where strong variable XYZ is defined, there is a chance that it will crash?
According to Swift for C++ Practitioners, Part 2: Reference Types & Optionals | Doug's Compiler Corner, you can promote weak to strong references that won't be deallocated:
A
weak
variable must have optional type, because the object it points to might go away at any point. If the object it points to is destroyed, then the weak reference will get the valuenil
. Code that works with weak references will naturally "promote" the weak reference to a strong reference when working with it, because it's the same operation as determining whether any other optional has a value in it:if let a = b.a { }
Thanks for reply, byt my question isn’t about zeroing weak ref, I’m concerning about thread safety.
As stated in reference I linked above
// Reading a weak reference is NOT thread-safe with respect to:
// * concurrent writes to the weak variable other than zeroing
// * concurrent destruction of the weak variable
So my question is still the same, is it possible to crash if one thread reads weak var while other is destructing it?
If you mean destructing the object that the weak reference is pointing to, no, weak references should be thread-safe against that. If you mean destructing the weak reference that you're trying to read, then yes, that can crash. It should not be possible to get yourself into the latter situation without using unsafe features, though.
Thanks for reply, still wonder how it works:
HeapObject *nativeLoadStrongFromBits(WeakReferenceBits bits) {
auto side = bits.getNativeOrNull();
return side ? side->tryRetain() : nullptr;
}
Code from there
Isn't it possible race condition to occur between nativeLoadStrongFromBits
and decrement of strong ref
Looks like i'm missing something tbh
UPD:
Found out that nativeLoadStrongFromBits
and decrement of strong ref derives to these method calls. Which looks mutually synchronized like almost everything in RefCount.h. Is it a sauce that makes weak ref safe to destruction?
template <PerformDeinit performDeinit>
SWIFT_ALWAYS_INLINE
bool doDecrement(uint32_t dec) {
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
RefCountBits newbits;
// constant propagation will remove this in swift_release, it should only
// be present in swift_release_n
if (dec != 1 && oldbits.isImmortal(true)) {
return false;
}
do {
newbits = oldbits;
bool fast =
newbits.decrementStrongExtraRefCount(dec);
if (SWIFT_UNLIKELY(!fast)) {
if (oldbits.isImmortal(false)) {
return false;
}
// Slow paths include side table; deinit; underflow
return doDecrementSlow<performDeinit>(oldbits, dec);
}
} while (!refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_release,
std::memory_order_relaxed));
return false; // don't deinit
}
SWIFT_ALWAYS_INLINE
bool tryIncrement() {
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
RefCountBits newbits;
do {
if (!oldbits.hasSideTable() && oldbits.getIsDeiniting())
return false;
newbits = oldbits;
bool fast = newbits.incrementStrongExtraRefCount(1);
if (SWIFT_UNLIKELY(!fast)) {
if (oldbits.isImmortal(false))
return true;
return tryIncrementSlow(oldbits);
}
} while (!refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_relaxed));
return true;
}
Btw, when we use optional chaining like this:
obj?.doSomeStuff()
Does it rules to loading strong ref?
Invocation any of these methods:
nativeLoadStrongFromBits
or nativeTakeStrongFromBits
?