-
Notifications
You must be signed in to change notification settings - Fork 67
Cycles between Dart and Java memory #575
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
Comments
I don't think this is possible actually. Everything happens based on Java global references. If a JObject is unreachable in Dart, the underlying global ref will be deleted and if the same object had been unreachable in Java as well, it will be GCd. Can you construct an example where this can happen? |
How about of we create an interface implementation, that in one of it's closures holds on to a JObject that has a field. And then we store our just created implementation into that field? |
Right! Foo foo;
foo.bar = Interface.implement($InterfaceImpl(
f: () {
return foo;
}
)); Now when |
Let's say in the previous example of Foo foo;
foo.bar = Interface.implement($InterfaceImpl(
f: () {
return foo;
}
)); We want to use foo, but not hold on to it. One might want to do this: Foo foo;
foo.bar = Interface.implement($InterfaceImpl(
f: () {
return weak(foo);
}
)); But the act of using One could wrap the callback to T Function(A1) captureWeak1Thing1<T, A1, C1 extends JObject>(
C1 c1, T Function(A1 arg0, {required C1 $1}) callback) {
return (A1 a1) => callback(a1, $1: c1.toWeak());
} |
What is Dart and Java objects in this example?
I was thinking if we could use Java's I was thinking if we could use Dart's weak reference on foo in the closure: void something(Foo foo) {
WeakReference<Foo> weak = WeakReference(foo);
foo.bar = Interface.implement($InterfaceImpl(
f: () {
final target = _cache?.target;
if (target == null) {
// throw exception in Dart that causes exception in Java
// or can't we do that and do we need something in the JNI code generation to throw the Java exception in C code?
}
return target;
}
));
} @HosseinYousefi Would this work? |
Your assumptions are correct.
That's exactly what I'm trying to do. However one has to manually turn the objects into weakly held ones as you did in your code sample before using them in the callback. I'm trying to come up with an easier API.
We can do that already, it throws a |
Hm, that does mean adding more magic to JNIgen, instead of off-the-shelf Dart features. I'd have to be convinced it's worth the complexity. Do you have a compelling example in which we don't want to just teach JNIgen users to use Dart's WeakReference? |
Oh, so you mean using the existing void something(Foo foo) {
final weakFoo = foo.toWeak(); // This will create another Foo object that has an internal JWeakGlobalReference instead.
foo.bar = Interface.implement($InterfaceImpl(
f: () {
if (weakFoo.isNull) {
// throw exception in Dart that causes exception in Java
}
return weakFoo;
}
));
} |
Hm, that could indeed work as well. I'm not entirely sure which of the two approaches is better. Some thoughts:
|
Notes from discussion:
|
Writing some docs about this at the moment. Using weak references converts graph TD;
f-->|holds in Dart|foo;
foo-->|holds in Java|bar;
bar-->|holds in Java|f;
into graph TD;
f-.->|holds weakly in Dart|foo;
foo-->|holds in Java|bar;
bar-->|holds in Java|f;
Here's a sample code: final weakFoo = WeakReference(foo);
foo.bar = Bar.implement($Bar(
f: () {
final foo = weakFoo.target;
if (foo == null) {
throw StateError();
}
return foo;
}
)); However, we still have to be careful that This means that if we do Here's a possible design. Let's have foo.bar = Bar.implement($Bar(
$capture: ['foo': foo],
f: (context) { // All methods get an additional context
final foo = context['foo']!.as(Foo.type);
return foo;
}
));
|
I'm not sure I understand how that would change the example. Doesn't that simply create a strong reference cycle between |
It does, but in Java which is fine. The way we can implement this is we send the entire map to be stored in Java, the map itself is no longer referenced in the closures, instead we access the elements through the |
Right, You want to borrow explicit "capture"s from C++ to signify this difference in behavior. I'm not sure if capture is the right word here, because you're capturing either in Dart or in Java (or in both, if you're not careful). But the doc-comment for the So, the only things that should be captured are
Doesn't this require a More unwrapping/dereffing musingsI was trying to come up with a workaround without generating this code pattern. But, one can't manually make the context in Java as a map and pass a closure in that does a lookup in that map. Because the Dart closure would hold on to that method, which holds on to those objects. So this pattern actually needs to be part of how we do interface implementations. This does create an issue with if you want to apply the same pattern for normal callback functions. You can't easily pass a JReference to some Kotlin API that wants a callback. Because we don't want to change the signature to take this extra context. How do we do the same level of unwrapping if we pass a Kotlin reference into a kotlin function that wants a callback argument? I guess there's also a question for if you want to do an interface implementation but you don't want to actually provide all methods in Dart, but just provide a |
Yes, that would be ideal.
Yeah, I missed typing
We keep having final function2 = kotlinMethodReturningAFunction2();
// both work:
kotlinMethodExpectingAFunction2(function2);
kotlinMethodExpectingAFunction2(KtFunction2.implement(...));
We currently have
Yes it does, fixed in the original comment. |
Clever solution. I'm not a big fan of storing the context as a map, but I can't think of a better way. The ObjC equivalent would be: foo.bar = Bar.implement(
$capture: ['foo': foo],
f: (context) {
final foo = Foo.castFrom(context['foo']!);
return foo;
}
); |
Because the Dart and Java GCs do not know about each other, we can end up in situations where we lose reference in the Dart to a Dart object A and lose reference in Java to a Java object B, but B holds on to A via a (for example via an open port if A is a closure) and A holds on to B via a JObject.
It is not clear if this issue is going to be very common, but we should keep an eye on it: We should document any patterns that we find that might result in this. Probably we can construct one with dart-archive/jnigen#369.
If it's common we should probably provide some kind of weak reference system in at least one direction. It might depend per use case if this is possible.
(A completely out of scope, but conceptually cool, solution: A unified Dart and Java heap in the style of Oilpan which unifies the C++ DOM heap with the JS heap: https://v8.dev/blog/oilpan-library)
The text was updated successfully, but these errors were encountered: