Skip to content

Commit b620227

Browse files
authored
fix race condition when trie generation thread may be invoked after storage has been closed (#3505)
motivation: with trie genration now running on lower priority, a race was exposed where the operation may start after the storage already was closed changes: * in trie generation, guard on state before trying to execute a statement on the connection since it may be terminated * in SQLite storage assert state does not got back frmo disconnected -> connected to help triage such issues * adjust test to new assertion
1 parent 46073d3 commit b620227

File tree

2 files changed

+14
-3
lines changed

2 files changed

+14
-3
lines changed

Sources/PackageCollections/Storage/SQLitePackageCollectionsStorage.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,14 @@ final class SQLitePackageCollectionsStorage: PackageCollectionsStorage, Closable
763763
DispatchQueue.sharedConcurrent.async(group: nil, qos: .background, flags: .assignCurrentContext, execute: {
764764
self.targetTrieReady.memoize {
765765
do {
766+
// since running on low priority thread make sure the database has not already gone away
767+
switch (try self.withStateLock { self.state }) {
768+
case .disconnected, .disconnecting:
769+
callback(.success(()))
770+
return false
771+
default:
772+
break
773+
}
766774
// Use FTS to build the trie
767775
let query = "SELECT collection_id_blob_base64, package_repository_url, name FROM \(Self.targetsFTSName);"
768776
try self.executeStatement(query) { statement in
@@ -833,6 +841,8 @@ final class SQLitePackageCollectionsStorage: PackageCollectionsStorage, Closable
833841
let db = try self.withStateLock { () -> SQLite in
834842
let db: SQLite
835843
switch (self.location, self.state) {
844+
case (_, .disconnecting), (_, .disconnected):
845+
preconditionFailure("DB id disconnecting or disconnected")
836846
case (.path(let path), .connected(let database)):
837847
if self.fileSystem.exists(path) {
838848
db = database

Tests/PackageCollectionsTests/PackageCollectionsStorageTests.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,16 +124,17 @@ class PackageCollectionsStorageTests: XCTestCase {
124124
}
125125

126126
try storage.close()
127-
storage.resetCache()
128127

129128
XCTAssertTrue(storage.fileSystem.exists(storagePath), "expected file to exist at \(path)")
130129
try storage.fileSystem.writeFileContents(storagePath, bytes: ByteString("blah".utf8))
131130

132-
XCTAssertThrowsError(try tsc_await { callback in storage.get(identifier: mockCollections.first!.identifier, callback: callback) }, "expected error", { error in
131+
let storage2 = SQLitePackageCollectionsStorage(path: path)
132+
defer { XCTAssertNoThrow(try storage2.close()) }
133+
XCTAssertThrowsError(try tsc_await { callback in storage2.get(identifier: mockCollections.first!.identifier, callback: callback) }, "expected error", { error in
133134
XCTAssert("\(error)".contains("is not a database"), "Expected file is not a database error")
134135
})
135136

136-
XCTAssertThrowsError(try tsc_await { callback in storage.put(collection: mockCollections.first!, callback: callback) }, "expected error", { error in
137+
XCTAssertThrowsError(try tsc_await { callback in storage2.put(collection: mockCollections.first!, callback: callback) }, "expected error", { error in
137138
XCTAssert("\(error)".contains("is not a database"), "Expected file is not a database error")
138139
})
139140
}

0 commit comments

Comments
 (0)