Skip to content

Commit 7f3506f

Browse files
committed
Web: Consistently use finalizers
1 parent 6dc65bd commit 7f3506f

File tree

8 files changed

+78
-58
lines changed

8 files changed

+78
-58
lines changed

sqlite3/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
- The SQLite library can only be customized with user defines.
66
- You should drop your dependencies on `sqlite3_flutter_libs` and
77
`sqlcipher_flutter_libs` when upgrading.
8+
- You can also remove dependencies on `sqlite3_native_assets`, since that
9+
package is now part of `package:sqlite3`.
810
- __Breaking change__: Parameters to `SqliteException`s are now named.
11+
- On native platforms, use native finalizers to reliably clear statements and databases.
12+
- Refactor binding text and blob values to reduce the chance of memory leaks.
913

1014
## 2.9.4
1115

sqlite3/lib/src/ffi/bindings.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,7 @@ final class FfiChangesetIterator implements RawChangesetIterator, Finalizable {
735735
}
736736

737737
@override
738-
SqliteResult<RawSqliteValue> sqlite3changeset_new(int columnNumber) {
738+
SqliteResult<RawSqliteValue?> sqlite3changeset_new(int columnNumber) {
739739
final outValue = allocate<Pointer<sqlite3_value>>();
740740
final result = libsqlite3.sqlite3changeset_new(
741741
iterator,
@@ -757,7 +757,7 @@ final class FfiChangesetIterator implements RawChangesetIterator, Finalizable {
757757
}
758758

759759
@override
760-
SqliteResult<RawSqliteValue> sqlite3changeset_old(int columnNumber) {
760+
SqliteResult<RawSqliteValue?> sqlite3changeset_old(int columnNumber) {
761761
final outValue = allocate<Pointer<sqlite3_value>>();
762762
final result = libsqlite3.sqlite3changeset_old(
763763
iterator,

sqlite3/lib/src/implementation/bindings.dart

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ import 'package:meta/meta.dart';
1010
import '../functions.dart';
1111
import '../vfs.dart';
1212

13+
/// A `sqlite3_changeset_iter` instance.
14+
///
15+
/// For iterators created through `sqlite3changeset_start`, implementations
16+
/// should use finalizers to automatically call `sqlite3changeset_finalize`
17+
/// even when [sqlite3changeset_finalize] is not called explicitly.
1318
abstract interface class RawChangesetIterator {
1419
// int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter);
1520
int sqlite3changeset_finalize();
@@ -19,7 +24,7 @@ abstract interface class RawChangesetIterator {
1924
// int iVal, /* Column number */
2025
// sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */
2126
// );
22-
SqliteResult<RawSqliteValue> sqlite3changeset_new(int columnNumber);
27+
SqliteResult<RawSqliteValue?> sqlite3changeset_new(int columnNumber);
2328

2429
// int sqlite3changeset_next(sqlite3_changeset_iter *pIter);
2530
int sqlite3changeset_next();
@@ -29,7 +34,7 @@ abstract interface class RawChangesetIterator {
2934
// int iVal, /* Column number */
3035
// sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */
3136
// );
32-
SqliteResult<RawSqliteValue> sqlite3changeset_old(int columnNumber);
37+
SqliteResult<RawSqliteValue?> sqlite3changeset_old(int columnNumber);
3338

3439
// int sqlite3changeset_op(
3540
// sqlite3_changeset_iter *pIter, /* Iterator object */
@@ -130,6 +135,11 @@ abstract interface class RawSqliteBindings {
130135
int sqlite3_initialize();
131136
}
132137

138+
/// A `sqlite3_session` instance.
139+
///
140+
/// Implementations should use finalizers to automatically calls
141+
/// `sqlite3session_delete` even when [sqlite3session_delete] is not called
142+
/// explcitly.
133143
abstract interface class RawSqliteSession {
134144
// int sqlite3session_attach(
135145
// sqlite3_session *pSession, /* Session object */
@@ -166,7 +176,7 @@ abstract interface class RawSqliteSession {
166176
}
167177

168178
/// Combines a sqlite result code and the result object.
169-
typedef SqliteResult<T extends Object> = ({T? result, int resultCode});
179+
typedef SqliteResult<T> = ({T? result, int resultCode});
170180

171181
typedef RawXFunc = void Function(RawSqliteContext, List<RawSqliteValue>);
172182
typedef RawXStep = void Function(RawSqliteContext, List<RawSqliteValue>);
@@ -176,6 +186,10 @@ typedef RawCommitHook = int Function();
176186
typedef RawRollbackHook = void Function();
177187
typedef RawCollation = int Function(String? a, String? b);
178188

189+
/// A `sqlite3` instance.
190+
///
191+
/// Instances should use finalizers to automatically close the database, even
192+
/// when [sqlite3_close_v2] is not called explicitly.
179193
abstract interface class RawSqliteDatabase {
180194
int sqlite3_changes();
181195
int sqlite3_last_insert_rowid();
@@ -252,6 +266,10 @@ abstract interface class RawStatementCompiler {
252266
void close();
253267
}
254268

269+
/// A `sqlite3_stmt` instance.
270+
///
271+
/// Instances should use finalizers to automatically call `sqlite3_finalize` on
272+
/// the statement, even when [sqlite3_finalize] is not called explicitly.
255273
abstract interface class RawSqliteStatement {
256274
void sqlite3_reset();
257275
int sqlite3_step();

sqlite3/lib/src/implementation/session.dart

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -187,22 +187,16 @@ final class ChangesetIteratorImplementation implements ChangesetIterator {
187187
final kind = SqliteUpdateKind.fromCode(op.operation)!;
188188

189189
final oldColumns = kind != SqliteUpdateKind.insert
190-
? List.generate(
191-
op.columnCount,
192-
(i) => raw
193-
.sqlite3changeset_old(i)
194-
.okOrThrowOutsideOfDatabase(bindings)
195-
.read(),
196-
)
190+
? List.generate(op.columnCount, (i) {
191+
final rawValue = raw.sqlite3changeset_old(i);
192+
return _readChangesetValue(rawValue);
193+
})
197194
: null;
198195
final newColumns = kind != SqliteUpdateKind.delete
199-
? List.generate(
200-
op.columnCount,
201-
(i) => raw
202-
.sqlite3changeset_new(i)
203-
.okOrThrowOutsideOfDatabase(bindings)
204-
.read(),
205-
)
196+
? List.generate(op.columnCount, (i) {
197+
final rawValue = raw.sqlite3changeset_new(i);
198+
return _readChangesetValue(rawValue);
199+
})
206200
: null;
207201
current = ChangesetOperation(
208202
table: op.tableName,
@@ -219,6 +213,13 @@ final class ChangesetIteratorImplementation implements ChangesetIterator {
219213
return false;
220214
}
221215

216+
Object? _readChangesetValue(SqliteResult<RawSqliteValue?> result) {
217+
return switch (result.resultCode) {
218+
0 => result.result?.read(),
219+
_ => throw createExceptionOutsideOfDatabase(bindings, result.resultCode),
220+
};
221+
}
222+
222223
@override
223224
void finalize() {
224225
if (_isFinalized) {

sqlite3/lib/src/implementation/utils.dart

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import '../constants.dart';
22
import 'bindings.dart';
3-
import 'exception.dart';
43

54
extension BigIntRangeCheck on BigInt {
65
BigInt get checkRange {
@@ -27,16 +26,6 @@ int eTextRep(bool deterministic, bool directOnly, bool subtype) {
2726
return flags;
2827
}
2928

30-
extension HandleResult<T extends Object> on SqliteResult<T> {
31-
T okOrThrowOutsideOfDatabase(RawSqliteBindings bindings) {
32-
if (resultCode != SqlError.SQLITE_OK) {
33-
throw createExceptionOutsideOfDatabase(bindings, resultCode);
34-
}
35-
36-
return result!;
37-
}
38-
}
39-
4029
extension ReadDartValue on RawSqliteValue {
4130
Object? read() {
4231
return switch (sqlite3_value_type()) {

sqlite3/lib/src/wasm/bindings.dart

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -229,11 +229,15 @@ final class WasmSqliteBindings implements RawSqliteBindings {
229229
final class WasmDatabase implements RawSqliteDatabase {
230230
final wasm.WasmBindings bindings;
231231
final Pointer db;
232+
final Object detach = Object();
232233

233-
WasmDatabase(this.bindings, this.db);
234+
WasmDatabase(this.bindings, this.db) {
235+
bindings.databaseFinalizer?.attach(this, db, detach: detach);
236+
}
234237

235238
@override
236239
int sqlite3_close_v2() {
240+
bindings.databaseFinalizer?.detach(detach);
237241
return bindings.sqlite3_close_v2(db);
238242
}
239243

@@ -448,8 +452,11 @@ final class WasmStatement implements RawSqliteStatement {
448452
final WasmDatabase database;
449453
final Pointer stmt;
450454
final WasmBindings bindings;
455+
final Object detach = Object();
451456

452-
WasmStatement(this.database, this.stmt) : bindings = database.bindings;
457+
WasmStatement(this.database, this.stmt) : bindings = database.bindings {
458+
bindings.statementFinalizer?.attach(this, stmt, detach: detach);
459+
}
453460

454461
@override
455462
int sqlite3_bind_blob64(int index, List<int> value) {
@@ -576,6 +583,7 @@ final class WasmStatement implements RawSqliteStatement {
576583
@override
577584
void sqlite3_finalize() {
578585
bindings.sqlite3_finalize(stmt);
586+
bindings.statementFinalizer?.detach(detach);
579587
}
580588

581589
@override
@@ -770,18 +778,14 @@ class WasmValueList extends ListBase<WasmValue> {
770778
}
771779

772780
final class WasmSession implements RawSqliteSession {
773-
static final Finalizer<(WasmBindings, int)> _finalizer = Finalizer((args) {
774-
args.$1.sqlite3session_delete(args.$2);
775-
});
776-
777781
final WasmSqliteBindings bindings;
778782
final int pointer; // the sqlite3_session ptr
779783
final Object detach = Object();
780784

781785
final WasmBindings _bindings;
782786

783787
WasmSession(this.bindings, this.pointer) : _bindings = bindings.bindings {
784-
_finalizer.attach(this, (_bindings, pointer), detach: detach);
788+
_bindings.sessionFinalizer?.attach(this, pointer, detach: detach);
785789
}
786790

787791
@override
@@ -828,7 +832,7 @@ final class WasmSession implements RawSqliteSession {
828832

829833
@override
830834
void sqlite3session_delete() {
831-
_finalizer.detach(this);
835+
_bindings.sessionFinalizer?.detach(this);
832836
_bindings.sqlite3session_delete(pointer);
833837
}
834838

@@ -859,16 +863,6 @@ final class WasmSession implements RawSqliteSession {
859863
}
860864

861865
final class WasmChangesetIterator implements RawChangesetIterator {
862-
static final Finalizer<(WasmBindings, int?, int)> _finalizer = Finalizer((
863-
args,
864-
) {
865-
if (args.$2 case final underlyingBytes?) {
866-
args.$1.free(underlyingBytes);
867-
}
868-
869-
args.$1.sqlite3changeset_finalize(args.$3);
870-
});
871-
872866
final WasmSqliteBindings bindings;
873867

874868
/// If this iterator was created from an uint8list allocated when creating it,
@@ -886,28 +880,29 @@ final class WasmChangesetIterator implements RawChangesetIterator {
886880
bool owned = true,
887881
}) : _bindings = bindings.bindings {
888882
if (owned) {
889-
_finalizer.attach(this, (
890-
_bindings,
891-
dataPointer,
883+
bindings.bindings.changesetFinalizer?.attach(
884+
this,
892885
pointer,
893-
), detach: detach);
886+
detach: detach,
887+
);
894888
}
895889
}
896890

897891
@override
898892
int sqlite3changeset_finalize() {
899-
_finalizer.detach(detach);
893+
final rc = _bindings.sqlite3changeset_finalize(pointer);
894+
bindings.bindings.changesetFinalizer?.detach(detach);
900895
if (dataPointer case final data?) {
901896
_bindings.free(data);
902897
}
903898

904-
return _bindings.sqlite3changeset_finalize(pointer);
899+
return rc;
905900
}
906901

907902
@override
908903
int sqlite3changeset_next() => _bindings.sqlite3changeset_next(pointer);
909904

910-
SqliteResult<RawSqliteValue> _extractValue(
905+
SqliteResult<RawSqliteValue?> _extractValue(
911906
int Function(Pointer, int, Pointer) extract,
912907
int index,
913908
) {
@@ -923,12 +918,12 @@ final class WasmChangesetIterator implements RawChangesetIterator {
923918
}
924919

925920
@override
926-
SqliteResult<RawSqliteValue> sqlite3changeset_old(int columnNumber) {
921+
SqliteResult<RawSqliteValue?> sqlite3changeset_old(int columnNumber) {
927922
return _extractValue(_bindings.sqlite3changeset_old, columnNumber);
928923
}
929924

930925
@override
931-
SqliteResult<RawSqliteValue> sqlite3changeset_new(int columnNumber) {
926+
SqliteResult<RawSqliteValue?> sqlite3changeset_new(int columnNumber) {
932927
return _extractValue(_bindings.sqlite3changeset_new, columnNumber);
933928
}
934929

sqlite3/lib/src/wasm/wasm_interop.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,24 @@ class WasmBindings {
2525
final DartCallbacks callbacks;
2626
final SqliteExports sqlite3;
2727

28+
// Finalizers are deliberately referenced from this instance only - if the
29+
// entire instance and module is GCed, we don't need to manually invoke
30+
// finalizers anymore.
31+
Finalizer<Pointer>? changesetFinalizer,
32+
sessionFinalizer,
33+
databaseFinalizer,
34+
statementFinalizer;
35+
2836
WasmBindings._(this.instance, _InjectedValues values)
2937
: memory = values.memory,
3038
callbacks = values.callbacks,
3139
sqlite3 = SqliteExports(instance.exports) {
3240
values.bindings = this;
41+
42+
changesetFinalizer = Finalizer((p) => sqlite3.sqlite3changeset_finalize(p));
43+
sessionFinalizer = Finalizer((p) => sqlite3.sqlite3session_delete(p));
44+
databaseFinalizer = Finalizer((p) => sqlite3.sqlite3_close_v2(p));
45+
statementFinalizer = Finalizer((p) => sqlite3.sqlite3_finalize(p));
3346
}
3447

3548
static Future<WasmBindings> instantiateAsync(web.Response response) async {

sqlite3/test/common/session.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ void testSession(FutureOr<CommonSqlite3> Function() loadSqlite) {
152152

153153
session = Session(database)..diff('another', 'entries');
154154
final changeset = session.changeset();
155-
expect(changeset, [
155+
expect(changeset.toList(), [
156156
isOp(
157157
operation: SqliteUpdateKind.update,
158158
oldValues: [1, 'b'],

0 commit comments

Comments
 (0)