Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit bd85403

Browse files
committedMay 27, 2025·
Fix unrecoverable error when opening native db
1 parent dd7c810 commit bd85403

File tree

4 files changed

+45
-14
lines changed

4 files changed

+45
-14
lines changed
 

‎packages/sqlite_async/lib/src/common/port_channel_native.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,10 @@ class ParentPortClient implements PortClient {
3030
ParentPortClient() {
3131
final initCompleter = Completer<SendPort>.sync();
3232
sendPortFuture = initCompleter.future;
33-
sendPortFuture.then((value) {
34-
sendPort = value;
35-
});
3633
_receivePort.listen((message) {
3734
if (message is _InitMessage) {
3835
assert(!initCompleter.isCompleted);
36+
sendPort = message.port;
3937
initCompleter.complete(message.port);
4038
} else if (message is _PortChannelResult) {
4139
final handler = handlers.remove(message.requestId);

‎packages/sqlite_async/lib/src/native/database/native_sqlite_connection_impl.dart

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class SqliteConnectionImpl
3636
final bool readOnly;
3737

3838
final bool profileQueries;
39+
bool _didOpenSuccessfully = false;
3940

4041
SqliteConnectionImpl({
4142
required AbstractDefaultSqliteOpenFactory openFactory,
@@ -47,11 +48,11 @@ class SqliteConnectionImpl
4748
bool primary = false,
4849
}) : _writeMutex = mutex,
4950
profileQueries = openFactory.sqliteOptions.profileQueries {
50-
isInitialized = _isolateClient.ready;
5151
this.upstreamPort = upstreamPort ?? listenForEvents();
5252
// Accept an incoming stream of updates, or expose one if not given.
5353
this.updates = updates ?? updatesController.stream;
54-
_open(openFactory, primary: primary, upstreamPort: this.upstreamPort);
54+
isInitialized =
55+
_open(openFactory, primary: primary, upstreamPort: this.upstreamPort);
5556
}
5657

5758
Future<void> get ready async {
@@ -100,22 +101,26 @@ class SqliteConnectionImpl
100101
_isolateClient.tieToIsolate(_isolate);
101102
_isolate.resume(_isolate.pauseCapability!);
102103
await _isolateClient.ready;
104+
_didOpenSuccessfully = true;
103105
});
104106
}
105107

106108
@override
107109
Future<void> close() async {
108110
eventsPort?.close();
109111
await _connectionMutex.lock(() async {
110-
if (readOnly) {
111-
await _isolateClient.post(const _SqliteIsolateConnectionClose());
112-
} else {
113-
// In some cases, disposing a write connection lock the database.
114-
// We use the lock here to avoid "database is locked" errors.
115-
await _writeMutex.lock(() async {
112+
if (_didOpenSuccessfully) {
113+
if (readOnly) {
116114
await _isolateClient.post(const _SqliteIsolateConnectionClose());
117-
});
115+
} else {
116+
// In some cases, disposing a write connection lock the database.
117+
// We use the lock here to avoid "database is locked" errors.
118+
await _writeMutex.lock(() async {
119+
await _isolateClient.post(const _SqliteIsolateConnectionClose());
120+
});
121+
}
118122
}
123+
119124
_isolate.kill();
120125
});
121126
}

‎packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ class SqliteDatabaseImpl
3434

3535
@override
3636
@protected
37-
// Native doesn't require any asynchronous initialization
38-
late Future<void> isInitialized = Future.value();
37+
// ignore: invalid_use_of_protected_member
38+
late Future<void> isInitialized = _internalConnection.isInitialized;
3939

4040
late final SqliteConnectionImpl _internalConnection;
4141
late final SqliteConnectionPool _pool;

‎packages/sqlite_async/test/native/basic_test.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,10 +344,38 @@ void main() {
344344

345345
await Future.wait([f1, f2]);
346346
});
347+
348+
test('reports open error', () async {
349+
// Ensure that a db that fails to open doesn't report any unhandled
350+
// exceptions. This could happen when e.g. SQLCipher is used and the open
351+
// factory supplies a wrong key pragma (because a subsequent pragma to
352+
// change the journal mode then fails with a "not a database" error).
353+
final db =
354+
SqliteDatabase.withFactory(_InvalidPragmaOnOpenFactory(path: path));
355+
await expectLater(
356+
db.initialize(),
357+
throwsA(
358+
isA<Object>().having(
359+
(e) => e.toString(), 'toString()', contains('syntax error')),
360+
),
361+
);
362+
});
347363
});
348364
}
349365

350366
// For some reason, future.ignore() doesn't actually ignore errors in these tests.
351367
void ignore(Future future) {
352368
future.then((_) {}, onError: (_) {});
353369
}
370+
371+
class _InvalidPragmaOnOpenFactory extends DefaultSqliteOpenFactory {
372+
const _InvalidPragmaOnOpenFactory({required super.path});
373+
374+
@override
375+
List<String> pragmaStatements(SqliteOpenOptions options) {
376+
return [
377+
'invalid syntax to fail open in test',
378+
...super.pragmaStatements(options),
379+
];
380+
}
381+
}

0 commit comments

Comments
 (0)
Please sign in to comment.