feat(rtl): mutex-protect weak reference table for thread safety

Add a pthread mutex around all weak reference table operations
(_WeakAssign, _WeakClear, _WeakZeroSlots).  The table is a shared
global data structure, and concurrent _ClassRelease calls from
multiple threads could corrupt the bucket chains without locking.

The mutex is coarse-grained (one lock for the entire table) but
sufficient because weak references are rare relative to strong ARC
operations.
This commit is contained in:
Graeme Geldenhuys 2026-06-06 12:48:08 +01:00
parent e093be0b70
commit 70a22267cc

View file

@ -12,24 +12,28 @@
A weak reference is a slot (variable or field) pointing at a class A weak reference is a slot (variable or field) pointing at a class
instance without contributing to its refcount. When the backing instance without contributing to its refcount. When the backing
object's last strong reference is released and the object is about to object's last strong reference is released and the object is about to
be freed, every registered weak slot pointing at it is nil'd — so a be freed, every registered weak slot pointing at it is nil'd -- so a
subsequent dereference sees nil rather than dangling memory. subsequent dereference sees nil rather than dangling memory.
Design: one global open-chained hash table, keyed on the user_ptr of Design: one global open-chained hash table, keyed on the user_ptr of
the backing object. Each entry carries a linked list of slot the backing object. Each entry carries a linked list of slot
addresses (pointers-to-pointers) that reference it. Inserts and addresses (pointers-to-pointers) that reference it. Inserts and
removals are O(bucket-chain + slot-list) — acceptable because weak removals are O(bucket-chain + slot-list) -- acceptable because weak
references are rare relative to strong references. references are rare relative to strong references.
Thread safety: a single pthread mutex protects all table mutations.
The lock is coarse-grained but sufficient because weak references
are infrequent relative to strong ARC operations.
API: API:
_WeakAssign(slot, new_target) — register slot under new_target and _WeakAssign(slot, new_target) -- register slot under new_target and
store new_target at *slot; any prior store new_target at *slot; any prior
registration for *slot is removed. registration for *slot is removed.
new_target may be nil (just unregister). new_target may be nil (just unregister).
_WeakClear(slot) — unregister and zero *slot. Called at _WeakClear(slot) -- unregister and zero *slot. Called at
scope exit and field cleanup for weak scope exit and field cleanup for weak
declarations. declarations.
_WeakZeroSlots(target) — nil every slot pointing at target and _WeakZeroSlots(target) -- nil every slot pointing at target and
drop target's entry from the table. drop target's entry from the table.
Called from _ClassRelease at refcount Called from _ClassRelease at refcount
zero, before the field-cleanup fn zero, before the field-cleanup fn
@ -65,9 +69,16 @@ type
function _BlaiseGetMem(Size: Integer): Pointer; external name '_BlaiseGetMem'; function _BlaiseGetMem(Size: Integer): Pointer; external name '_BlaiseGetMem';
procedure _BlaiseFreeMem(Ptr: Pointer); external name '_BlaiseFreeMem'; procedure _BlaiseFreeMem(Ptr: Pointer); external name '_BlaiseFreeMem';
function pthread_mutex_init(Mutex: Pointer; Attr: Pointer): Integer;
external name 'pthread_mutex_init';
function pthread_mutex_lock(Mutex: Pointer): Integer;
external name 'pthread_mutex_lock';
function pthread_mutex_unlock(Mutex: Pointer): Integer;
external name 'pthread_mutex_unlock';
var var
WeakTable: array[0..255] of PWeakEntry; WeakTable: array[0..255] of PWeakEntry;
WeakMutex: array[0..5] of Int64;
function WeakHash(Ptr: Pointer): Integer; function WeakHash(Ptr: Pointer): Integer;
begin begin
@ -152,6 +163,7 @@ var
begin begin
if Slot = nil then if Slot = nil then
Exit; Exit;
pthread_mutex_lock(@WeakMutex);
WeakUnregister(Slot); WeakUnregister(Slot);
PP := Slot; PP := Slot;
PP^ := NewTarget; PP^ := NewTarget;
@ -159,14 +171,21 @@ begin
begin begin
E := WeakFindOrCreate(NewTarget); E := WeakFindOrCreate(NewTarget);
if E = nil then if E = nil then
begin
pthread_mutex_unlock(@WeakMutex);
Exit; Exit;
end;
S := PWeakSlot(_BlaiseGetMem(16)); S := PWeakSlot(_BlaiseGetMem(16));
if S = nil then if S = nil then
begin
pthread_mutex_unlock(@WeakMutex);
Exit; Exit;
end;
S^.Addr := Slot; S^.Addr := Slot;
S^.Next := E^.Slots; S^.Next := E^.Slots;
E^.Slots := S; E^.Slots := S;
end; end;
pthread_mutex_unlock(@WeakMutex);
end; end;
procedure _WeakClear(Slot: Pointer); procedure _WeakClear(Slot: Pointer);
@ -175,9 +194,11 @@ var
begin begin
if Slot = nil then if Slot = nil then
Exit; Exit;
pthread_mutex_lock(@WeakMutex);
WeakUnregister(Slot); WeakUnregister(Slot);
PP := Slot; PP := Slot;
PP^ := nil; PP^ := nil;
pthread_mutex_unlock(@WeakMutex);
end; end;
procedure _WeakZeroSlots(Target: Pointer); procedure _WeakZeroSlots(Target: Pointer);
@ -192,6 +213,7 @@ var
begin begin
if Target = nil then if Target = nil then
Exit; Exit;
pthread_mutex_lock(@WeakMutex);
H := WeakHash(Target); H := WeakHash(Target);
Prev := nil; Prev := nil;
E := WeakTable[H]; E := WeakTable[H];
@ -214,11 +236,16 @@ begin
else else
Prev^.Next := Dead^.Next; Prev^.Next := Dead^.Next;
_BlaiseFreeMem(Dead); _BlaiseFreeMem(Dead);
pthread_mutex_unlock(@WeakMutex);
Exit; Exit;
end; end;
Prev := E; Prev := E;
E := E^.Next; E := E^.Next;
end; end;
pthread_mutex_unlock(@WeakMutex);
end; end;
initialization
pthread_mutex_init(@WeakMutex, nil);
end. end.