Skip to content

Commit a55d41d

Browse files
authored
Merge pull request #601 from TableProApp/fix/perf-audit-tier1
fix: prevent hang on coordinator dealloc and concurrent WindowOpener waits
2 parents ac39675 + 52dccf5 commit a55d41d

File tree

5 files changed

+66
-15
lines changed

5 files changed

+66
-15
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- Fix potential hang when coordinator deallocates during save
13+
- Fix Cmd+W save not persisting data grid changes (sidebar edits intercepted save path)
14+
1015
## [0.27.5] - 2026-04-06
1116

1217
### Added

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/Resources/Localizable.xcstrings

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5964,6 +5964,9 @@
59645964
}
59655965
}
59665966
}
5967+
},
5968+
"Change Primary Key" : {
5969+
59675970
},
59685971
"Character Set" : {
59695972
"localizations" : {
@@ -10610,7 +10613,6 @@
1061010613
}
1061110614
},
1061210615
"Delete Column" : {
10613-
"extractionState" : "stale",
1061410616
"localizations" : {
1061510617
"tr" : {
1061610618
"stringUnit" : {
@@ -10699,7 +10701,6 @@
1069910701
}
1070010702
},
1070110703
"Delete Foreign Key" : {
10702-
"extractionState" : "stale",
1070310704
"localizations" : {
1070410705
"tr" : {
1070510706
"stringUnit" : {
@@ -10744,7 +10745,6 @@
1074410745
}
1074510746
},
1074610747
"Delete Index" : {
10747-
"extractionState" : "stale",
1074810748
"localizations" : {
1074910749
"tr" : {
1075010750
"stringUnit" : {
@@ -10815,6 +10815,12 @@
1081510815
}
1081610816
}
1081710817
}
10818+
},
10819+
"Delete Row" : {
10820+
10821+
},
10822+
"Delete Rows" : {
10823+
1081810824
},
1081910825
"Delete Selected" : {
1082010826
"localizations" : {
@@ -11689,6 +11695,12 @@
1168911695
}
1169011696
}
1169111697
}
11698+
},
11699+
"Edit Cell" : {
11700+
11701+
},
11702+
"Edit Column" : {
11703+
1169211704
},
1169311705
"Edit Connection" : {
1169411706
"localizations" : {
@@ -11734,6 +11746,12 @@
1173411746
}
1173511747
}
1173611748
}
11749+
},
11750+
"Edit Foreign Key" : {
11751+
11752+
},
11753+
"Edit Index" : {
11754+
1173711755
},
1173811756
"Edit Profile..." : {
1173911757
"localizations" : {
@@ -17115,6 +17133,12 @@
1711517133
}
1711617134
}
1711717135
}
17136+
},
17137+
"Insert Row" : {
17138+
17139+
},
17140+
"Insert Rows" : {
17141+
1711817142
},
1711917143
"INSERT Statement(s)" : {
1712017144
"localizations" : {
@@ -28525,6 +28549,7 @@
2852528549
}
2852628550
},
2852728551
"Search for field..." : {
28552+
"extractionState" : "stale",
2852828553
"localizations" : {
2852928554
"tr" : {
2853028555
"stringUnit" : {
@@ -28636,6 +28661,7 @@
2863628661
}
2863728662
},
2863828663
"Search shortcuts..." : {
28664+
"extractionState" : "stale",
2863928665
"localizations" : {
2864028666
"tr" : {
2864128667
"stringUnit" : {
@@ -28658,6 +28684,7 @@
2865828684
}
2865928685
},
2866028686
"Search tables, views, databases..." : {
28687+
"extractionState" : "stale",
2866128688
"localizations" : {
2866228689
"tr" : {
2866328690
"stringUnit" : {

TablePro/Views/Main/MainContentCommandActions.swift

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -358,22 +358,36 @@ final class MainContentCommandActions {
358358
return
359359
}
360360

361-
// Sidebar edits
361+
// Data grid changes or pending table operations take priority
362+
let hasDataChanges = coordinator.changeManager.hasChanges
363+
|| !pendingTruncates.wrappedValue.isEmpty
364+
|| !pendingDeletes.wrappedValue.isEmpty
365+
if hasDataChanges {
366+
let saved = await withCheckedContinuation { continuation in
367+
coordinator.saveCompletionContinuation = continuation
368+
saveChanges()
369+
}
370+
if saved {
371+
performClose()
372+
}
373+
return
374+
}
375+
376+
// Sidebar-only edits (made directly in the inspector panel)
362377
if rightPanelState.editState.hasEdits {
363378
rightPanelState.onSave?()
364379
performClose()
365380
return
366381
}
367382

368-
// Data grid changes: await the async save via continuation
369-
let saved = await withCheckedContinuation { continuation in
370-
coordinator.saveCompletionContinuation = continuation
371-
saveChanges()
372-
}
373-
374-
if saved {
383+
// File save (query editor with source file)
384+
if coordinator.tabManager.selectedTab?.isFileDirty == true {
385+
saveFileToSourceURL()
375386
performClose()
387+
return
376388
}
389+
390+
performClose()
377391
}
378392

379393
private func saveFileToSourceURL() {

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)