dispose method
void
dispose()
Release the native handle. Must be called when done.
Marks disposed FIRST (every Isolate.run FFI method early-returns on
_disposed, and the check + _runFfi spawn happen with no await
between them, so no new background call can start once this runs), then
defers the
actual coreFree until any IN-FLIGHT background-isolate calls finish —
otherwise coreFree would free the handle while a background isolate is
still dereferencing it (use-after-free).
Implementation
void dispose() {
if (_disposed) return;
_disposed = true;
clearStreamCallback();
clearHsiCallback();
final pending = _inflightFfi.toList();
Future<void> freeWhenIdle() async {
if (pending.isNotEmpty) {
try {
await Future.wait<Object?>(pending);
} catch (_) {
// A failed in-flight call still completed (no longer touching the
// handle) — proceed to free.
}
}
_ffi.coreFree(_handle);
// Only now is it safe to free the trampolines. `coreFree` drops the
// handle, which drops the embedded tokio runtime and joins its workers,
// so no task can be part-way through a callback dispatch any more. Doing
// this before `coreFree` is what crashed — see [_retiredCallables].
for (final callable in _retiredCallables) {
callable.close();
}
_retiredCallables.clear();
if (_sdkCryptoTable != null) {
calloc.free(_sdkCryptoTable!);
_sdkCryptoTable = null;
}
}
unawaited(freeWhenIdle());
}