-
-
Notifications
You must be signed in to change notification settings - Fork 813
Description
What did you do?
Setup DatabasePool with config that has maximumReaderCount == 2 (can be any number)
Started long write operation in DatabasePool. Created 2 ValueObservations for read. Canceled (or not) them and started 3rd ValueObservation.
What did you expect to happen?
3rd ValueObservation performs read and publishes notifications about change in tracked region/records, etc because Pool's semaphore value drops down eventually (when another concurrent reads are performed) its value and doesn't block reads forever.
What happened instead?
3rd ValueObservation is blocked, because Pool.get() is locked by semaphore (itemsSemaphore). This read will never be executed while long write is still in progress. Even if previous ValueObservations was cancelled, 3rd ValueObservation does not starts publishing anything. stack trace shows that Thread is blocked by Pool.get().

Environment
GRDB flavor(s): (GRDB, SQLCipher, Custom SQLite build?)
GRDB version: 6.16.0
Installation method: (CocoaPods, SPM, manual?) manual
Xcode version: 15.4
Swift version: 5.7
Platform(s) running GRDB: (iOS, macOS, watchOS?)
macOS version running Xcode: 14.6.1
Demo Project
func test_WriteLocksReads() throws {
let configuration = Configuration()
configuration.maximumReaderCount = 2
let databasePool = try DatabasePool(
path: path,
configuration: configuration
)
var writeSubscriptions: Set<AnyCancellable> = []
var readSubscriptions: Set<AnyCancellable> = []
// write
databasePool.writePublisher { _ in
sleep(100000)
}
.sink()
.store(in: &writeSubscriptions)
let firstReadExpectation = XCTestExpectation()
ValueObservation.tracking { database in
defer { firstReadExpectation.fulfill() }
return try SomeRecord.fetchOne(database, key: "record id")
}
.publisher(in: databasePool)
.sink()
.store(in: &readSubscriptions)
wait(for: [firstReadExpectation])
let secondReadExpectation = XCTestExpectation()
ValueObservation.tracking { database in
defer { secondReadExpectation.fulfill() }
return try SomeRecord.fetchOne(database, key: "record id")
}
.publisher(in: databasePool)
.sink()
.store(in: &readSubscriptions)
wait(for: [secondReadExpectation])
readSubscriptions.removeAll() // can comment this line, doesn't change the problem
let thirdReadExpectation = XCTestExpectation()
ValueObservation.tracking { database in
defer { thirdReadExpectation.fulfill() }
return try SomeRecord.fetchOne(database, key: "record id")
}
.publisher(in: databasePool)
.sink()
.store(in: &readSubscriptions)
wait(for: [thirdReadExpectation])
XCTAssertTrue(true) // will not be executed until write's sleep(100000) is done
}