Skip to content

Commit c52e3ca

Browse files
committed
fix: prevent hang on coordinator dealloc and concurrent WindowOpener waits
- Resume saveCompletionContinuation in MainContentCoordinator.deinit to prevent permanent hang if coordinator deallocates during save - Support multiple concurrent waitUntilReady() callers in WindowOpener by using an array of continuations instead of a single optional
1 parent 1c698c7 commit c52e3ca

File tree

3 files changed

+11
-4
lines changed

3 files changed

+11
-4
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2424
- Replace event monitor with native SwiftUI .onKeyPress() in connection switcher
2525
- Extract reusable SearchFieldView component from 4 custom search field implementations
2626
- Replace timing hacks with structured signals (polling loops, arbitrary delays)
27+
- Fix potential hang when coordinator deallocates during save (saveCompletionContinuation)
28+
- Fix potential hang when multiple subsystems await WindowOpener readiness simultaneously
2729

2830
### Changed
2931

TablePro/Core/Services/Infrastructure/WindowOpener.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@ internal final class WindowOpener {
1515

1616
internal static let shared = WindowOpener()
1717

18-
private var readyContinuation: CheckedContinuation<Void, Never>?
18+
private var readyContinuations: [CheckedContinuation<Void, Never>] = []
1919

2020
/// Set on appear by ContentView, WelcomeViewModel, or ConnectionFormView.
2121
/// Safe to store — OpenWindowAction is app-scoped, not view-scoped.
2222
internal var openWindow: OpenWindowAction? {
2323
didSet {
2424
if openWindow != nil {
25-
readyContinuation?.resume()
26-
readyContinuation = nil
25+
for continuation in readyContinuations {
26+
continuation.resume()
27+
}
28+
readyContinuations.removeAll()
2729
}
2830
}
2931
}
@@ -35,7 +37,7 @@ internal final class WindowOpener {
3537
if openWindow != nil {
3638
continuation.resume()
3739
} else {
38-
readyContinuation = continuation
40+
readyContinuations.append(continuation)
3941
}
4042
}
4143
}

TablePro/Views/Main/MainContentCoordinator.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,9 @@ final class MainContentCoordinator {
410410
}
411411

412412
deinit {
413+
saveCompletionContinuation?.resume(returning: false)
414+
saveCompletionContinuation = nil
415+
413416
let connectionId = connection.id
414417
let alreadyHandled = _didTeardown.withLock { $0 } || _teardownScheduled.withLock { $0 }
415418

0 commit comments

Comments
 (0)