Skip to content

Commit 6f1e786

Browse files
committed
Use native finalizers for db and stmts
1 parent 3bb129a commit 6f1e786

File tree

1 file changed

+131
-80
lines changed

1 file changed

+131
-80
lines changed

sqlite3/lib/src/ffi/bindings.dart

Lines changed: 131 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -812,18 +812,65 @@ final class FfiChangesetIterator implements RawChangesetIterator, Finalizable {
812812
}
813813
}
814814

815-
final class FfiDatabase implements RawSqliteDatabase {
815+
/// For user-defined functions, SQLite hooks, or virtual file systems, we
816+
/// register function pointers as [NativeCallable]s.
817+
///
818+
/// To avoid leaking resources, we should [NativeCallable.close] those once
819+
/// they're no longer used. SQLite provides the `xDestroy` callback for this.
820+
/// For a long time, this package used an additional callable for `xDestroy`
821+
/// that closed the original callables and itself.
822+
///
823+
/// After migrating to native finalizers however, this approach stopped working.
824+
/// Because native finalizers can run as the Dart isolate is shutting down, we
825+
/// can't invoke Dart code anymore. There is no good way to close callables from
826+
/// C (https://dartbug.com/61887), so we can't use `xDestroy` callbacks from
827+
/// SQLite.
828+
///
829+
/// Instead, we:
830+
///
831+
/// - Manually close callables when the database is closed in Dart.
832+
/// - Use (regular, non-native) finalizers to asynchronously close callbacks
833+
/// for databases that haven't been closed manually.
834+
final class _FunctionFinalizers {
835+
final List<NativeCallable> _callables = [];
836+
837+
void closeAll() {
838+
for (final callable in _callables) {
839+
callable.close();
840+
}
841+
}
842+
843+
static final Finalizer<_FunctionFinalizers> finalizer = Finalizer(
844+
(f) => f.closeAll(),
845+
);
846+
}
847+
848+
final class FfiDatabase implements RawSqliteDatabase, Finalizable {
816849
final Pointer<sqlite3> db;
850+
final _FunctionFinalizers _functions = _FunctionFinalizers();
851+
final Object _detachToken = Object();
817852

818853
NativeCallable<_UpdateHook>? _installedUpdateHook;
819854
NativeCallable<_CommitHook>? _installedCommitHook;
820855
NativeCallable<_RollbackHook>? _installedRollbackHook;
821856

822-
FfiDatabase(this.db);
857+
FfiDatabase(this.db) {
858+
databaseFinalizer.attach(this, db.cast(), detach: _detachToken);
859+
_FunctionFinalizers.finalizer.attach(
860+
this,
861+
_functions,
862+
detach: _detachToken,
863+
);
864+
}
823865

824866
@override
825867
int sqlite3_close_v2() {
826-
return libsqlite3.sqlite3_close_v2(db);
868+
final rc = libsqlite3.sqlite3_close_v2(db);
869+
870+
_functions.closeAll();
871+
_FunctionFinalizers.finalizer.detach(_detachToken);
872+
databaseFinalizer.detach(_detachToken);
873+
return rc;
827874
}
828875

829876
@override
@@ -883,15 +930,15 @@ final class FfiDatabase implements RawSqliteDatabase {
883930
required RawCollation collation,
884931
}) {
885932
final name = allocateBytes(collationName, additionalLength: 1);
886-
final compare = collation.toNative();
933+
final compare = collation.toNative(_functions);
887934

888935
final result = libsqlite3.sqlite3_create_collation_v2(
889936
db,
890937
name.cast(),
891938
eTextRep,
892939
nullPtr(),
893940
compare.nativeFunction,
894-
_xDestroy([compare]),
941+
nullPtr(),
895942
);
896943
name.free();
897944

@@ -910,10 +957,10 @@ final class FfiDatabase implements RawSqliteDatabase {
910957
}) {
911958
final functionNamePtr = allocateBytes(functionName, additionalLength: 1);
912959

913-
final step = xStep.toNative();
914-
final $final = xFinal.toNative(true);
915-
final value = xValue.toNative(false);
916-
final inverse = xInverse.toNative();
960+
final step = xStep.toNative(_functions);
961+
final $final = xFinal.toNative(clean: true, finalizers: _functions);
962+
final value = xValue.toNative(clean: false, finalizers: _functions);
963+
final inverse = xInverse.toNative(_functions);
917964

918965
final result = libsqlite3.sqlite3_create_window_function(
919966
db,
@@ -925,7 +972,7 @@ final class FfiDatabase implements RawSqliteDatabase {
925972
$final.nativeFunction,
926973
value.nativeFunction,
927974
inverse.nativeFunction,
928-
_xDestroy([step, $final, value, inverse]),
975+
nullPtr(),
929976
);
930977
functionNamePtr.free();
931978
return result;
@@ -942,9 +989,9 @@ final class FfiDatabase implements RawSqliteDatabase {
942989
}) {
943990
final functionNamePtr = allocateBytes(functionName, additionalLength: 1);
944991

945-
final func = xFunc?.toNative();
946-
final step = xStep?.toNative();
947-
final $final = xFinal?.toNative(true);
992+
final func = xFunc?.toNative(_functions);
993+
final step = xStep?.toNative(_functions);
994+
final $final = xFinal?.toNative(clean: true, finalizers: _functions);
948995

949996
final result = libsqlite3.sqlite3_create_function_v2(
950997
db,
@@ -955,11 +1002,7 @@ final class FfiDatabase implements RawSqliteDatabase {
9551002
func?.nativeFunction ?? nullPtr(),
9561003
step?.nativeFunction ?? nullPtr(),
9571004
$final?.nativeFunction ?? nullPtr(),
958-
_xDestroy([
959-
if (func != null) func,
960-
if (step != null) step,
961-
if ($final != null) $final,
962-
]),
1005+
nullPtr(),
9631006
);
9641007
functionNamePtr.free();
9651008
return result;
@@ -973,7 +1016,7 @@ final class FfiDatabase implements RawSqliteDatabase {
9731016
_installedUpdateHook = null;
9741017
libsqlite3.sqlite3_update_hook(db, nullPtr(), nullPtr());
9751018
} else {
976-
final native = _installedUpdateHook = hook.toNative();
1019+
final native = _installedUpdateHook = hook.toNative(_functions);
9771020
libsqlite3.sqlite3_update_hook(db, native.nativeFunction, nullPtr());
9781021
}
9791022

@@ -988,7 +1031,7 @@ final class FfiDatabase implements RawSqliteDatabase {
9881031
_installedCommitHook = null;
9891032
libsqlite3.sqlite3_commit_hook(db, nullPtr(), nullPtr());
9901033
} else {
991-
final native = _installedCommitHook = hook.toNative();
1034+
final native = _installedCommitHook = hook.toNative(_functions);
9921035
libsqlite3.sqlite3_commit_hook(db, native.nativeFunction, nullPtr());
9931036
}
9941037

@@ -1002,7 +1045,7 @@ final class FfiDatabase implements RawSqliteDatabase {
10021045
if (hook == null) {
10031046
libsqlite3.sqlite3_rollback_hook(db, nullPtr(), nullPtr());
10041047
} else {
1005-
final native = _installedRollbackHook = hook.toNative();
1048+
final native = _installedRollbackHook = hook.toNative(_functions);
10061049
libsqlite3.sqlite3_rollback_hook(db, native.nativeFunction, nullPtr());
10071050
}
10081051

@@ -1024,23 +1067,6 @@ final class FfiDatabase implements RawSqliteDatabase {
10241067
RawStatementCompiler newCompiler(List<int> utf8EncodedSql) {
10251068
return FfiStatementCompiler(this, allocateBytes(utf8EncodedSql));
10261069
}
1027-
1028-
static Pointer<NativeFunction<Void Function(Pointer<Void>)>> _xDestroy(
1029-
List<NativeCallable> callables,
1030-
) {
1031-
void destroy(Pointer<Void> _) {
1032-
for (final callable in callables) {
1033-
callable.close();
1034-
}
1035-
}
1036-
1037-
final callable = NativeCallable<Void Function(Pointer<Void>)>.isolateLocal(
1038-
destroy,
1039-
)..keepIsolateAlive = false;
1040-
callables.add(callable);
1041-
1042-
return callable.nativeFunction;
1043-
}
10441070
}
10451071

10461072
final class FfiStatementCompiler implements RawStatementCompiler {
@@ -1101,12 +1127,15 @@ final class FfiStatementCompiler implements RawStatementCompiler {
11011127
}
11021128
}
11031129

1104-
final class FfiStatement implements RawSqliteStatement {
1130+
final class FfiStatement implements RawSqliteStatement, Finalizable {
11051131
final Pointer<sqlite3_stmt> stmt;
1132+
final Object _detachToken = Object();
11061133

11071134
final List<Pointer> _allocatedArguments = [];
11081135

1109-
FfiStatement(this.stmt);
1136+
FfiStatement(this.stmt) {
1137+
statementFinalizer.attach(this, stmt.cast(), detach: _detachToken);
1138+
}
11101139

11111140
@visibleForTesting
11121141
List<Pointer> get allocatedArguments => _allocatedArguments;
@@ -1251,6 +1280,7 @@ final class FfiStatement implements RawSqliteStatement {
12511280
@override
12521281
void sqlite3_finalize() {
12531282
libsqlite3.sqlite3_finalize(stmt);
1283+
statementFinalizer.detach(_detachToken);
12541284
}
12551285

12561286
@override
@@ -1455,72 +1485,93 @@ typedef _UpdateHook =
14551485
typedef _CommitHook = Int Function(Pointer<Void>);
14561486
typedef _RollbackHook = Void Function(Pointer<Void>);
14571487

1488+
extension on NativeCallable {
1489+
void closeIn(_FunctionFinalizers finalizers) {
1490+
finalizers._callables.add(this);
1491+
}
1492+
}
1493+
14581494
extension on RawXFunc {
1459-
NativeCallable<_XFunc> toNative() {
1495+
NativeCallable<_XFunc> toNative(_FunctionFinalizers finalizers) {
14601496
return NativeCallable.isolateLocal((
1461-
Pointer<sqlite3_context> ctx,
1462-
int nArgs,
1463-
Pointer<Pointer<sqlite3_value>> args,
1464-
) {
1465-
this(FfiContext(ctx), _ValueList(nArgs, args));
1466-
})..keepIsolateAlive = false;
1497+
Pointer<sqlite3_context> ctx,
1498+
int nArgs,
1499+
Pointer<Pointer<sqlite3_value>> args,
1500+
) {
1501+
this(FfiContext(ctx), _ValueList(nArgs, args));
1502+
})
1503+
..closeIn(finalizers)
1504+
..keepIsolateAlive = false;
14671505
}
14681506
}
14691507

14701508
extension on RawXFinal {
1471-
NativeCallable<_XFinal> toNative(bool clean) {
1509+
NativeCallable<_XFinal> toNative({
1510+
required bool clean,
1511+
required _FunctionFinalizers finalizers,
1512+
}) {
14721513
return NativeCallable.isolateLocal((Pointer<sqlite3_context> ctx) {
1473-
final context = FfiContext(ctx);
1474-
this(context);
1475-
if (clean) context.freeContext();
1476-
})..keepIsolateAlive = false;
1514+
final context = FfiContext(ctx);
1515+
this(context);
1516+
if (clean) context.freeContext();
1517+
})
1518+
..closeIn(finalizers)
1519+
..keepIsolateAlive = false;
14771520
}
14781521
}
14791522

14801523
extension on RawCollation {
1481-
NativeCallable<_XCompare> toNative() {
1524+
NativeCallable<_XCompare> toNative(_FunctionFinalizers finalizers) {
14821525
return NativeCallable.isolateLocal((
1483-
Pointer<Void> _,
1484-
int lengthA,
1485-
Pointer<Void> a,
1486-
int lengthB,
1487-
Pointer<Void> b,
1488-
) {
1489-
final dartA = a.cast<sqlite3_char>().readNullableString(lengthA);
1490-
final dartB = b.cast<sqlite3_char>().readNullableString(lengthB);
1491-
1492-
return this(dartA, dartB);
1493-
}, exceptionalReturn: 0)..keepIsolateAlive = false;
1526+
Pointer<Void> _,
1527+
int lengthA,
1528+
Pointer<Void> a,
1529+
int lengthB,
1530+
Pointer<Void> b,
1531+
) {
1532+
final dartA = a.cast<sqlite3_char>().readNullableString(lengthA);
1533+
final dartB = b.cast<sqlite3_char>().readNullableString(lengthB);
1534+
1535+
return this(dartA, dartB);
1536+
}, exceptionalReturn: 0)
1537+
..closeIn(finalizers)
1538+
..keepIsolateAlive = false;
14941539
}
14951540
}
14961541

14971542
extension on RawUpdateHook {
1498-
NativeCallable<_UpdateHook> toNative() {
1543+
NativeCallable<_UpdateHook> toNative(_FunctionFinalizers finalizers) {
14991544
return NativeCallable.isolateLocal((
1500-
Pointer<Void> _,
1501-
int kind,
1502-
Pointer<sqlite3_char> db,
1503-
Pointer<sqlite3_char> table,
1504-
int rowid,
1505-
) {
1506-
final tableName = table.readString();
1507-
this(kind, tableName, rowid);
1508-
})..keepIsolateAlive = false;
1545+
Pointer<Void> _,
1546+
int kind,
1547+
Pointer<sqlite3_char> db,
1548+
Pointer<sqlite3_char> table,
1549+
int rowid,
1550+
) {
1551+
final tableName = table.readString();
1552+
this(kind, tableName, rowid);
1553+
})
1554+
..closeIn(finalizers)
1555+
..keepIsolateAlive = false;
15091556
}
15101557
}
15111558

15121559
extension on RawCommitHook {
1513-
NativeCallable<_CommitHook> toNative() {
1560+
NativeCallable<_CommitHook> toNative(_FunctionFinalizers finalizers) {
15141561
return NativeCallable.isolateLocal((Pointer<Void> _) {
1515-
return this();
1516-
}, exceptionalReturn: 1)..keepIsolateAlive = false;
1562+
return this();
1563+
}, exceptionalReturn: 1)
1564+
..closeIn(finalizers)
1565+
..keepIsolateAlive = false;
15171566
}
15181567
}
15191568

15201569
extension on RawRollbackHook {
1521-
NativeCallable<_RollbackHook> toNative() {
1570+
NativeCallable<_RollbackHook> toNative(_FunctionFinalizers finalizers) {
15221571
return NativeCallable.isolateLocal((Pointer<Void> _) {
1523-
this();
1524-
})..keepIsolateAlive = false;
1572+
this();
1573+
})
1574+
..closeIn(finalizers)
1575+
..keepIsolateAlive = false;
15251576
}
15261577
}

0 commit comments

Comments
 (0)