Skip to content
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

SIGTRAP when using threading events to wait for completion handler #609

Open
torablien opened this issue May 21, 2024 · 4 comments
Open

Comments

@torablien
Copy link

torablien commented May 21, 2024

I'm new to pyobjc so this definitely could be a beginner mistake.

I'm trying to figure out the best way to wait for completion handlers to finish and encapsulate that into a simpler synchronous-like function. I've been testing with threading.Event and although it generally works, I've noticed that if the program exits immediately after, I see a SIGTRAP (zsh: trace trap ...). Is there a proper/better way to do this?

import threading


def get_shareable_applications():
    from ScreenCaptureKit import SCShareableContent  # type: ignore

    shareable_applications = []

    completion_event = threading.Event()

    def shareable_content_completion_handler(shareable_content, error):
        try:
            nonlocal shareable_applications
            shareable_applications = shareable_content.applications()
            # Potentially more stuff here including calls to other methods with completion handlers
        finally:
            completion_event.set()

    SCShareableContent.getShareableContentWithCompletionHandler_(
        shareable_content_completion_handler
    )

    completion_event.wait()

    return shareable_applications


def main():
    get_shareable_applications()


main()

Running Python 3.12 on M1 Pro 14.1.2

Thanks!

@ronaldoussoren
Copy link
Owner

Completion handlers in general only get called when there's an active Cocoa event loop (the exception being when you can specify a dispatch queue, those often send work to a background thread and call the completion handler on a background thread as well).

That's not the case here though. The completion handler gets called in a background thread and there appears to be a race condition between cleaning up on the background thread (which is a daemon thread as far as the CPython runtime is concerned) and the main thread exiting.

A quick workaround for this is to add a short sleep after waiting for the completion_event. On my system time.sleep(0.1) worked reliably, but YMMV. With some luck the sleep isn't necessary when you integrate this in a larger code base.

I'm keeping the issue open because I want to do further research to see if I can avoid the crash (or have to file an issue with the cpython project).

@torablien
Copy link
Author

Thanks @ronaldoussoren - are there any example implementations or recommendations on how to work with completion handlers?

I tried some simple implementations with AppHelper.runConsoleEventLoop(), Foundation.CFRunLoopRun(), but couldn't get them to work well/robustly (e.g. terminating program, hanging if there is an error). Python's threading.Event somewhat works but is also running into issues (e.g. sometimes some completion handlers seem to never get called and the Event times out, but something lingers that slowly bring my machine to a halt). I get the sense that this is not the best way to do this - if you have any recommendations, that would be greatly appreciated!

@ronaldoussoren
Copy link
Owner

Thanks @ronaldoussoren - are there any example implementations or recommendations on how to work with completion handlers?

I tried some simple implementations with AppHelper.runConsoleEventLoop(), Foundation.CFRunLoopRun(), but couldn't get them to work well/robustly (e.g. terminating program, hanging if there is an error). Python's threading.Event somewhat works but is also running into issues (e.g. sometimes some completion handlers seem to never get called and the Event times out, but something lingers that slowly bring my machine to a halt). I get the sense that this is not the best way to do this - if you have any recommendations, that would be greatly appreciated!

I don't have a good solution at this moment, and is something I want to research over the summer anyway because issues like this will crop up more given Apple's switch to Swift and the concurrency model of Swift (which is spelled as "async/await" but uses pervasive threading)

I hope to have some time over the summer to finish some work on asyncio integration for PyObjC, including adding async methods for all Objective-C selectors with a completion handler argument. This should help hide most of the complexity in shared code, and should also result in cleaner code in scripts.

That should end up with code like this for your problem:

import asyncio
from ScreenCaptureKit import SCShareableContent

async def get_shareable_applications():
     return await SCShareableContent.getShareableContent()

asyncio.run(get_shareable_applications())

I still have to work out a good design though, and this likely requires using a PyObjC implementation of an asyncio runloop in general (but not necessarily in this use case).

@torablien
Copy link
Author

Thanks, appreciate it. If you come across a good workaround, I'd be interested in trying it. An API for these kinds of situations (e.g. async methods) would be great.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants