Skip to content

Commit 6400a82

Browse files
committed
Try something else.
1 parent c67cfcc commit 6400a82

File tree

1 file changed

+76
-4
lines changed

1 file changed

+76
-4
lines changed

src/Foundation/NSObject2.cs

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,18 +85,33 @@ public NSObjectDataHandle ()
8585
}
8686
}
8787

88+
public NSObjectDataHandle (IntPtr handle)
89+
: base (handle)
90+
{
91+
//Runtime.NSLog ($"NSObjectDataHandle (0x{handle:x}): wrapped existing pointer");
92+
}
93+
94+
public void Invalidate ()
95+
{
96+
handle = IntPtr.Zero;
97+
//Runtime.NSLog ($"NSObjectDataHandle.Disown (): disowned 0x{handle:x}");
98+
}
99+
88100
public unsafe NSObjectData* Data {
89101
get => (NSObjectData*) handle;
90102
}
91103

92104
public override bool IsInvalid {
93-
get => handle != IntPtr.Zero;
105+
get => handle == IntPtr.Zero;
94106
}
95107

96108
protected override bool ReleaseHandle ()
97109
{
98-
unsafe {
99-
NativeMemory.Free ((void*) handle);
110+
if (handle != IntPtr.Zero) {
111+
unsafe {
112+
NativeMemory.Free ((void*) handle);
113+
}
114+
//Runtime.NSLog ($"NSObjectDataHandle.ReleaseHandle (): released 0x{handle:x}");
100115
}
101116
handle = IntPtr.Zero;
102117
return true;
@@ -150,9 +165,19 @@ unsafe NativeHandle handle {
150165

151166
internal unsafe NSObjectData* GetData ()
152167
{
153-
return AllocateData ().Data;
168+
var rv = AllocateData ().Data;
169+
170+
//Runtime.NSLog ($"{GetType ().Name}.GetData () id={objectId} data: 0x{(IntPtr) rv}");
171+
if (rv is null) {
172+
//Runtime.NSLog ($"{GetType ().Name}.GetData (): no handle\n{Environment.StackTrace}");
173+
throw new ObjectDisposedException ("No data???");
174+
}
175+
176+
return rv;
154177
}
155178

179+
static int objectIdCounter;
180+
int objectId;
156181
unsafe NSObjectDataHandle AllocateData ()
157182
{
158183
if (data_handle is not null)
@@ -166,6 +191,9 @@ unsafe NSObjectDataHandle AllocateData ()
166191
return previousValue;
167192
}
168193

194+
objectId = Interlocked.Increment (ref objectIdCounter);
195+
//Runtime.NSLog ($"{GetType ().Name}.AllocateData () id={objectId} allocated 0x{((IntPtr) data.Data):x}");
196+
169197
if (!Runtime.IsCoreCLR) // This condition (and the assignment to __handle_for_mono if applicable) is trimmed away by the linker.
170198
__data_for_mono = data.Data;
171199

@@ -999,10 +1027,54 @@ protected virtual void Dispose (bool disposing)
9991027
ReleaseManagedRef ();
10001028
} else {
10011029
NSObject_Disposer.Add (this);
1030+
RecreateDataHandle ();
10021031
}
10031032
}
10041033
}
10051034

1035+
#nullable enable
1036+
void RecreateDataHandle ()
1037+
{
1038+
// OK, this code is _weird_.
1039+
// We need to delay the deletion of the native memory pointed to by data_handle until
1040+
// after this instance has been collected. A CriticalHandle seems to fit this purpose like glove, until
1041+
// you realize that a CriticalHandle is only kept alive until the parent object _becomes finalizable_,
1042+
// not _is collected_, which is very different - in other words, resurrected objects don't keep CriticalHandles
1043+
// they contain alive. This is a problem because every single managed NSObject instance is resurrected, and we
1044+
// need the native memory to stay alive after resurrection.
1045+
//
1046+
// So this solution depends on a few bits:
1047+
// * At this point, this instance may have become finalizable, but the native memory shouldn't have been freed yet.
1048+
// * The original NSObjectDataHandle (aka CriticalHandle) will be collected in this/upcoming GC cycle, and can't
1049+
// be trusted to keep the native memory alive anymore.
1050+
// * So we just create a new one, pointing to the same native memory, and replace the original NSObjectDataHandle (aka
1051+
// CriticalHandle) with it
1052+
// * This works, because since this instance has become / will become resurrected, it's not finalizable anymore,
1053+
// and it will keep the new NSObjectDataHandle instance (and the native memory it points to) alive.
1054+
// * Now if this instance is deemed finalizable, and then resurrected *again*, bad things will likely happen. This
1055+
// is a bit more unlikely though, because we don't re-register the finalizer for execution, so unless somebody
1056+
// else does that, it's quite unlikely this instance will become resurrected a second time.
1057+
var previous_data = data_handle;
1058+
if (previous_data is null) {
1059+
throw new InvalidOperationException ($"Huh? (1)");
1060+
// return;
1061+
}
1062+
1063+
unsafe {
1064+
data_handle = new NSObjectDataHandle ((IntPtr) previous_data.Data);
1065+
}
1066+
1067+
if (previous_data.IsInvalid) {
1068+
throw new InvalidOperationException ($"Huh? (2)");
1069+
// return;
1070+
}
1071+
1072+
//Runtime.NSLog ($"{GetType ().Name}: id={objectId} previous value invalidated and disposed. Current flags: {flags}");
1073+
previous_data.Invalidate ();
1074+
previous_data.Dispose ();
1075+
}
1076+
#nullable disable
1077+
10061078
[Register ("__NSObject_Disposer")]
10071079
[Preserve (AllMembers = true)]
10081080
internal class NSObject_Disposer : NSObject {

0 commit comments

Comments
 (0)