@@ -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