Repository navigation
Free sd_ctx_t at process exit before ggml-metal teardown, and a one-argument way to clear the progress callback #10
Description
Release triage — 2026-10-05
Priority: P1 · production macOS lifecycle correctness. Flutter macOS quit with an image model loaded reaches ggml-metal's static destructor with live buffers and aborts; an unhandled Dart error during load or generation aborts the VM through the progress callback. The Dart finalizer repair in leehack/llamadart#823 covers only orderly isolate shutdown. Mirror of leehack/llamadart-native#96; reuse its registry design once that lands rather than designing a second one.
Release disposition: resolve the production quit path, or record an explicit owner-approved limitation with the onExitRequested disposal workaround and its qualified scope. Debug hot restart is a separate acceptance case.
Acceptance: normal context disposal, idle loaded-model quit, quit during new_sd_ctx/generate_image, repeated create/free, hot restart then quit; bounded coordination, no free under an active native call, no double free, no hanging exit; and a one-argument progress-callback clear (or native no-op sink) so no Dart callback is invoked after VM shutdown. This triage reviewed the existing reproduction evidence; it did not rerun the crash.
Need
Two exports that llamadart can call from Dart FFI, each a plain C function taking at most one pointer argument:
- A way to free every live
sd_ctx_tduring Cexit(), before ggml-metal's static device destructor runs, when the process exits without shutting its Dart isolates down. - A one-argument (or zero-argument) function that clears the process-wide progress callback, for example
void sd_dart_clear_progress_callback(void *). llamadart can then attach it as aNativeFinalizeron the isolate that owns the progress callback.
Why
On macOS Metal, exit → __cxa_finalize_ranges → ggml_metal_device_free → ggml_metal_rsets_free aborts with GGML_ASSERT([rsets->data count] == 0) while an image model is loaded.
leehack/llamadart#823 frees the context from a NativeFinalizer when the image worker isolate shuts down. That covers plain Dart programs that return from main or throw. Two paths remain open:
- Flutter macOS quit and hot restart (macOS Metal: quitting after a Flutter hot restart aborts on models the discarded isolate loaded llamadart#813): no isolate is shut down, so the Dart side gets no chance to free the context. The crash report shows frames in
libstable-diffusion.dylibunder-[NSApplication terminate:]. The same need is filed for llama.cpp in Free remaining llama.cpp objects at process exit before ggml-metal teardown (Flutter quit, hot restart) llamadart-native#96. - Unhandled error during a generation: llamadart routes progress to a
NativeCallable.listener. Whenmaindies mid-generation,generate_imagekeeps calling that callback while the VM shuts down, and the VM aborts withGetFfiCallbackMetadata called after shutdown. Reproduced 3 of 3 times with SDXS 512x512. With (2), llamadart would clear the callback when the calling isolate shuts down, which happens before the VM stops accepting callbacks.
Constraints
- The
atexit-time free must not free a context while a worker thread is insidegenerate_imageornew_sd_ctx. It can wait with a bound, or skip. - Contexts freed normally must be untracked, so they are never freed twice.
Activity
leehack commentedon Oct 1, 2026
Two more cases from the independent audit of leehack/llamadart#823:
new_sd_ctx(image model load): llamadart sets the progress callback during the load, only to stop sd.cpp printing progress bars to stdout. Throwing 500–700 ms intoImageGenerationEngine.loadwith SDXS aborts withGetFfiCallbackMetadata called after shutdown(3/3 here, 4/4 in the audit). A native no-op progress sink, or a quiet flag, would let llamadart avoid installing a Dart callback during loads. The one-argument clear function requested above would also cover it.new_sd_ctxreturns, so Dart can never register the new context for shutdown. Tracking has to start inside the native creation call.Keep the upstream build directory short for Windows
Move the Vulkan shader generator build to a short path
Record progress natively instead of calling back
Check DLL exports and smoke-test macOS in CI
Keep a history of progress reports
Pin the Dart SDK of the dart vm jobs
Add an ordered progress recorder that needs no callback (#14)
Only load models on a Metal device that cannot compute
Wait for a free in flight, and longer for a load or a generation
Test a quit during the last free, and require Metal on request
Free tracked contexts at exit before ggml-metal teardown (#15)
Pin the exit teardown wait defaults and call counters in the stand-in…
fix: adopt stable-diffusion-native v0.2.0-1 with polled progress and …
leehack commentedon Oct 7, 2026
Fixed by #13, #15 and #16, released in v0.2.0-1, adopted in llamadart by #945. The requested one-argument callback clear was replaced by polled progress (
sd_dart_progress_read), so no Dart callback is installed. Remaining limit: a quit during image generation waits for the generation to finish.