ronaldoussoren / pyobjc

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

Deallocating objective-C class #619

Open jonashaag opened 2 months ago

jonashaag commented 2 months ago

Describe the bug

When running a piece of code, I get this print:

Deallocating objective-C class VNRecognizeTextRequest

It seems like this is unexpected: https://github.com/ronaldoussoren/pyobjc/blob/b4efea53d01d5715de985a4f28758d0c80a64f76/pyobjc-core/Modules/objc/objc-class.m#L1182-L1188

It seems like this only happens if I do a local import in a function:

def foo():
    import Cocoa, Vision, objc

I can try to come up with a reproducer if requested.

Platform information

PyObjC from conda-forge and from pip

❯ pixi list | grep objc
pyobjc-core                               10.3.1           py312hbb55c70_0           479.6 KiB  conda  pyobjc-core-10.3.1-py312hbb55c70_0.conda
pyobjc-framework-cocoa                    10.3.1           py312hbb55c70_0           370.5 KiB  conda  pyobjc-framework-cocoa-10.3.1-py312hbb55c70_0.conda
pyobjc_framework_coreml                   10.3.1                                     111.8 KiB  pypi   pyobjc_framework_coreml-10.3.1-cp36-abi3-macosx_11_0_universal2.http.whl
pyobjc_framework_quartz                   10.3.1                                     1.6 MiB    pypi   pyobjc_framework_quartz-10.3.1-cp312-cp312-macosx_10_9_universal2.http.whl
pyobjc_framework_vision                   10.3.1                                     156.7 KiB  pypi   pyobjc_framework_vision-10.3.1-cp36-abi3-macosx_11_0_universal2.http.whl

macOS version: 14.5 (23F79)

To Reproduce

Not sure how to reproduce in an isolated piece of code (haven't tried).

Expected behavior

Not sure

ronaldoussoren commented 2 months ago

A reproducer would be nice because this should not happen.

Can you reproduce this in your code when using PyObjC 10.3.0?

jonashaag commented 2 months ago

Reproducer:

from concurrent.futures import ThreadPoolExecutor

def foobar():
    import Vision
    x = Vision.VNRecognizedTextObservation

with ThreadPoolExecutor() as pool:
    for _ in range(2):
        pool.submit(foobar)
ronaldoussoren commented 2 months ago

Thanks for the reproducer.

I'm starting to understand what going on here, but do not have a solution at this point. There is a race condition in the ObjC parts of pyobjc-core in updating some core data structures (caused be reentering the interpreter in a block of my code that thinks its protected from reentrancy by the GIL).

BTW. Your timing in reporting this is spot on, I'm working on making PyObjC compatible with the free threaded build of Python 3.13 and this would have been a real head scratcher if I'd found this while testing the free threaded build...

ronaldoussoren commented 2 months ago

I've committed a fix for this in the master branch, will backport this to the in current stable branch as well.

As a workaround: make sure that classes are imported only on a single thread, such as by importing classes when the module/script is loaded instead of in a nested function.

jonashaag commented 2 months ago

Thanks for the prompt fix!