-
-
Notifications
You must be signed in to change notification settings - Fork 52
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
Attribute intermittently returns the wrong object #249
Comments
Some notes on how Chaquopy deals with object identity and deletion. All paths are relative to product/runtime. JNI doesn't expose memory addresses directly; instead it uses opaque JNI references. These are represented by a low-level class (never exposed to the Chaquopy user) in src/main/python/java/env.pxi. It has a In src/main/python/java/class.pxi there's an The Python proxy object itself also stores a copy of the JNI reference (assigned to Once the Python proxy is destroyed, the Then, the Python proxy's This is tested in src/test/python/chaquopy/test/test_reflect.py:
Hopefully this will give you some ideas. |
@mhsmith Thanks for those details - sounds remarkably close to what Rubicon is doing. The only real differences appear to be the garbage collection chain on the weak references (which doesn't appear to be quite as immediate from the ObjC side); and the lack of a good "IsSameObject" analog (Rubicon has to work with the memory address, which (a) can be re-used, and (b) has no way to identify if it's currently valid) |
At first I was confused by how this is possible. But I guess it's explained by the documentation section on Reference counting, which says that Rubicon will only take "ownership" of an Objective-C instance when you create it Does this mean that all other Python wrapper objects do nothing to keep the Objective-C object alive, and can have the object disappear from underneath them at any time? If so, doesn't the workaround in #246 still leave the potential for all kinds of incomprehensible bugs and crashes?
In Chaquopy, all Python wrapper objects have a strong reference to the Java object, so the object cannot be disposed by Java until after the Python object is destroyed. Could this be implemented in Rubicon? |
So - yes, there's potential for crashes for the reasons you identify - if you haven't got your memory allocation correct. Objective C is "C with syntactic sugar", not a fully memory managed language; and as such, the author needs to be very aware of malloc/free usage (spelled Unfortunately, what you need to do is not completely straightforward, because it's dependent on the API you're invoking. The line about "alloc, new, copy or mutableCopy" comes from the Objective C runtime's memory management guide; but other APIs will explicitly call out that "the user calling the method takes ownership of the allocated object" (or similar). There's one small affordance - I don't think the "strong reference" route is entirely viable (although I'm open to ideas); it would definitely prevent crashes - if you retain everything and only release when the Python object is deleted, then Objective C can't free or reallocate the memory. However, this would be very prone to memory leaks - it would very very easy to get into a situation where Python is the entity preventing the release of memory. The other issue is timing. Garbage collection on the Python side isn't guaranteed to be immediate, which leaves a (small, but clearly reproducible) window where an object still exists on the Python side, but it hasn't been garbage collected; and as a result, it hasn't been removed from the weak reference dictionary. I definitely won't rule out the possibility that there's something we could do to improve things. However, Rubicon's current strategy has (until this PR, anyway) been reasonably stable; messing around with memory allocation code is a I'm hesitant to go spelunking in this area without a clear idea of a future state, or a reproducible problem to solve. |
Based on this page, it looks like objects are always deallocated when their reference count reaches 0, but there's a way to defer a |
This was discovered while testing beeware/toga#1707: I'll leave it to @freakboy3742 to fill in the details.
The Details
This is something that turns up when running the newly added Toga GUI test suite. I believe it could manifest in other scenarios, but it's proving hard to replicate. Even in the Toga GUI test suite, the problem is intermittent - maybe 1 in 20 runs.
This is the sequence of issues that occurs:
NSView.subviews
(an NSArray).When the ObjCInstance is constructed for the NSArray object, Rubicon checks the memory address in the instance weakref dictionary, it determines that an instance already exists, and returns that instance. However, that instance has been previously loaded as an NSLayoutConstraint, so the methods cache has been loaded for a different class; and attempting to iterate over the NSArray raises an error that it isn't an iterable object.
It isn't yet clear if the issue is the gap between deletion of the object on ObjC and deletion/garbage collection of the Python object; or an issue with the retain/release strategy. However, a workaround has been to intercept the return value of
ObjCMethod.__call__
, and if it's an ObjC object, use anobject_getClass
to compare the class name associated with the returned pointer with the cached object; if there's a discrepancy, the cache is purged.The text was updated successfully, but these errors were encountered: