reuseOrInvalidate method
Future<EmbeddingModel?>
reuseOrInvalidate(
- ActiveEmbedderParams requested, {
- required String label,
The cached embedder when it matches requested, else null for "build one".
A mismatch closes the cached model before returning, so the caller only ever has to handle "reuse this" or "build a new one".
Implementation
Future<EmbeddingModel?> reuseOrInvalidate(
ActiveEmbedderParams requested, {
required String label,
}) async {
final cached = _cached;
if (cached == null) return null;
// Checked, not trusted. Eviction rides the close listener, so a model that
// never fires one — or that was already closed when it was recorded, after
// which `fireCloseListeners` has nothing left to call — would be handed to
// every later caller, and every `generateEmbedding` on it throws. Only as
// good as the model's own `isClosed`: the interface default is false, for
// implementations that predate it.
if (cached.model.isClosed) {
gemmaLog('ℹ️ Cached embedder is closed; building a new one for $label');
_cached = null;
return null;
}
final changedParam = cached.params.firstDifference(requested);
if (changedParam == null) {
gemmaLog('ℹ️ Reusing existing embedding model instance for $label');
return cached.model;
}
gemmaLog(
'⚠️ Embedder config changed ($changedParam) for $label — rebuilding',
);
// Dropped BEFORE the await, not after: while a close is in flight the
// cached model is no longer a valid answer to anybody.
_cached = null;
gemmaLog('🔄 Closing old embedding model and creating new one...');
// Reported on its own terms, not as the new caller's failure. They asked for
// a different embedder; handing them the old one's teardown error would name
// neither model, and the rebuild they asked for would never happen. The
// inference lane in the shells does the same (see `createModel`). Nothing
// depends on this succeeding — the bookkeeping is already cleared above.
try {
await cached.model.close();
} catch (e, st) {
// `print`, not `gemmaLog`, for the reason `_warn` in
// flutter_gemma_litertlm's litert_default_scope.dart already documents:
// gemmaLog opens with `if (!kDebugMode) return`, so it is silent in
// release — and release is the build where a leaked worker isolate gets
// debugged. A teardown that throws leaves that isolate and its native
// model alive, so this is worth a line that reaches logcat. It fires only
// in an abnormal state, so it costs nothing in the normal case.
// ignore: avoid_print
print(
'[flutter_gemma] WARNING: the old embedder\'s close() threw while '
'rebuilding for $label; its worker isolate and native model may be '
'leaked: $e\n$st',
);
}
return null;
}