@@ -9,6 +9,10 @@ import TableProPluginKit
99import Testing
1010
1111/// Records every catalog read it answers, so a test can count the queries a refresh costs.
12+ ///
13+ /// A schema load reads tables, routines, triggers and types concurrently, so the read log and the
14+ /// pause gate are locked rather than isolated: an unlocked append from those reads raced and
15+ /// crashed the test host.
1216final class CatalogReadCountingDriver : DatabaseDriver , @unchecked Sendable {
1317 let connection : DatabaseConnection
1418 var status : ConnectionStatus = . connected
@@ -18,23 +22,62 @@ final class CatalogReadCountingDriver: DatabaseDriver, @unchecked Sendable {
1822 var tablesBySchema : [ String : [ TableInfo ] ] = [ : ]
1923 var tablesError : Error ?
2024 var allSchemaTables : [ TableInfo ] ?
21- private( set) var reads : [ String ] = [ ]
2225
23- var pausesNextTableFetch = false
24- var onTableFetchPaused : ( @Sendable ( ) -> Void ) ?
26+ private let lock = NSLock ( )
27+ private var readLog : [ String ] = [ ]
28+ private var pauseNextTableFetch = false
29+ private var tableFetchPausedHandler : ( @Sendable ( ) -> Void ) ?
2530 private var tableFetchGate : CheckedContinuation < Void , Never > ?
2631
2732 init ( connection: DatabaseConnection ) {
2833 self . connection = connection
2934 }
3035
36+ var reads : [ String ] {
37+ lock. withLock { readLog }
38+ }
39+
40+ var pausesNextTableFetch : Bool {
41+ get { lock. withLock { pauseNextTableFetch } }
42+ set { lock. withLock { pauseNextTableFetch = newValue } }
43+ }
44+
45+ var onTableFetchPaused : ( @Sendable ( ) -> Void ) ? {
46+ get { lock. withLock { tableFetchPausedHandler } }
47+ set { lock. withLock { tableFetchPausedHandler = newValue } }
48+ }
49+
3150 func resumeTableFetch( ) {
32- tableFetchGate? . resume ( )
33- tableFetchGate = nil
51+ let gate = lock. withLock {
52+ let gate = tableFetchGate
53+ tableFetchGate = nil
54+ return gate
55+ }
56+ gate? . resume ( )
3457 }
3558
3659 func forgetReads( ) {
37- reads. removeAll ( )
60+ lock. withLock { readLog. removeAll ( ) }
61+ }
62+
63+ private func record( _ read: String ) {
64+ lock. withLock { readLog. append ( read) }
65+ }
66+
67+ private func takeTableFetchPause( ) -> Bool {
68+ lock. withLock {
69+ let pauses = pauseNextTableFetch
70+ pauseNextTableFetch = false
71+ return pauses
72+ }
73+ }
74+
75+ private func park( _ continuation: CheckedContinuation < Void , Never > ) {
76+ let handler = lock. withLock {
77+ tableFetchGate = continuation
78+ return tableFetchPausedHandler
79+ }
80+ handler ? ( )
3881 }
3982
4083 func reads( ofSchema schema: String ) -> [ String ] {
@@ -63,48 +106,46 @@ final class CatalogReadCountingDriver: DatabaseDriver, @unchecked Sendable {
63106 }
64107
65108 func fetchSchemas( ) async throws -> [ String ] {
66- reads . append ( " schemas " )
109+ record ( " schemas " )
67110 return schemasToReturn
68111 }
69112
70113 func fetchTables( ) async throws -> [ TableInfo ] {
71- reads . append ( " tables " )
114+ record ( " tables " )
72115 return [ ]
73116 }
74117
75118 func fetchTables( schema: String ? ) async throws -> [ TableInfo ] {
76119 let schema = schema ?? " "
77- reads . append ( " tables: \( schema) " )
120+ record ( " tables: \( schema) " )
78121 if let tablesError { throw tablesError }
79122 let snapshot = tablesBySchema [ schema] ?? [ ]
80- if pausesNextTableFetch {
81- pausesNextTableFetch = false
123+ if takeTableFetchPause ( ) {
82124 await withCheckedContinuation { continuation in
83- tableFetchGate = continuation
84- onTableFetchPaused ? ( )
125+ park ( continuation)
85126 }
86127 try Task . checkCancellation ( )
87128 }
88129 return snapshot
89130 }
90131
91132 func fetchTablesInAllSchemas( ) async throws -> [ TableInfo ] ? {
92- reads . append ( " allSchemaTables " )
133+ record ( " allSchemaTables " )
93134 return allSchemaTables
94135 }
95136
96137 func fetchRoutines( schema: String ? ) async throws -> [ RoutineInfo ] {
97- reads . append ( schema. map { " routines: \( $0) " } ?? " routines " )
138+ record ( schema. map { " routines: \( $0) " } ?? " routines " )
98139 return [ ]
99140 }
100141
101142 func fetchAllTriggers( schema: String ? ) async throws -> [ TriggerInfo ] {
102- reads . append ( schema. map { " triggers: \( $0) " } ?? " triggers " )
143+ record ( schema. map { " triggers: \( $0) " } ?? " triggers " )
103144 return [ ]
104145 }
105146
106147 func fetchUserDefinedTypes( schema: String ? ) async throws -> [ UserDefinedTypeInfo ] {
107- reads . append ( schema. map { " types: \( $0) " } ?? " types " )
148+ record ( schema. map { " types: \( $0) " } ?? " types " )
108149 return [ ]
109150 }
110151
0 commit comments