From 1e6bcafd1075effae6ef2469a17936f3b56e10f9 Mon Sep 17 00:00:00 2001 From: Ricci Adams <1097294+iccir@users.noreply.github.com> Date: Fri, 5 Jun 2026 19:08:48 -0700 Subject: [PATCH] FIX: memory management within macos backend Fix several MacOS memory management bugs. Use more auto release pools on every transition from Python to objc. Add error handling to turn objc exceptions into Python exceptions within these blocks. --- src/_macosx.m | 330 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 231 insertions(+), 99 deletions(-) diff --git a/src/_macosx.m b/src/_macosx.m index 62daf43abe1b..0de0540018a7 100755 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -21,6 +21,37 @@ #define WINDOW_CLOSING 3 +/* When calling into Objective-C from Python, wrap the calls with + BEGIN_OBJC_ENTRY and END_OBJC_ENTRY. This will set up an autorelease + pool as well as catch any Obj-C exceptions thrown. These macros + should be used for any call exposed to Python via the external module + interface. + + To avoid undefined behavior, each END_OBJC_ENTRY should be followed + by a return statement which handles the rare case when an Objective-C + exception was thrown. + + As a convenience, the RETURN_NULL_OR_NONE macro can be used for functions + that return a PyObject* */ +#define BEGIN_OBJC_ENTRY \ + @autoreleasepool { @try { + +#define END_OBJC_ENTRY \ + } @catch (NSException *e) { errSetException(e); } } + +#define RETURN_NULL_OR_NONE \ + if (PyErr_Occurred()) { \ + return NULL; \ + } else { \ + Py_RETURN_NONE; \ + } + + +/* Variable for our delegate since it has a +1 reference count. + Not needed under manual reference count, but standard practice + under ARC. */ +static id appDelegate = nil; + /* Keep track of number of windows present Needed to know when to stop the NSApp */ static long FigureWindowCount = 0; @@ -43,6 +74,11 @@ // Global variable to store the original SIGINT handler static PyOS_sighandler_t originalSigintAction = NULL; +// Convert an Objective-C exception into a Python RuntimeError +static void errSetException(NSException *exception) { + PyErr_SetString(PyExc_RuntimeError, [[exception reason] UTF8String]); +} + // Stop the current app's run loop, sending an event to ensure it actually stops static void stopWithEvent() { [NSApp stop: nil]; @@ -73,57 +109,64 @@ static void handleSigint(int signal) { // It is used in the input hook as well as wrapped in a version callable from Python. static void flushEvents() { while (true) { - NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny - untilDate: [NSDate distantPast] - inMode: NSDefaultRunLoopMode - dequeue: YES]; - if (!event) { - break; + @autoreleasepool { + NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny + untilDate: [NSDate distantPast] + inMode: NSDefaultRunLoopMode + dequeue: YES]; + if (!event) { + break; + } + [NSApp sendEvent:event]; } - [NSApp sendEvent:event]; } } static int wait_for_stdin() { + BEGIN_OBJC_ENTRY + // Short circuit if no windows are active // Rely on Python's input handling to manage CPU usage // This queries the NSApp, rather than using our FigureWindowCount because that is decremented when events still // need to be processed to properly close the windows. - if (![[NSApp windows] count]) { - flushEvents(); - return 1; + @autoreleasepool { + if (![[NSApp windows] count]) { + flushEvents(); + return 1; + } } - @autoreleasepool { - // Set up a SIGINT handler to interrupt the event loop if ctrl+c comes in too - originalSigintAction = PyOS_setsig(SIGINT, handleSigint); + // Set up a SIGINT handler to interrupt the event loop if ctrl+c comes in too + originalSigintAction = PyOS_setsig(SIGINT, handleSigint); - // Create an NSFileHandle for standard input - NSFileHandle *stdinHandle = [NSFileHandle fileHandleWithStandardInput]; + // Create an NSFileHandle for standard input + NSFileHandle *stdinHandle = [NSFileHandle fileHandleWithStandardInput]; - // Register for data available notifications on standard input - id notificationID = [[NSNotificationCenter defaultCenter] addObserverForName: NSFileHandleDataAvailableNotification - object: stdinHandle - queue: [NSOperationQueue mainQueue] // Use the main queue - usingBlock: ^(NSNotification *notification) {stopWithEvent();} - ]; + // Register for data available notifications on standard input + id notificationID = [[NSNotificationCenter defaultCenter] addObserverForName: NSFileHandleDataAvailableNotification + object: stdinHandle + queue: [NSOperationQueue mainQueue] // Use the main queue + usingBlock: ^(NSNotification *notification) {stopWithEvent();} + ]; - // Wait in the background for anything that happens to stdin - [stdinHandle waitForDataInBackgroundAndNotify]; + // Wait in the background for anything that happens to stdin + [stdinHandle waitForDataInBackgroundAndNotify]; - // Run the application's event loop, which will be interrupted on stdin or SIGINT - [NSApp run]; + // Run the application's event loop, which will be interrupted on stdin or SIGINT + [NSApp run]; - // Remove the input handler as an observer - [[NSNotificationCenter defaultCenter] removeObserver: notificationID]; + // Remove the input handler as an observer + [[NSNotificationCenter defaultCenter] removeObserver: notificationID]; - // Restore the original SIGINT handler upon exiting the function - PyOS_setsig(SIGINT, originalSigintAction); + // Restore the original SIGINT handler upon exiting the function + PyOS_setsig(SIGINT, originalSigintAction); - return 1; - } + return 1; + + END_OBJC_ENTRY + return 0; } /* ---------------------------- Cocoa classes ---------------------------- */ @@ -225,7 +268,8 @@ static void lazy_init(void) { NSApp = [NSApplication sharedApplication]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; - [NSApp setDelegate: [[[MatplotlibAppDelegate alloc] init] autorelease]]; + appDelegate = [[MatplotlibAppDelegate alloc] init]; + [NSApp setDelegate:appDelegate]; // Run our own event loop while waiting for stdin on the Python side // this is needed to keep the application responsive while waiting for input @@ -235,21 +279,26 @@ static void lazy_init(void) { static PyObject* event_loop_is_running(PyObject* self) { + BEGIN_OBJC_ENTRY + if (backend_inited) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } + + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* wake_on_fd_write(PyObject* unused, PyObject* args) { + BEGIN_OBJC_ENTRY int fd; if (!PyArg_ParseTuple(args, "i", &fd)) { return NULL; } NSFileHandle* fh = [[NSFileHandle alloc] initWithFileDescriptor: fd]; - [fh waitForDataInBackgroundAndNotify]; - [[NSNotificationCenter defaultCenter] + __block id notificationID = [[NSNotificationCenter defaultCenter] addObserverForName: NSFileHandleDataAvailableNotification object: fh queue: nil @@ -257,15 +306,21 @@ static void lazy_init(void) { PyGILState_STATE gstate = PyGILState_Ensure(); PyErr_CheckSignals(); PyGILState_Release(gstate); + [fh release]; + [[NSNotificationCenter defaultCenter] removeObserver:notificationID]; }]; - Py_RETURN_NONE; + [fh waitForDataInBackgroundAndNotify]; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* stop(PyObject* self, PyObject* _ /* ignored */) { + BEGIN_OBJC_ENTRY stopWithEvent(); - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static CGFloat _get_device_scale(CGContextRef cr) @@ -343,16 +398,22 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) static PyObject* FigureCanvas_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY + lazy_init(); FigureCanvas *self = (FigureCanvas*)type->tp_alloc(type, 0); if (!self) { return NULL; } self->view = [View alloc]; return (PyObject*)self; + + END_OBJC_ENTRY + return NULL; } static int FigureCanvas_init(FigureCanvas *self, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY if (!self->view) { PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return -1; @@ -392,14 +453,18 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) Py_XDECREF(super_init); Py_XDECREF(init_res); Py_XDECREF(wh); + + END_OBJC_ENTRY return PyErr_Occurred() ? -1 : 0; } static void FigureCanvas_dealloc(FigureCanvas* self) { + BEGIN_OBJC_ENTRY [self->view setCanvas: NULL]; [self->view release]; + END_OBJC_ENTRY Py_TYPE(self)->tp_free((PyObject*)self); } @@ -413,13 +478,16 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) static PyObject* FigureCanvas_update(FigureCanvas* self) { + BEGIN_OBJC_ENTRY [self->view setNeedsDisplay: YES]; - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE; } static PyObject* FigureCanvas_flush_events(FigureCanvas* self) { + BEGIN_OBJC_ENTRY // We run the app, matching any events that are waiting in the queue // to process, breaking out of the loop when no events remain and // displaying the canvas if needed. @@ -430,12 +498,14 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) Py_END_ALLOW_THREADS [self->view displayIfNeeded]; - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureCanvas_set_cursor(PyObject* unused, PyObject* args) { + BEGIN_OBJC_ENTRY int i; if (!PyArg_ParseTuple(args, "i", &i)) { return NULL; } switch (i) { @@ -455,12 +525,14 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) case 7: [[NSCursor resizeUpDownCursor] set]; break; default: return NULL; } - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureCanvas_set_rubberband(FigureCanvas* self, PyObject *args) { + BEGIN_OBJC_ENTRY View* view = self->view; if (!view) { PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); @@ -477,19 +549,23 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) NSRect rubberband = NSMakeRect(x0 < x1 ? x0 : x1, y0 < y1 ? y0 : y1, abs(x1 - x0), abs(y1 - y0)); [view setRubberband: rubberband]; - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureCanvas_remove_rubberband(FigureCanvas* self) { + BEGIN_OBJC_ENTRY [self->view removeRubberband]; - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureCanvas__start_event_loop(FigureCanvas* self, PyObject* args, PyObject* keywords) { + BEGIN_OBJC_ENTRY float timeout = 0.0; static char* kwlist[] = {"timeout", NULL}; @@ -502,23 +578,27 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) NSDate* date = (timeout > 0.0) ? [NSDate dateWithTimeIntervalSinceNow: timeout] : [NSDate distantFuture]; - while (true) - { NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny - untilDate: date - inMode: NSDefaultRunLoopMode - dequeue: YES]; - if (!event || [event type]==NSEventTypeApplicationDefined) { break; } - [NSApp sendEvent: event]; + while (true) { + @autoreleasepool { + NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny + untilDate: date + inMode: NSDefaultRunLoopMode + dequeue: YES]; + if (!event || [event type]==NSEventTypeApplicationDefined) { break; } + [NSApp sendEvent: event]; + } } Py_END_ALLOW_THREADS - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureCanvas_stop_event_loop(FigureCanvas* self) { + BEGIN_OBJC_ENTRY // +[NSEvent otherEventWithType:...] is declared nullable but will not return // nil for these constant, valid arguments; guard defensively anyway. NSEvent* event = [NSEvent otherEventWithType: NSEventTypeApplicationDefined @@ -533,7 +613,8 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) if (event) { [NSApp postEvent: event atStart: true]; } - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyTypeObject FigureCanvasType = { @@ -591,6 +672,7 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) static PyObject* FigureManager_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY if (![NSThread isMainThread]) { PyErr_SetString( PyExc_RuntimeError, @@ -612,11 +694,14 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) self->window = window; ++FigureWindowCount; return (PyObject*)self; + END_OBJC_ENTRY + return NULL; } static int FigureManager_init(FigureManager *self, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY PyObject* canvas; if (!PyArg_ParseTuple(args, "O", &canvas)) { return -1; @@ -649,15 +734,18 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) Window* window = self->window; [window setDelegate: view]; [window makeFirstResponder: view]; + [window setReleasedWhenClosed:NO]; [[window contentView] addSubview: view]; [view updateDevicePixelRatio: [window backingScaleFactor]]; + END_OBJC_ENTRY return 0; } static PyObject* FigureManager__set_window_mode(FigureManager* self, PyObject* args) { + BEGIN_OBJC_ENTRY const char* window_mode; if (!PyArg_ParseTuple(args, "s", &window_mode) || !self->window) { return NULL; @@ -671,7 +759,8 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) } else { // system settings [self->window setTabbingMode: NSWindowTabbingModeAutomatic]; } - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* @@ -684,28 +773,39 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) static void FigureManager_dealloc(FigureManager* self) { + BEGIN_OBJC_ENTRY [self->window close]; + [self->window setDelegate:nil]; + [self->window release]; + END_OBJC_ENTRY Py_TYPE(self)->tp_free((PyObject*)self); } static PyObject* FigureManager__show(FigureManager* self) { + BEGIN_OBJC_ENTRY [self->window makeKeyAndOrderFront: nil]; - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureManager__raise(FigureManager* self) { + BEGIN_OBJC_ENTRY [self->window orderFrontRegardless]; - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureManager_destroy(FigureManager* self) { + BEGIN_OBJC_ENTRY [self->window close]; + [self->window setDelegate:nil]; + [self->window release]; self->window = NULL; // call super(self, FigureManager).destroy() - it seems we need the @@ -726,11 +826,13 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) } Py_DECREF(result); - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureManager_set_icon(PyObject* null, PyObject* args) { + BEGIN_OBJC_ENTRY PyObject* icon_path; if (!PyArg_ParseTuple(args, "O&", &PyUnicode_FSDecoder, &icon_path)) { return NULL; @@ -740,38 +842,35 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) Py_DECREF(icon_path); return NULL; } - @autoreleasepool { - NSString* ns_icon_path = [NSString stringWithUTF8String: icon_path_ptr]; - Py_DECREF(icon_path); - if (!ns_icon_path) { - PyErr_SetString(PyExc_RuntimeError, "Could not convert to NSString*"); - return NULL; - } - NSImage* image = [[[NSImage alloc] initByReferencingFile: ns_icon_path] autorelease]; - if (!image) { - PyErr_SetString(PyExc_RuntimeError, "Could not create NSImage*"); - return NULL; - } - if (!image.valid) { - PyErr_SetString(PyExc_RuntimeError, "Image is not valid"); - return NULL; - } - @try { - NSApplication* app = [NSApplication sharedApplication]; - app.applicationIconImage = image; - } - @catch (NSException* exception) { - PyErr_SetString(PyExc_RuntimeError, exception.reason.UTF8String); - return NULL; - } + + NSString* ns_icon_path = [NSString stringWithUTF8String: icon_path_ptr]; + Py_DECREF(icon_path); + if (!ns_icon_path) { + PyErr_SetString(PyExc_RuntimeError, "Could not convert to NSString*"); + return NULL; + } + NSImage* image = [[[NSImage alloc] initByReferencingFile: ns_icon_path] autorelease]; + if (!image) { + PyErr_SetString(PyExc_RuntimeError, "Could not create NSImage*"); + return NULL; + } + if (!image.valid) { + PyErr_SetString(PyExc_RuntimeError, "Image is not valid"); + return NULL; } - Py_RETURN_NONE; + + NSApplication* app = [NSApplication sharedApplication]; + app.applicationIconImage = image; + + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureManager_set_window_title(FigureManager* self, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY const char* title; if (!PyArg_ParseTuple(args, "s", &title)) { return NULL; @@ -780,23 +879,26 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) // not return nil here; the nullable annotation is a false positive. // NOLINTNEXTLINE(clang-analyzer-nullability.NullablePassedToNonnull) [self->window setTitle: [NSString stringWithUTF8String: title]]; - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureManager_get_window_title(FigureManager* self) { + BEGIN_OBJC_ENTRY NSString* title = [self->window title]; if (title) { return PyUnicode_FromString([title UTF8String]); - } else { - Py_RETURN_NONE; } + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureManager_resize(FigureManager* self, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY int width, height; if (!PyArg_ParseTuple(args, "ii", &width, &height)) { return NULL; @@ -809,14 +911,17 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) // 36 comes from hard-coded size of toolbar later in code [window setContentSize: NSMakeSize(width, height + 36.)]; } - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureManager_full_screen_toggle(FigureManager* self) { + BEGIN_OBJC_ENTRY [self->window toggleFullScreen: nil]; - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyTypeObject FigureManagerType = { @@ -938,6 +1043,7 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } static PyObject* NavigationToolbar2_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY lazy_init(); NavigationToolbar2Handler* handler = [NavigationToolbar2Handler alloc]; if (!handler) { return NULL; } @@ -948,11 +1054,14 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } } self->handler = handler; return (PyObject*)self; + END_OBJC_ENTRY + return NULL; } static int NavigationToolbar2_init(NavigationToolbar2 *self, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY FigureCanvas* canvas; const char* images[7]; const char* tooltips[7]; @@ -1062,14 +1171,17 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } [[window contentView] display]; self->messagebox = messagebox; + END_OBJC_ENTRY return 0; } static void NavigationToolbar2_dealloc(NavigationToolbar2 *self) { + BEGIN_OBJC_ENTRY [self->handler release]; [self->messagebox release]; + END_OBJC_ENTRY Py_TYPE(self)->tp_free((PyObject*)self); } @@ -1082,6 +1194,7 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } static PyObject* NavigationToolbar2_set_message(NavigationToolbar2 *self, PyObject* args) { + BEGIN_OBJC_ENTRY const char* message; if (!PyArg_ParseTuple(args, "s", &message)) { return NULL; } @@ -1113,7 +1226,8 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } [[messagebox.superview window] disableCursorRects]; } - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyTypeObject NavigationToolbar2Type = { @@ -1139,6 +1253,8 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } static PyObject* choose_save_file(PyObject* unused, PyObject* args) { + BEGIN_OBJC_ENTRY + int result; const char* title; const char* directory; @@ -1162,7 +1278,9 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } } return PyUnicode_FromString([filename UTF8String]); } - Py_RETURN_NONE; + + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } @implementation Window @@ -1775,17 +1893,26 @@ - (void)flagsChanged:(NSEvent *)event static PyObject* show(PyObject* self) { - [NSApp activateIgnoringOtherApps: YES]; - NSArray *windowsArray = [NSApp windows]; - NSEnumerator *enumerator = [windowsArray objectEnumerator]; - NSWindow *window; - while ((window = [enumerator nextObject])) { - [window orderFront:nil]; + BEGIN_OBJC_ENTRY + + // Iterating over -[NSApp windows] will add the windows to the topmost + // autorelease pool, wrap in @autoreleasepool as -[NSApp run] is long-running. + @autoreleasepool { + [NSApp activateIgnoringOtherApps: YES]; + NSArray *windowsArray = [NSApp windows]; + NSEnumerator *enumerator = [windowsArray objectEnumerator]; + NSWindow *window; + while ((window = [enumerator nextObject])) { + [window orderFront:nil]; + } } + Py_BEGIN_ALLOW_THREADS [NSApp run]; Py_END_ALLOW_THREADS - Py_RETURN_NONE; + + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } typedef struct { @@ -1797,6 +1924,7 @@ - (void)flagsChanged:(NSEvent *)event static PyObject* Timer_new(PyTypeObject* type, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY lazy_init(); Timer* self = (Timer*)type->tp_alloc(type, 0); if (!self) { @@ -1804,6 +1932,8 @@ - (void)flagsChanged:(NSEvent *)event } self->timer = NULL; return (PyObject*) self; + END_OBJC_ENTRY + return NULL; } static PyObject* @@ -1816,6 +1946,7 @@ - (void)flagsChanged:(NSEvent *)event static PyObject* Timer__timer_start(Timer* self, PyObject* args) { + BEGIN_OBJC_ENTRY NSTimeInterval interval; PyObject* py_interval = NULL, * py_single = NULL, * py_on_timer = NULL; int single; @@ -1850,11 +1981,8 @@ - (void)flagsChanged:(NSEvent *)event Py_XDECREF(py_interval); Py_XDECREF(py_single); Py_XDECREF(py_on_timer); - if (PyErr_Occurred()) { - return NULL; - } else { - Py_RETURN_NONE; - } + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static void @@ -1869,14 +1997,18 @@ - (void)flagsChanged:(NSEvent *)event static PyObject* Timer__timer_stop(Timer* self) { + BEGIN_OBJC_ENTRY Timer__timer_stop_impl(self); - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static void Timer_dealloc(Timer* self) { + BEGIN_OBJC_ENTRY Timer__timer_stop_impl(self); + END_OBJC_ENTRY Py_TYPE(self)->tp_free((PyObject*)self); }