serialize<T> method
Runs body after every earlier serialize call on this cache has
settled, so that resolving paths, deciding on reuse and building are one
indivisible step.
Without this the three steps have awaits between them and two callers can both finish deciding before either records a model: two worker isolates, two compiles, and the loser orphaned with nobody to close it. It also removes the reordering hazard — moving an await earlier in the entry point silently widened that window once already.
Not reentrant: a body that calls back into the same entry point waits
for itself. Nothing in the plugin does; a backend's createModel that
called getActiveEmbedder would.
Implementation
Future<T> serialize<T>(Future<T> Function() body) {
final previous = _lane;
// The lane advances on a completer of its own rather than on a handler
// attached to the returned future. Attaching one there marks the caller's
// error as HANDLED, so a fire-and-forget `createEmbeddingModel()` that
// failed reported nothing at all — the silence this whole change is
// against. `gate` only ever completes with a value, so a failure belongs
// to its own caller and still cannot reach the next one in line.
final gate = Completer<void>();
_lane = gate.future;
return previous.then((_) => body()).whenComplete(gate.complete);
}