minigpu_ffi_completion library
COMPLETION DELIVERY FOR ASYNC GPU WORK.
Every async minigpu entry point starts work on the WebGPU worker thread and signals Dart when it finishes. How that signal travels is the whole subject of this file, because the obvious way is unsound.
Why not a NativeCallable
final nc = NativeCallable<Void Function()>.listener(cb);
try {
ffi.mgpuSomethingAsync(..., nc.nativeFunction);
await completer.future;
} finally {
nc.close(); // ← THE BUG
}
close() DELETES the trampoline, and the C layer cannot be told to forget
a pointer it has already been handed — there is no cancel. So the finally
is a bet that native will never call again, and the VM's penalty for losing
it is not an exception:
runtime_entry.cc: error: Callback invoked after it has been deleted.
That is an unconditional FATAL: the whole process dies, uncatchable, and
Flutter reports it as Lost connection to device. Worse, close() is not
the only deleter — isolate teardown deletes every callback the isolate
owns, so hot restart, a hard kill, and any worker isolate exiting mid-work
are all fatal too, and no amount of Dart-side discipline can cover them.
The port
Posting to a Dart port that is closed, or whose isolate is gone, is a
defined, silent no-op. It is thread-safe from any thread, with or without
an isolate — which is exactly what the WebGPU worker and Dawn's own threads
are. So completions travel as a port message, and the failure mode above
stops existing rather than being guarded against. The log stream already
made this move for the same reason; see minigpu_ffi_log_port.dart.
WIRE FORMAT: one int64 per completion, (token << 1) | ok. Tokens are
allocated here, never reused, and dropped from _pending as soon as they
resolve — so a late or duplicate completion is a map miss rather than
somebody else's future resolving early.
The fallback
A build without the vendored Dart API (the Emscripten/web library) cannot
post to a port, so VoidCompletionSlot / IntCompletionSlot remain: pooled
NativeCallables that are recycled but never closed, which removes the
abort even though it cannot remove the trampoline. They are the fallback,
not the default.
Classes
- GpuCompletion
- Issues async GPU work and awaits its completion.
- IntCompletionSlot
-
A pooled
void(int)completion callback for the no-port fallback — the shared-texture blits report success as an int. Same lifetime rules as VoidCompletionSlot. - MgpuElementType
- Element type codes for mgpuReadAsyncToPort.
- VoidCompletionSlot
-
A pooled
void()completion callback for the no-port fallback.
Functions
-
mgpuContextInitializeAsyncToPort(
Pointer< MGPUContextHandle> handle, int port, int token) → void -
mgpuDispatchAsyncToPort(
Pointer< MGPUComputeShader> shader, int groupsX, int groupsY, int groupsZ, int port, int token) → void -
mgpuDrainWorkQueue(
) → void - Blocks until every task already queued on the WebGPU worker thread has run.
-
mgpuInitializeContextAsyncToPort(
int port, int token) → void -
mgpuReadAsyncToPort(
Pointer< MGPUBuffer> buffer, Pointer<Void> outputData, int elementCount, int elementOffset, int elementType, int port, int token) → void
Typedefs
-
IntCallbackIssue
= void Function(Pointer<
NativeFunction< callback)Void Function(Int)> > -
…or with a
void(int)callback pointer, for work that reports success. - PortIssue = void Function(int port, int token)
- How an operation is started when the port path is available.
-
VoidCallbackIssue
= void Function(Pointer<
NativeFunction< callback)Void Function()> > -
How it is started when it is not: with a
void()callback pointer.