launching a modal dialog in windowDidBecomeMain crashing in macOS 10.13

As of macOS 10.13, we are starting to see some crashes in our app related to launching a dialog during a windowDidBecomeMain, whether via [NSApp runModalForWindow:] or [NSAlert runModal]. This has worked up until 10.13 (in 10.12 we didn't crash but often there would be drawing anomolies like the window title bar not drawing, etc., and in 10.11/10.10 everything appeared to draw fine), but as we have been looking into it, we have noticed some messages in the console like this as well:


-[NSAlert runModal] may not be invoked inside of transaction begin/commit pair, or inside of transaction commit (usually this means it was invoked inside of a view's -drawRect: method.)


Unfortunately, I can't see that particular console message with my current steps to reproduce this crash, but I suspect it is a similar issue (I have gottoen that console message with a simple Cocoa project I have used to reproduce the issue). We are not in a drawRect call but the stack is showing that we are possibly inside a transaction (the CA::Transaction::assert_inactive() line?).


Any suggestions on how to fix? I have seen suggestions out there on related posts about using performSelector:withObject:afterDelay: or [[NSOperationQueue mainQueue] addOperationWithBlock] but I'm really trying to understand if we are breaking documented rules by attempting to launch modal dialog in windowDidBecomeMain (partly because a more general fix has other implications for our app that I won't go into here, but we could band-aid this in the one problematic place with either performSelector/addOperationWithBlock).


#0 0x00007fff6d550fce in __pthread_kill ()

#1 0x0000000105aa21c4 in pthread_kill ()

#2 0x00007fff6d4ad32a in abort ()

#3 0x00007fff5100fd89 in CA::Transaction::assert_inactive() ()

#4 0x00007fff436ed52d in -[NSApplication runModalForWindow:] ()

#5 0x00000001005a90c7 in edlg::CocoaRunAppModalLoopForWindow(EDLG)

#6 0x00000001004fefe7 in edlg::FinaleModelessDialog(EWND, short, long (*)(EDLG, unsigned int, long, long, void*), void*, int, short, bool, int*, __CFBundle*)

#7 0x00000001004fe7a7 in edlg::FinaleModalDialogInt(EWND, short, long (*)(EDLG, unsigned int, long, long, void*), void*, int, __CFBundle*)

#8 0x0000000100005d70 in fcdlg::FCMacDialog::DoModal()

#9 0x0000000101151b8e in escrb::HyperscribeToolProc(FINTOOLMSG, long)

#10 0x000000010053c21f in DispatchMessage(ftool::FINTOOLID, FINTOOLMSG, long)

#11 0x0000000101b8184a in ftool::FinaleTool::SendMessage(FINTOOLMSG, long)

#12 0x0000000100053237 in ftool::FinaleTool::DoToolInitHandler(ftool::TOOL_INIT_MODE)

#13 0x0000000101b7f604 in ftool::FinaleTool::DoToolInit(ftool::TOOL_INIT_MODE, bool)

#14 0x000000010053bdb5 in ToolAccess::OpenCurrentTool(bool)

#15 0x00000001002a6de4 in OpenCurrentTool(bool)

#16 0x0000000100349c72 in ::-[MusicWindowController windowGainedFocus]()

#17 0x00000001003d87d5 in ::-[FCMacDocumentController setAsCurrentDoc:](MusicWindowController *)

#18 0x00000001003d8dd6 in ::-[FCMacDocumentController windowDidBecomeMain:](NSNotification *)

#19 0x00007fff45eb5b5c in __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ ()

#20 0x00007fff45eb5a4a in _CFXRegistrationPost ()

#21 0x00007fff45eb5792 in ___CFXNotificationPost_block_invoke ()

#22 0x00007fff45e73570 in -[_CFXNotificationRegistrar find:object:observer:enumerator:] ()

#23 0x00007fff45e726a3 in _CFXNotificationPost ()

#24 0x00007fff47f31477 in -[NSNotificationCenter postNotificationName:object:userInfo:] ()

#25 0x00007fff435df8e5 in -[NSWindow _changeKeyAndMainLimitedOK:] ()

#26 0x00007fff4368ce9a in -[NSWindow _makeKeyRegardlessOfVisibility] ()

#27 0x00007fff435e28f4 in NSPerformVisuallyAtomicChange ()

#28 0x00007fff4368cdd1 in -[NSWindow makeKeyAndOrderFront:] ()

#29 0x00007fff50e6d121 in -[QLSeamlessDocumentOpener showWindow:contentFrame:withBlock:] ()

#30 0x00007fff4368bffa in -[NSWindowController showWindow:] ()

#31 0x00007fff434cc5d2 in -[NSDocument showWindows] ()

#32 0x00007fff43a21dae in -[NSDocumentController(NSDeprecated) openDocumentWithContentsOfURL:display:error:] ()

#33 0x00000001003d73de in ::-[FCMacDocumentController openDocumentWithContentsOfURL:display:documentAlreadyOpen:error:](NSURL *, BOOL, BOOL *, NSError **)

#34 0x00000001003d7f44 in ::-[FCMacDocumentController newUntitledDocFromURL:display:isDefault:documentAlreadyOpen:](NSURL *, BOOL, BOOL, BOOL *)

#35 0x0000000100c85e9a in DoNew(bool, bool, FinUString const&, bool*)

#36 0x00000001003ba38f in fcdlg::FinaleLaunchDialog::FLD_DoDefaultDoc()

#37 0x00000001003ba2dd in fcdlg::FinaleLaunchDialog::DoFinaleLaunchDialogAction(fcdlg::FinaleLaunchDialog::LAUNCH_ACTION_CHOICE)

#38 0x00000001003ba24e in fcdlg::FinaleLaunchDialog::DoFinaleLaunchDialog()

#39 0x0000000100de4fa3 in DoStartupAction()

#40 0x0000000100af2dde in ::-[FinaleAppDelegate applicationDidFinishLaunching:](NSNotification *)

#41 0x00007fff45eb5b5c in __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ ()

#42 0x00007fff45eb5a4a in _CFXRegistrationPost ()

#43 0x00007fff45eb5792 in ___CFXNotificationPost_block_invoke ()

#44 0x00007fff45e73570 in -[_CFXNotificationRegistrar find:object:observer:enumerator:] ()

#45 0x00007fff45e726a3 in _CFXNotificationPost ()

#46 0x00007fff47f31477 in -[NSNotificationCenter postNotificationName:object:userInfo:] ()

#47 0x00007fff435efef2 in -[NSApplication _postDidFinishNotification] ()

#48 0x00007fff434b2fbb in -[NSApplication finishLaunching] ()

#49 0x00007fff434b205b in -[NSApplication run] ()

#50 0x00007fff434813fe in NSApplicationMain ()

#51 0x00000001003e7096 in main

#52 0x00007fff6d401145 in start ()

#53 0x00007fff6d401145 in start ()

I don't know enough of the inner workings of NSWindow to have an explanation (some experts here certainly have).


But the message you got in 10.12 seems pretty clear. There's a conflcit with what windowDidBecomeMain is doing and setting an NSAlert that makes the window looses its main status.

I thought you could try to put the alert in windowDidBecomeKey, but the same problem should logically occur (could be worth a try).


So question: do you absolutely need to have this alert called from WindowDidBecomeMain ?

From a user point of view, getting an alert each time the window becomes main would probablybe annoying (so I assume you call this alert on some conditions only).


I have a suggestion:

- post a notification from withing windowDidBecomeMain

- handle the alert in the answer to notification.


Hope that works, I've not tested.

Strange I'm getting a very similar error as well here is the block of code I've narrowed it down to:



- (void)windowDidBecomeKey:(NSNotification *)notification {

if (notification.object == self.window) {

if (!self.window.isModalPanel) {

modelSession = [NSApp beginModalSessionForWindow:self.window];

[NSApp runModalSession:modelSession];

}

}

}


With the same error message:

[General] -[NSApplication runModalSession:] may not be invoked inside of transaction begin/commit pair, or inside of transaction commit (usually this means it was invoked inside of a view's -drawRect: method.)


The exception is thrown as soon as I step through runModalSession


I'm more of an iOS guy so thie legacy macOS is tough to understand. Are you suggesting I should post a notification within windowDidBecomeKey? It seems weird that windowDidBecomeKey is already responding to a notification and I am in turn posting another.

Posting a notification won't help. Notification observers are called synchronously before the post operation returns, so the problematic context (from AppKit's point of view) would be just the same. That is, it would still be inside of a transaction begin/commit pair.


File a bug with Apple. It may really be a bug on their part or maybe they'll tell you you've never been allowed to do what you're doing and it's just being enforced more reliably now.


If you need to work around it in the meantime, deferring the operation as previously suggested is probably your only course.

OK, that doesn't work, Ken had the explanation why.


What I do usually, is call the alert in a queue (to avoid the message you had):


                        DispatchQueue.main.async { // Needed to avoid execution warning OSX 10.12 : [NSAlert runModal] may not be invoked inside of transaction commit (usually this means it was invoked inside of a view's -drawRect: method.) The modal dialog has been suppressed to avoid deadlock.
                            let alert = NSAlert()

                           // build the alert
                            let modalResponse = alert.runModal()
                            if (modalResponse == .alertSecondButtonReturn) {
                                // handle appropriately
                            }
                        }

<<so I assume you call this alert on some conditions only>>

That is correct. It only happens one time and only in a specific set of conditions.

Sorry, forgot to mention that I had filed a bug with Apple on this particular issue before posting here. Sometimes filing a bug with Apple feels like throwing it into an abyss, so wanted to get some discussion going here as well 🙂

This was our backup plan from the start as I mentioned in the original post. I hesitate to mark any of this "correct answer" as this is basically a work around for an issue that doesn't seem to be well documented and could be a bug on Apple's part. I'll post back here if I hear anything from Apple on the bug we filed.

I don't see it as a bug. Apple warned developer in 10.12 that this was a problem.


[General] -[NSApplication runModalSession:] may not be invoked inside of transaction begin/commit pair, or inside of transaction commit (usually this means it was invoked inside of a view's -drawRect: method.)


So it may not desired, but it is an intented (insufficiently documented maybe ?) behavior.

Hi ccianflone,


Thank you for filing a Radar, and for providing a good repro case. I'll make sure it's looked at by the correct engineers.


In the meantime, if it's appropriate for your app's logic, you can likely work around this error by running the modal dialog on a subsequent pass of the main runloop, using e.g.

-[NSRunLoop performBlock:]
or a similar mechanism.

The same issue occurs in MacOS 12.1 for my app (written in Swift using Xcode 13.3) during the NSTextView's delegate method:

func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool
{
    return askUserIfOkayToEndTextEditing()          // Causes a crash the moment the NSAlert is run modally
}

The crash reports on the terminal:

2024-01-03 09:42:35.394910-0700 MyApp[23691:765726] [General] An uncaught exception was raised
2024-01-03 09:42:35.394941-0700 MyApp[23691:765726] [General] -[NSApplication runModalForWindow:] may not be 
invoked inside of transaction begin/commit pair, or inside of transaction commit (usually this means it was
invoked inside of a view's -drawRect: method.)

I've not tested this on later systems. What's weird is that putting up the alert used to work and now doesn't, even though I've not updated my MacOS system recently. So it seems some other condition is triggering the crash. Currently a mystery, but it would be nice to know from Apple what the best practice is for putting up an alert such as this as soon as possible to know how to proceed. The problem is that the delegate method seems completely like the right place to put up such an alert. Alas, not.

launching a modal dialog in windowDidBecomeMain crashing in macOS 10.13
 
 
Q