Open slluis opened 2 years ago
This is also the case on Mono/xammac, 7.11.
I'm not sure there's much we (in Xamarin.Mac) can do here because:
[NSTextView dealloc]
method is horribly slow. I profiled a bit with Instruments, and over 90% of the time is spent there. There's not much we can do here (except not allocate thousands of NSTextViews).That said, I found a minor memory leak (https://github.com/xamarin/xamarin-macios/pull/13109), so that's good :)
Disposing NSView subclasses is unsafe, in case you have a managed subclass and appkit keeps a view around for longer, you will end up with resurrection issues. Or you could dispose some object passed around that you don't own (i.e. something stored on a field) and all the NSObject wrappers are disposed.
So we have to rely on finalizers most of the time in this case. That way, copy (IntPtr) constructors are reserved for when AppKit copies things - like NSTextFieldCell - and we don't have to worry about resurrection.
I've been doing a bit of digging, and it seems like performSelectorOnMainThread
has some quirks to it:
https://developer.apple.com/documentation/objectivec/nsobject/1414900-performselectoronmainthread
Specifically, the runloop part:
This method registers with the runloop of its current context, and depends on that runloop being run on a regular basis to perform correctly. One common context where you might call this method and end up registering with a runloop that is not automatically run on a regular basis is when being invoked by a dispatch queue. If you need this type of functionality when running on a dispatch queue, you should use dispatch_after and related methods to get the behavior you want.
Wouldn't the finalizer thread stop running the runloop until woken up by the GC? I'll try and crop up a test case comparing performSelectorOnMainThread
and dispatch_async
. It's possible that most of the issues I was encountering were a Mono-only problem, so have to validate that first.
Wouldn't the finalizer thread stop running the runloop until woken up by the GC?
The finalizer thread isn't running a runloop, it's just queuing stuff to be executed on the main thread's runloop.
I don't quite understand what the documentation is saying, but according to this: https://stackoverflow.com/a/9336253/183422, the main thread's runloop can run in a mode that doesn't process the items queued by performSelectorOnMainThread
. That shouldn't be a problem for a normal UI app (and I've never seen it be a problem either).
https://gist.github.com/Therzok/55aaad6aa6b85aff6e763edb4765c87e
I'm not sure if it's by chance or not, but I'm seeing quite different behaviour in processing these, with AppKit's performSelectorOnMainThread
being overall slower and delayed compared to libdispatch - but not sure if that's caused by interop overhead because of the way the test is constructed.
I've improved on the sample here: https://github.com/Therzok/TestFinalizerRunLoop
I don't think it is a problem of the GC here. If you take a look at the current implementation - which has some workarounds in play to avoid crashing - and run the code, you will see that forcing allocations on a loop here, the nested subviews go away on separate GC cycles.
Note: The workarounds are to bind the lifetime of the Window to the WindowController, otherwise the Window is released on the same GC cycle as the WindowController causing a resurrection in ViewDidMoveToWindow. It happens regardless of ReleaseWhenClosed, because the NSWindowController takes over the lifetime of the NSWindow.
Steps to Reproduce
Expected Behavior
Every time the button is clicked the NSTextView instance count should be increased by 1000 and then gradually decrease to 0 as the GC starts collecting the views.
Actual Behavior
This is what I'm experiencing in my machine:
Environment
Build Logs
Example Project
Sample.zip