diff --git a/packages/sqlite_async/lib/src/common/port_channel_native.dart b/packages/sqlite_async/lib/src/common/port_channel_native.dart index 8b05feb..bb8318e 100644 --- a/packages/sqlite_async/lib/src/common/port_channel_native.dart +++ b/packages/sqlite_async/lib/src/common/port_channel_native.dart @@ -30,12 +30,10 @@ class ParentPortClient implements PortClient { ParentPortClient() { final initCompleter = Completer.sync(); sendPortFuture = initCompleter.future; - sendPortFuture.then((value) { - sendPort = value; - }); _receivePort.listen((message) { if (message is _InitMessage) { assert(!initCompleter.isCompleted); + sendPort = message.port; initCompleter.complete(message.port); } else if (message is _PortChannelResult) { final handler = handlers.remove(message.requestId); diff --git a/packages/sqlite_async/lib/src/native/database/native_sqlite_connection_impl.dart b/packages/sqlite_async/lib/src/native/database/native_sqlite_connection_impl.dart index ae3c376..38c184e 100644 --- a/packages/sqlite_async/lib/src/native/database/native_sqlite_connection_impl.dart +++ b/packages/sqlite_async/lib/src/native/database/native_sqlite_connection_impl.dart @@ -36,6 +36,7 @@ class SqliteConnectionImpl final bool readOnly; final bool profileQueries; + bool _didOpenSuccessfully = false; SqliteConnectionImpl({ required AbstractDefaultSqliteOpenFactory openFactory, @@ -47,11 +48,11 @@ class SqliteConnectionImpl bool primary = false, }) : _writeMutex = mutex, profileQueries = openFactory.sqliteOptions.profileQueries { - isInitialized = _isolateClient.ready; this.upstreamPort = upstreamPort ?? listenForEvents(); // Accept an incoming stream of updates, or expose one if not given. this.updates = updates ?? updatesController.stream; - _open(openFactory, primary: primary, upstreamPort: this.upstreamPort); + isInitialized = + _open(openFactory, primary: primary, upstreamPort: this.upstreamPort); } Future get ready async { @@ -100,6 +101,7 @@ class SqliteConnectionImpl _isolateClient.tieToIsolate(_isolate); _isolate.resume(_isolate.pauseCapability!); await _isolateClient.ready; + _didOpenSuccessfully = true; }); } @@ -107,15 +109,18 @@ class SqliteConnectionImpl Future close() async { eventsPort?.close(); await _connectionMutex.lock(() async { - if (readOnly) { - await _isolateClient.post(const _SqliteIsolateConnectionClose()); - } else { - // In some cases, disposing a write connection lock the database. - // We use the lock here to avoid "database is locked" errors. - await _writeMutex.lock(() async { + if (_didOpenSuccessfully) { + if (readOnly) { await _isolateClient.post(const _SqliteIsolateConnectionClose()); - }); + } else { + // In some cases, disposing a write connection lock the database. + // We use the lock here to avoid "database is locked" errors. + await _writeMutex.lock(() async { + await _isolateClient.post(const _SqliteIsolateConnectionClose()); + }); + } } + _isolate.kill(); }); } diff --git a/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart b/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart index 5cb60f3..7bea111 100644 --- a/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart +++ b/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart @@ -34,8 +34,8 @@ class SqliteDatabaseImpl @override @protected - // Native doesn't require any asynchronous initialization - late Future isInitialized = Future.value(); + // ignore: invalid_use_of_protected_member + late Future isInitialized = _internalConnection.isInitialized; late final SqliteConnectionImpl _internalConnection; late final SqliteConnectionPool _pool; diff --git a/packages/sqlite_async/test/native/basic_test.dart b/packages/sqlite_async/test/native/basic_test.dart index eba5493..dec1fed 100644 --- a/packages/sqlite_async/test/native/basic_test.dart +++ b/packages/sqlite_async/test/native/basic_test.dart @@ -344,6 +344,22 @@ void main() { await Future.wait([f1, f2]); }); + + test('reports open error', () async { + // Ensure that a db that fails to open doesn't report any unhandled + // exceptions. This could happen when e.g. SQLCipher is used and the open + // factory supplies a wrong key pragma (because a subsequent pragma to + // change the journal mode then fails with a "not a database" error). + final db = + SqliteDatabase.withFactory(_InvalidPragmaOnOpenFactory(path: path)); + await expectLater( + db.initialize(), + throwsA( + isA().having( + (e) => e.toString(), 'toString()', contains('syntax error')), + ), + ); + }); }); } @@ -351,3 +367,15 @@ void main() { void ignore(Future future) { future.then((_) {}, onError: (_) {}); } + +class _InvalidPragmaOnOpenFactory extends DefaultSqliteOpenFactory { + const _InvalidPragmaOnOpenFactory({required super.path}); + + @override + List pragmaStatements(SqliteOpenOptions options) { + return [ + 'invalid syntax to fail open in test', + ...super.pragmaStatements(options), + ]; + } +}