pyobjc
pyobjc copied to clipboard
Python crashes when sending a User Notifcation, zsh: illegal hardware instruction
Describe the bug
I send a notification with the User Notifications framework (UNNotification* superclass) and python crashes, looking like this:
It does not crash Pycharm or interrupt the notification from sending as the notification will appear, however the same pop-up with this stack trace always will show up. Also, I also get a zsh error thrown below as well:
zsh: illegal hardware instruction python3 unnotif.py
I think this has something to do with this but I am not sure where it comes from, I can not find any posts that explain what it is exactly I now docs for now either. I am rather confused after that on what to do, I have not yet compiled it and tried this yet though.
Platform information
- Python Version: 3.9.2
- PyCharm venv: /Users/leif/PycharmProjects/shoutout/venv/bin/python3
- Python installed with Homebrew, however in the HB folder there is no python binary but its in my /Library/Frameworks directory...
- macOS 10.15.7 (Catalina)
- MacBook Pro 2017 Intel
To Reproduce
This is the code: https://pastebin.com/m2t0uDKr
I used Pycharm 2022 and 2021, ran the code with packages installed with PyObj-C 8.5 in the terminal with the venv python binary with the command python3 unnotif.py
.
Expected behavior
What should be happening is that there is no zsh: illegal hardware exception
thrown, and thus probably as well Python not crashing bringing up the popup error every time.
Additional context Nothing too much else
The code:
from uuid import uuid4
import UserNotifications as UN
import Foundation
DEFAULT_AUTH_OPTIIONS = UN.UNAuthorizationOptions(UN.UNAuthorizationOptionBadge)
class NotificationScheduler:
center = UN.UNUserNotificationCenter.currentNotificationCenter()
def __init__(self): # Init objects
self.granted = None
self.settings = None
self.timeInterval = 1 # In minutes
self.repeatNotif = True # Repeat notification? Boolean
self.center.getNotificationSettingsWithCompletionHandler_(self._settingsHandler)
def _haveAuthorization(self, block=False):
if block:
while self.granted is None:
pass
return bool(self.granted)
def _settingsHandler(self, settings):
self.settings = settings
# if settings.authorizationStatus() == UNAuthorizationStatusDenied:
self.center.requestAuthorizationWithOptions_completionHandler_(DEFAULT_AUTH_OPTIIONS,
self._authorizationRequestHandler)
def _authorizationRequestHandler(self, granted, error):
self.granted = granted
print(f"Completion handler granted: {granted}")
if error:
print(error)
def _notificationRequestHandler(self, error):
print("_notificationRequestHandler")
if error:
print(error)
def addNotificationRequest(self, title, body):
if not self._haveAuthorization(block=True):
print("Error: Missing authorization to add notification request.")
return None
# Trigger repeats every minute (may not be using this though)
trigger = UN.UNTimeIntervalNotificationTrigger.triggerWithTimeInterval_repeats_(60, True)
# Making notification content with instance of UNMutableNotificationContent class
content = UN.UNMutableNotificationContent.alloc().init()
content.setTitle_(title)
content.setSubtitle_("Reminder")
content.setBody_(body)
content.setSound_(UN.UNNotificationSound.defaultSound())
content.setCategoryIdentifier_("Shoutout")
# Attachments (logo)
# fileURL = Foundation.NSURL.fileURLWithPath_("/Users/leif/PycharmProjects/shoutout/images/shoutout_logo.png")
# attachments = UN.UNNotificationAttachment.attachmentWithIdentifier_URL_options_error_\
# ("Shoutout_image", fileURL, {}, None)
# content.setAttachments_([attachments])
# Actions for notification
# action_open = UN.UNNotificationAction.actionWithIdentifier_title_options_("open", "Open", [])
# category = UN.UNNotificationCategory.categoryWithIdentifier_actions_intentIdentifiers_options_("Shoutout", [action_open], [])
# self.center.setNotificationCategories_([category])
# Make a random identifier
identifier = str(uuid4())
# Form NotificationRequest object to be sent to current notification center
request = UN.UNNotificationRequest.requestWithIdentifier_content_trigger_(identifier, content, None)
self.center.addNotificationRequest_withCompletionHandler_(request, self._notificationRequestHandler)
if __name__ == "__main__":
notificationScheduler = NotificationScheduler()
notificationScheduler.addNotificationRequest("Shoutout!", "You have a word of the day to check!")
# Time Interval Class
# https://developer.apple.com/documentation/usernotifications/untimeintervalnotificationtrigger?language=objc
This doesn't seem to be a bug in PyObjC itself, but in the interaction between PyObjC and Cocoa during interpreter shutdown. That's something I cannot (easily) fix.
What appears to happen:
- You schedule a notification (
addNotificationRequest:withCompletionHandler:
) - The script exits before this is finished, shutting down the interpreter
- Cocoa calls the completion handler on a secondary thread, but the interpreter is already gone
- BOOM!
APIs like this generally require a running runloop. One way to avoid the crash is to run the Cocoa runloop for a while (e.g. call NSRunLoop.currentRunLoop.run()
and arrange to stop the runloop when you are done).
Thank you man! I put this:
if __name__ == "__main__":
notificationScheduler = NotificationScheduler()
notificationScheduler.addNotificationRequest("Shoutout!", "Reminder", "You have a word of the day to check!")
import Cocoa
loop = Cocoa.NSRunLoop.currentRunLoop() # Returns the run loop for the current thread (once?)
Is this ok? The run
method says it does a permanent loop, so I just added NSRunLoop.currentRunLoop()
and it works fine. It says it,
Returns the run loop for the current thread. Which I think is just 1 time, and I don't think I need to close anything then I think? I don't know how to do that unless I use a untilDate method and put an NSDate object in there.
You also have to arrange for the loop to run for a while, using the runMode_beforeDate_
method. currentRunLoop
just returns the runloop for the current thread.
See also: https://developer.apple.com/documentation/foundation/nsrunloop/1411525-runmode