Skip to content

Commit a64b09d

Browse files
committed
Failing test for #1715
1 parent 287c4be commit a64b09d

File tree

1 file changed

+51
-0
lines changed

1 file changed

+51
-0
lines changed

Tests/GRDBTests/DatabaseWriterTests.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,57 @@ class DatabaseWriterTests : GRDBTestCase {
415415

416416
// MARK: - Task Cancellation
417417

418+
// Regression test for <https://github.com/groue/GRDB.swift/issues/1715>.
419+
func test_write_is_possible_after_read_cancelled_after_database_access() async throws {
420+
// When a read access is cancelled, DatabaseQueue needs to execute
421+
// `PRAGMA query_only=0` in order to restore the read/write access.
422+
//
423+
// Here we test that this pragma can run from a cancelled read.
424+
//
425+
// Small difficulty: some SQLite versions (seen with 3.43.2) execute
426+
// the `query_only` pragma at compile time, not only at execution
427+
// time (yeah, that's an SQLite bug). The problem of this bug is
428+
// that even if the `PRAGMA query_only=0` is not executed due to
429+
// Task cancellation, its side effect is still executed when it is
430+
// compiled, unintentionally. A cancelled `PRAGMA query_only=0`
431+
// still works!
432+
//
433+
// To avoid this SQLite bug from messing with our test, we perform
434+
// two reads: one that compiles and cache `PRAGMA query_only`
435+
// statements, and a second read that we cancel. This time the
436+
// `PRAGMA query_only=0` triggers its side effect if and only if it
437+
// is actually executed (the behavior we are testing).
438+
func test(_ dbWriter: some DatabaseWriter) async throws {
439+
let semaphore = AsyncSemaphore(value: 0)
440+
let cancelledTaskMutex = Mutex<Task<Void, any Error>?>(nil)
441+
let task = Task {
442+
await semaphore.wait()
443+
444+
// First read, not cancelled, so that all `query_only`
445+
// pragma statements are compiled (see above).
446+
try await dbWriter.read { db in }
447+
448+
// Second read, cancelled.
449+
try await dbWriter.read { db in
450+
try XCTUnwrap(cancelledTaskMutex.load()).cancel()
451+
}
452+
}
453+
cancelledTaskMutex.store(task)
454+
semaphore.signal()
455+
// Wait until reads are completed
456+
try? await task.value
457+
458+
// Write access is restored after read cancellation (no error is thrown)
459+
try await dbWriter.write { db in
460+
try db.execute(sql: "CREATE TABLE test(a)")
461+
}
462+
}
463+
464+
try await test(makeDatabaseQueue())
465+
try await test(makeDatabasePool())
466+
try await test(AnyDatabaseWriter(makeDatabaseQueue()))
467+
}
468+
418469
func test_writeWithoutTransaction_is_cancelled_by_Task_cancellation_performed_before_database_access() async throws {
419470
func test(_ dbWriter: some DatabaseWriter) async throws {
420471
let semaphore = AsyncSemaphore(value: 0)

0 commit comments

Comments
 (0)