serialize<T> method

Future<T> serialize<T>(
  1. Future<T> body()
)

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);
}