-
Notifications
You must be signed in to change notification settings - Fork 63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ffigen] Some objects must be destroyed on the platform thread #1470
Comments
"The best API is no API." So probably option 1, unless we can measure performance impacts. If we can't do option 1, I'd like to refrain from option 2. Because that would create an asymmetry between |
It couldn't be done for all calls. If I have a thread-hostile Obj-C object, and I create an instance of it on a thread other than the platform thread, then this would break things. |
An object like that will already be broken under the current approach, since GC can run on a background thread. There's currently no guarantee about what thread a finalizer will be run on. Do you have any objects like that? I guess the ideal solution would be to have some way of ensuring that the object is destroyed on the same thread it was created on. I'm not sure that's possible in general though. We can't even guarantee the creation thread still exists. We might be able to use |
I don't need this for
That is definitely extremely bad for the common case of platform-thread-only objects. That means my current |
I don't see a good way of solving the fully general version of this problem. So until we have a concrete use case where option 1 won't work, I'm inclined to send every release call to the platform thread (assuming this is ok from a performance perspective). We know that this thread exists, and there are existing ObjC constructs to send tasks to it. If users have an object that is very thread-hostile, my current advice would be to manage its life-cycle in native code (maybe write a thread safe wrapper that can be used in Dart). Or I guess you could just create and interact with it using |
Performance doesn't seem to be a significant issue here. I wrote a little benchmark that creates a million final t = Stopwatch()..start();
print('global retain count BEFORE = ${getGlobalRetainCount()}');
void inner() {
List<NSString>? objs = <NSString>[];
for (int i = 0; i < 1000000; ++i) {
objs.add('str $i'.toNSString());
}
int lensum = 0;
for (final o in objs) {
lensum += o.toString().length;
}
print(lensum);
print('global retain count DURING = ${getGlobalRetainCount()}');
objs = null;
}
inner();
doGC();
await Future<void>.delayed(Duration(milliseconds: 500));
doGC();
await Future<void>.delayed(Duration(milliseconds: 500));
print('global retain count AFTER = ${getGlobalRetainCount()}');
print('time = ${t.elapsed}'); Under the current approach, this benchmark runs in 3.7 sec (subtracting the 1 second of waiting I'm doing around the GCs). If we dispatch all the releases to the main thread, the benchmark takes 4.1 sec. That 4.1 sec doesn't include the time spent on the main thread itself, but I don't have a good way of measuring that. One thing I can say is that it definitely spends less than 1 second on the main thread, because by the time that final UPDATE: Managed to narrow this down by plotting this retain count over time. It goes from 1 million to 0 in 0.17 sec. That translates to about 0.17 μs spent on the main thread per object. I'm using this ObjC function to do the dispatching: void runOnMainThread(void(*fn)(void*), void* arg) {
dispatch_queue_main_t q = dispatch_get_main_queue();
if (q == nil) {
fn(arg);
} else {
dispatch_async(q, ^{
fn(arg);
});
}
} There are 2 issues left to resolve:
|
Some ObjC objects can only be used from the platform thread. These objects should also only be destroyed from the platform thread.
We can move our finalizer to the platform thread easily enough, but there's no way of detecting which classes this is necessary for. So there are two options:
The text was updated successfully, but these errors were encountered: