I am seeing the following crash in my software:
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x00000001a7d2e1f8
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libswiftCore.dylib 0x1a7d2e1f8 _assertionFailure(_:_:file:line:flags:) + 176
1 Light Table 0x10423d788 specialized static MainActor.assumeIsolated<A>(_:file:line:) + 388
2 Light Table 0x104087cf8 CommitDetailsTextView.awakeFromNib() + 68 (CommitDetailsTextView.swift:11) [inlined]
3 Light Table 0x104087cf8 @objc CommitDetailsTextView.awakeFromNib() + 120 (/<compiler-generated>:8)
4 CoreFoundation 0x196659b08 -[NSSet makeObjectsPerformSelector:] + 176
...
So, I am on the main thread (Crashed Thread: 0 Dispatch queue: com.apple.main-thread
), but the cause is MainActor.assumeIsolated
? This is not something I expected to be possible.
This is happening when a NIB is loaded, with code like this:
class CommitDetailsTextView: NSTextView { // implicit @MainActor
// ...
override func awakeFromNib() { // implicit NOT @MainActor
super.awakeFromNib()
MainActor.assumeIsolated {
self._awakeFromNib()
}
}
private func _awakeFromNib() {
self.font = Self.displayFont // must happen on main thread
// ...
}
}
The regular awakeFromNib
is from NSObject
and thus not main actor isolated. This is why I have this jump to a main actor isolated _awakeFromNib
all over my project so I can access main actor isolated IBOutlet
variables and such.
Just to try, I replaced the MainActor.assumeIsolated { … }
above with a custom awakeFromNibOnMainThread { … }
like so:
func awakeFromNibOnMainThread(_ body: @escaping @MainActor () -> ()) {
if Thread.isMainThread {
MainActor.assumeIsolated {
body()
}
}
else {
Task { @MainActor in
body()
}
}
}
But that still crashes:
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x00000001a7d2e1f8
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libswiftCore.dylib 0x1a7d2e1f8 _assertionFailure(_:_:file:line:flags:) + 176
1 Light Table 0x126789058 specialized static MainActor.assumeIsolated<A>(_:file:line:) + 388
2 Light Table 0x12674073c awakeFromNibOnMainThread(_:) + 232 (awakeFromNibOnMainThread.swift:5)
3 Light Table 0x1265b3550 CommitDetailsTextView.awakeFromNib() + 16 (CommitDetailsTextView.swift:11) [inlined]
4 Light Table 0x1265b3550 @objc CommitDetailsTextView.awakeFromNib() + 132 (/<compiler-generated>:8)
5 CoreFoundation 0x196659b08 -[NSSet makeObjectsPerformSelector:] + 176
...
Is this to be expected? MainActor.assumeIsolated
crashing on the main thread? Or is this just a bug?
Tested with Swift 6.2 in Xcode 26 beta and Swift 6.1 in Xcode 16.4 on macOS 15.5.
3 Likes
tera
June 23, 2025, 10:07pm
2
Looks like a bug.
Would that work in the meantime?
override func awakeFromNib() { // implicit NOT @MainActor
super.awakeFromNib()
Task { @MainActor in
self._awakeFromNib()
}
}
That’s not a great workaround as some of my code expects to be run after the initial view setup has happened in awakeFromNib
.
Is there a way to run @MainActor
code without runtime checks? Not as a real solution, but to bridge the bug.
vanvoorden
(Rick van Voorden)
June 23, 2025, 10:32pm
4
What might be worth confirming here is if "legacy" code that runs on the main
thread without explicitly isolating to main
thread with modern concurrency should not crash from assumeIsolated
. I don't have an answer for that…
1 Like
I’m not quite sure what you mean by that. Legacy code as in AppKit or Objective-C in general outside the Swift compiler?
nikola
(Nikola)
June 23, 2025, 10:46pm
6
I wonder if Task.startSynchronously avoids a thread hop here?
override func awakeFromNib() {
super.awakeFromNib()
Task.startSynchronously { @MainActor in
self._awakeFromNib()
}
}
vanvoorden
(Rick van Voorden)
June 23, 2025, 10:48pm
7
I was thinking legacy code as in code that might use something like GCD dispatchMain
to send work to main
thread. But code that is not compiled with Swift Strict Concurrency and does not put its types on MainActor
directly.
1 Like
I think that is the case here. This is the full stack trace, which is mostly AppKit loading a storyboard and its individual NIB files triggered by a menu item action:
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x00000001a7d2e1f8
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libswiftCore.dylib 0x1a7d2e1f8 _assertionFailure(_:_:file:line:flags:) + 176
1 Light Table 0x10423d788 specialized static MainActor.assumeIsolated<A>(_:file:line:) + 388
2 Light Table 0x104087cf8 CommitDetailsTextView.awakeFromNib() + 68 (CommitDetailsTextView.swift:11) [inlined]
3 Light Table 0x104087cf8 @objc CommitDetailsTextView.awakeFromNib() + 120 (/<compiler-generated>:8)
4 CoreFoundation 0x196659b08 -[NSSet makeObjectsPerformSelector:] + 176
a6 AppKit 0x19a5d3dbc -[NSNib _instantiateNibWithExternalNameTable:options:] + 332
7 AppKit 0x19a5d3ba8 -[NSNib _instantiateWithOwner:options:topLevelObjects:] + 132
8 AppKit 0x19a5d3508 -[NSViewController loadView] + 296
9 AppKit 0x19a5d3304 -[NSViewController _loadViewIfRequired] + 72
10 AppKit 0x19ac16e58 __24-[NSViewController view]_block_invoke + 28
11 AppKit 0x19a5cd070 NSPerformVisuallyAtomicChange + 108
12 AppKit 0x19a5d3274 -[NSViewController view] + 160
13 AppKit 0x19a5f8774 -[_NSSplitViewItemViewWrapper wrapView] + 72
14 AppKit 0x19a5f7838 -[NSSplitViewController _setupSplitView] + 312
15 AppKit 0x19a5f74e4 -[NSSplitViewController viewDidLoad] + 164
16 AppKit 0x19a5e5650 -[NSViewController _sendViewDidLoad] + 84
17 AppKit 0x19a5d33a8 -[NSViewController _loadViewIfRequired] + 236
18 AppKit 0x19ac16e58 __24-[NSViewController view]_block_invoke + 28
19 AppKit 0x19a5cd070 NSPerformVisuallyAtomicChange + 108
20 AppKit 0x19a5d3274 -[NSViewController view] + 160
21 AppKit 0x19a7040b4 -[NSTabViewController _goodTabViewContentSize] + 120
22 AppKit 0x19a703ec8 -[NSTabViewController viewDidLoad] + 88
23 AppKit 0x19a5e5650 -[NSViewController _sendViewDidLoad] + 84
24 AppKit 0x19a5d33a8 -[NSViewController _loadViewIfRequired] + 236
25 AppKit 0x19ac16e58 __24-[NSViewController view]_block_invoke + 28
26 AppKit 0x19a5cd070 NSPerformVisuallyAtomicChange + 108
27 AppKit 0x19a5d3274 -[NSViewController view] + 160
28 AppKit 0x19a5f8774 -[_NSSplitViewItemViewWrapper wrapView] + 72
29 AppKit 0x19a5f7838 -[NSSplitViewController _setupSplitView] + 312
30 AppKit 0x19a5f74e4 -[NSSplitViewController viewDidLoad] + 164
31 AppKit 0x19a5e5650 -[NSViewController _sendViewDidLoad] + 84
32 AppKit 0x19a5d33a8 -[NSViewController _loadViewIfRequired] + 236
33 AppKit 0x19ac16e58 __24-[NSViewController view]_block_invoke + 28
34 AppKit 0x19a5cd070 NSPerformVisuallyAtomicChange + 108
35 AppKit 0x19a5d3274 -[NSViewController view] + 160
36 AppKit 0x19a7569a4 -[NSWindow _contentViewControllerChanged] + 80
37 AppKit 0x19a5cd070 NSPerformVisuallyAtomicChange + 108
38 AppKit 0x19a7568e0 -[NSWindow setContentViewController:] + 132
39 Foundation 0x197c08a1c -[NSObject(NSKeyValueCoding) setValue:forKey:] + 324
40 AppKit 0x19a781f34 -[NSWindow setValue:forKey:] + 144
41 AppKit 0x19a604354 -[NSIBUserDefinedRuntimeAttributesConnector establishConnection] + 168
42 AppKit 0x19a56da6c -[NSIBObjectData nibInstantiateWithOwner:options:topLevelObjects:] + 732
43 AppKit 0x19a5d3dbc -[NSNib _instantiateNibWithExternalNameTable:options:] + 332
44 AppKit 0x19a5d3ba8 -[NSNib _instantiateWithOwner:options:topLevelObjects:] + 132
45 AppKit 0x19afebbd8 -[NSStoryboard _instantiateControllerWithIdentifier:creator:storyboardSegueTemplate:sender:] + 644
46 AppKit 0x19afeb900 -[NSStoryboard instantiateControllerWithIdentifier:creator:] + 24
47 AppKit 0x19afeb934 -[NSStoryboard instantiateControllerWithIdentifier:] + 20
48 Light Table 0x1040b6a78 specialized static Dashboard.dashboard(for:) + 248 (Dashboard.swift:24)
49 Light Table 0x1041f1544 static Dashboard.dashboard(for:) + 8 [inlined]
50 Light Table 0x1041f1544 specialized Plugin.showRepository(_:) + 264 (Plugin.swift:349)
51 Light Table 0x1041f0d78 @objc Plugin.showRepository(_:) + 148
52 Glyphs 3 0x1008a722c 0x10082c000 + 504364
53 AppKit 0x19a7f32d8 -[NSMenuItem _corePerformAction] + 372
54 AppKit 0x19af56510 _NSMenuPerformActionWithHighlighting + 152
55 AppKit 0x19adaa7f0 -[NSMenu _performKeyEquivalentForItemAtIndex:] + 172
56 AppKit 0x19a7f232c -[NSMenu performKeyEquivalent:] + 356
57 AppKit 0x19af2b108 routeKeyEquivalent + 444
58 AppKit 0x19af28fdc -[NSApplication(NSEventRouting) sendEvent:] + 652
59 Glyphs 3 0x1008a6d8c 0x10082c000 + 503180
60 AppKit 0x19ab2842c -[NSApplication _handleEvent:] + 60
61 AppKit 0x19a57ec8c -[NSApplication run] + 520
62 AppKit 0x19a55535c NSApplicationMain + 880
63 Glyphs 3 0x1008329b4 0x10082c000 + 27060
64 dyld 0x1961dab98 start + 6076
Since that code is not using any await
, I think it’s the same as without Task.startSynchronously { … }
. Also, the docs read
Create and immediately start running a new task in the context of the calling thread/task .
So, I think this would not accept a @MainActor
closure anyway.
1 Like
extension MainActor {
/// - Note: https://github.com/swiftlang/swift/blob/dbf7fe6aa01c958fa6711df30423e88ff22c0b75/stdlib/public/Concurrency/MainActor.swift#L128
@available(*, noasync)
nonisolated
static func myAssumeIsolated<T: Sendable>(_ operation: @MainActor () throws -> T) rethrows -> T {
precondition(Thread.isMainThread)
return try withoutActuallyEscaping(operation) {
return try unsafeBitCast($0, to: (() throws -> T).self)()
}
}
}
class CommitDetailsTextView: NSTextView {
override func awakeFromNib() {
super.awakeFromNib()
MainActor.myAssumeIsolated {
self._awakeFromNib()
}
}
private func _awakeFromNib() {
self.font = Self.displayFont
}
}
1 Like
Thanks, that works for now.
There is still the issue that a MainActor.assumeIsolated
call nested inside _awakeFromNib() { … }
can cause a crash, so I would like to understand if this is something I can fix in my code or if this is a bug in Swift.
I would definitely suggest filing a bug report about it. Either it's a bug in Swift in that it shouldn't happen, or it's a bug in Swift in that we should make it more clear what's going on and what to do about it.
4 Likes