diff --git a/drift/CHANGELOG.md b/drift/CHANGELOG.md index e8b15ac8..2df2d013 100644 --- a/drift/CHANGELOG.md +++ b/drift/CHANGELOG.md @@ -3,6 +3,7 @@ - Don't keep databases in an unusable state if the `setup` callback throws an exception. Instead, drift will retry the next time the database is used. - Allow targeting partial indices in `DoUpdate` ([#2394](https://github.com/simolus3/drift/issues/2394)) +- Fix deadlocks when `computeWithDatabase` is called inside a `transaction()`. ## 2.7.0 diff --git a/drift/lib/isolate.dart b/drift/lib/isolate.dart index cf59ee82..9aed6f26 100644 --- a/drift/lib/isolate.dart +++ b/drift/lib/isolate.dart @@ -221,13 +221,17 @@ extension ComputeWithDriftIsolate on DB { /// requiring a database is also available through [computeWithDatabase]. @experimental Future serializableConnection() async { + final currentlyInRootConnection = resolvedEngine is GeneratedDatabase; // ignore: invalid_use_of_protected_member - final localConnection = connection; + final localConnection = resolvedEngine.connection; final data = await localConnection.connectionData; - if (data is DriftIsolate) { - // The local database is already connected to an isolate, use that one - // directly. + // If we're connected to an isolate already, we can use that one directly + // instead of starting a short-lived drift server. + // However, this does not work if [serializableConnection] is called in a + // transaction zone, since the top-level connection could be blocked waiting + // for the transaction (as transactions can't be concurrent in sqlite3). + if (data is DriftIsolate && currentlyInRootConnection) { return data; } else { // Set up a drift server acting as a proxy to the existing database diff --git a/drift/test/isolate_test.dart b/drift/test/isolate_test.dart index 1bea9145..5622c167 100644 --- a/drift/test/isolate_test.dart +++ b/drift/test/isolate_test.dart @@ -199,6 +199,26 @@ void main() { // Make sure database still works after computeWithDatabase // https://github.com/simolus3/drift/issues/2279#issuecomment-1455385439 await db.customSelect('SELECT 1').get(); + + // This should still work when computeWithDatabase is called in a + // transaction. + await db.transaction(() async { + await db.into(db.categories).insert( + CategoriesCompanion.insert(description: 'main / transaction')); + + await db.computeWithDatabase( + computation: (db) async { + await db.batch((batch) { + batch.insert( + db.categories, + CategoriesCompanion.insert(description: 'nested remote batch!'), + ); + }); + }, + connect: TodoDb.new, + ); + }); + await db.close(); }