FB9857161: NSAlert crashes app if called from a Task
- Date: 2022-01-24
- Resolution: Open
- Area: AppKit
- OS: macOS 12.1
- Type: Application Crash
Description
NSAlert crashes when called from a Task, even if you use @MainActor. This is a pretty serious issue as a lot of code uses NSAlert.
I have attached a sample project that reproduces the issue.
Task { @MainActor in
let alert = NSAlert()
alert.messageText = "X"
alert.runModal()
}
Stack trace:
Thread 1 Queue : com.apple.main-thread (serial)
#0 0x00000001c7c37a18 in libunwind::UnwindCursor<libunwind::LocalAddressSpace, libunwind::Registers_arm64>::step() ()
#1 0x00000001bd0b762c in objc_addExceptionHandler ()
#2 0x00000001c00e5e80 in -[NSApplication _commonBeginModalSessionForWindow:relativeToWindow:modalDelegate:didEndSelector:contextInfo:] ()
#3 0x00000001c00e5a50 in __35-[NSApplication runModalForWindow:]_block_invoke_2 ()
#4 0x00000001c00e5a08 in __35-[NSApplication runModalForWindow:]_block_invoke ()
#5 0x00000001c00e5198 in _NSTryRunModal ()
#6 0x00000001c00e5048 in -[NSApplication runModalForWindow:] ()
#7 0x00000001c024e218 in __19-[NSAlert runModal]_block_invoke_2 ()
#8 0x00000001c024e15c in __19-[NSAlert runModal]_block_invoke ()
#9 0x00000001c00e5198 in _NSTryRunModal ()
#10 0x00000001c016f694 in -[NSAlert runModal] ()
#11 0x0000000104b69ddc in closure #1 in ViewController.viewDidLoad()
Files
Hacky workaround:
extension NSAlert {
/**
Workaround to allow using `NSAlert` in a `Task`.
[FB9857161](https://github.com/feedback-assistant/reports/issues/288)
*/
@MainActor
@discardableResult
func run() async -> NSApplication.ModalResponse {
await withCheckedContinuation { continuation in
DispatchQueue.main.async { [self] in
continuation.resume(returning: runModal())
}
}
}
}
+1. (Encountered the issue when refactoring to swift-concurrency)
Neither MainActor.run { alert.runModal() }, Task { await alert.runModal() } and Task { @MainActor alert.runModal()} works for me. The same crash and stack trace.
Thanks for your workaround. But it still seems tricky for me.
I can't call it in MainActor.run{ } since there is no concurrency context(can't use await in it).
And I can't call it plain Task context like Task { ... await alert.runModal } because NSAlert() related method are all marked with @MainActor. So I need the following.
Task {
let alert = await NSAlert()
await alert.messageText = "X"
await alert.run()
}
And the final answer is using the above code you provide which is
Task { @MainActor in
let alert = NSAlert()
alert.messageText = "X"
await alert.run()
}
But personally I prefer MainActor.run {} over Task { @MainActor in ... }
Hoping Apple could fix problem asap😳.
By the way, in addition to NSAlert, NSOpenPanel and other NSView containing runModal methods will also crash for the same reason
The same approach could be applied 👇
extension NSOpenPanel {
/**
Workaround to allow using `NSOpenPanel` in a `Task`.
[FB9857161](https://github.com/feedback-assistant/reports/issues/288)
*/
@MainActor
@discardableResult
func run() async -> NSApplication.ModalResponse {
await withCheckedContinuation { continuation in
DispatchQueue.main.async { [self] in
continuation.resume(returning: runModal())
}
}
}
}
For NSOpenPanel, you can just use await panel.begin() (the auto-transformed completion handler method).
Also, don't forget to duplicate my report, to make it more likely Apple will see it and listen.
is seems to be fixed when run in macOS 13 (beta5). Still crashes when run on macOS 12
Fixed in macOS 13.3.1