ronaldoussoren / pyobjc

The Python <-> Objective-C Bridge with bindings for macOS frameworks
https://pyobjc.readthedocs.io
554 stars 47 forks source link

Objc object references being lost #292

Open ronaldoussoren opened 4 years ago

ronaldoussoren commented 4 years ago

Original report by Alex Kritchevsky (Bitbucket: [Alex Kritchevsky](https://bitbucket.org/Alex Kritchevsky), ).


Hi,
I’m investigating an issue where we occasionally see exceptions from objective-C due to nil references on code that appears to be innocuous.

An example partial stack trace is:

 0  libobjc.A.dylib!lookUpImpOrForward + 0x7a
 1  libobjc.A.dylib!_objc_msgSend_uncached + 0x49
 2  CoreFoundation!CFEqual + 0x26e
 3  QuartzCore!CAValueEqual + 0x78
 4  QuartzCore!CA::Layer::setter(unsigned int, _CAValueType, void const*) + 0xa0
 5  QuartzCore!-[CAShapeLayer setFillColor:] + 0x30
 6  libffi.6.dylib!ffi_call_unix64 + 0x4c
 7  libffi.6.dylib!ffi_call + 0x381
 8  objc._objc.cpython-37m-darwin.so!_PyObjCFFI_Caller + 0x75f
 9  objc._objc.cpython-37m-darwin.so!_objcsel_call + 0x23b
10  libdropbox_python.3.7.dylib!_PyObject_FastCallKeywords [call.c : 199 + 0x10]
11  libdropbox_python.3.7.dylib!call_function [ceval.c : 4619 + 0x5]
12  libdropbox_python.3.7.dylib!_PyEval_EvalFrameDefault [ceval.c : 3093 + 0x7]
13  libdropbox_python.3.7.dylib!function_code_fastcall [call.c : 283 + 0xa]
14  objc._objc.cpython-37m-darwin.so!_method_stub + 0x542
15  libffi.6.dylib!ffi_closure_unix64_inner + 0x32e
16  libffi.6.dylib!ffi_closure_unix64 + 0x46
17  AppKit!-[NSView init] + 0x2c

The offending code is:

class SomeNSViewSubclass(NSView):

    def initWithFrame_(self, frame):
        self = NSView.initWithFrame_(
            self,
            frame,
        )

        if not self:
            return None

       # ... other initialization code ...

        self._shapeLayer = shapeLayer = CAShapeLayer.layer()
        shapeLayer.setFillColor_(CGColorCreateGenericRGB(1.0, 1.0, 1.0, 0.95)) # fails here

If I am reading the error correctly, it appears that our CAShapeLayer is already GCed by the time we call setFillColor on it.

We’ve seen this a few times with other classes. They all seem related in that they all tend to involve construction objc objects with static functions. I haven’t managed to reproduce anything myself, but we have plenty of stack traces.

Another function that has had similar problems is NSMutableParagraphStyle:

paragraphStyle = NSMutableParagraphStyle.defaultParagraphStyle().mutableCopy()
paragraphStyle.setLineSpacing_(0.0)

I believe there are some others, I can find them if it’s helpful.

Wondering: is this a known issue? Are we doing something wrong?
Thanks

ronaldoussoren commented 4 years ago

Original comment by Ronald Oussoren (Bitbucket: ronaldoussoren, GitHub: ronaldoussoren).


That’s weird. I haven’t seen this issue before. Is this something that’s consistently fails?

ronaldoussoren commented 4 years ago

Original comment by Alex Kritchevsky (Bitbucket: [Alex Kritchevsky](https://bitbucket.org/Alex Kritchevsky), ).


It happens very rarely – we get a few hits per day, maybe, across many users.

ronaldoussoren commented 4 years ago

Original comment by Ronald Oussoren (Bitbucket: ronaldoussoren, GitHub: ronaldoussoren).


That makes it hard to debug the issue, but you wouldn’t have asked if this were an easy problem :wink:.

BTW. Given your description I’d expect that the shape layer is fine, but that the result from `CGColorCreateGenericRGB` is GC-d and set to nil too soon. Or the result of CGColorCreateGenericRGB() is nil, but that shouldn’t happen either (the result ofCGColorCreateGenericRGB is not declared as nullable in the CoreGraphics headers)

I’m not sure why that would be so, the python proxy for the CGColorRef and CAShapeLayer own a reference to native object and that reference gets released when the proxy object is GC-ed. The shape layer is stored as an attribute and shouldn’t be GC-ed at the call, the color proxy will get GC-ed, but only after call to setFillColor_ finishes.

I have read the relevant code again, although not very thoroughly, and that seems correct. The relevant code is in function.m:func_call, and code in corefoundation.m, libffi_support.m and objc_support.m; all in pyobjc-core.

BTW. Have you tried this variation (or even keeping the reference to the color alive in an instance variable):

self._shapeLayer = shapeLayer = CAShapeLayer.layer()
fillColor = CGColorCreateGenericRGB(1.0, 1.0, 1.0, 0.95)
shapeLayer.setFillColor_(fillColor)

I don’t expect any change in behavior, but I wouldn’t expect to see the behavior you are seeing in the first place.