fix: Share single Deno/V8 worker thread#237
Merged
Conversation
Move the worker thread + command dispatch loop into a lazy_static global (VL_CONVERTER_RUNTIME) so every VlConverter::new() reuses the same V8 isolate. This eliminates segfaults from multiple V8 isolates running across threads (#206). Font config changes (register_font_directory) are tracked with an atomic version counter (FONT_CONFIG_VERSION). Before each command the worker checks the version and refreshes SharedFontConfig in OpState. To handle Vega's internally cached text-measurement canvas, the canvas text ops (measureText, fillText, strokeText) also compare the version and hot-swap the FontSystem when fonts have changed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…t font refresh - Replace lazy_static singleton with Mutex<Option<VlConverterRuntime>> so the worker thread can be respawned if it exits unexpectedly - Add get_or_spawn_sender() that checks JoinHandle::is_finished() and respawns transparently; VlConverter no longer caches a sender - Extract spawn_worker_thread() from the lazy_static initializer block - Catch refresh_font_config_if_needed errors per-command via VlConvertCommand::send_error() instead of killing the worker with ? - Add refresh_canvas_fonts_if_needed to op_canvas_set_font - Add test_font_version_propagation test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Share a single Deno/V8 worker thread across all
VlConverterinstances, fixing segfaults caused by multiple V8 isolates coexisting in the same process (#206). Adds font configuration version tracking so dynamically-registered fonts propagate to long-lived canvas contexts, and a worker respawn mechanism so the process can recover from worker thread failures.Motivation
Previously, each
VlConverter::new()spawned a dedicated worker thread with its own V8 isolate. This caused segfaults when multipleVlConverterinstances existed simultaneously (#206), because V8 isolates cannot safely coexist in the same process.Changes
Core architecture (
vl-convert-rs/src/converter.rs, +214/-118)VlConverterRuntimestruct holds aSender<VlConvertCommand>and the worker threadJoinHandleVL_CONVERTER_RUNTIMEis astatic Mutex<Option<VlConverterRuntime>>so the worker can be respawned if it exitsget_or_spawn_sender()checks if the worker is alive viaJoinHandle::is_finished(); if the worker has exited, it spawns a fresh one transparentlyspawn_worker_thread()extracted as a standalone function (was previously inlined inlazy_static!)VlConverterno longer caches a sender — each method call goes throughget_or_spawn_sender(), ensuring it always talks to a live workerError resilience
VlConvertCommand::send_error()method forwards errors to the command's oneshot responder regardless of variant typerefresh_font_config_if_needederrors in the worker loop are caught, sent to the current command's responder, and the loop continues (previously the?operator killed the worker thread)Font version propagation
An
AtomicU64counter (FONT_CONFIG_VERSION) is incremented when fonts are registered viaregister_font_directory(). The worker thread and individual canvas contexts each track the last-seen version, refreshing their font databases lazily when a mismatch is detected. This ensures dynamically-registered fonts are available even in long-lived canvas contexts that Vega caches internally.op_canvas_set_fontnow callsrefresh_canvas_fonts_if_needed, matching the pattern in the other text ops (measureText,fillText,strokeText, etc.). This ensures newly-registered fonts are available when Vega sets a font, not just when it measures or renders text.Supporting changes
SharedFontConfigchanged from tuple struct to named fields (addsversion: u64)CanvasResource::new()accepts initial font config versionCanvas2dContext::update_font_database()method for hot-swapping fonts on existing contextsReview Tour
vl-convert-rs/src/converter.rs— The core architectural change. Search forfn spawn_worker_thread()for the worker loop,fn get_or_spawn_sender()for the respawn mechanism, andimpl VlConverterfor the simplified public API.VlConvertCommand::send_error()is near the enum definition at the bottom.vl-convert-rs/src/text.rs— GlobalFONT_CONFIG_VERSIONatomic counter, incremented inregister_font_directoryvl-convert-canvas2d-deno/src/ops/text.rs— Per-canvasrefresh_canvas_fonts_if_needed()helper that propagates font changes from the shared config to individual canvas contexts. Called in 6 text ops (includingop_canvas_set_font).vl-convert-canvas2d-deno/src/lib.rs—SharedFontConfigstruct evolution (tuple to named fields with version)vl-convert-canvas2d-deno/src/ops/mod.rs—op_canvas_createcaptures font versionvl-convert-canvas2d-deno/src/resource.rs—CanvasResourcegainsfont_config_versionfieldvl-convert-canvas2d/src/context/mod.rs—update_font_database()method onCanvas2dContextTesting
Existing test suite passes (117 tests via
cargo test -p vl-convert-rs -- --test-threads=1). The architectural change is transparent to the public API — all existing conversions produce identical output.New test
test_font_version_propagationverifies thatregister_font_directoryincrements the version counter and that the worker thread survives the font config refresh, confirming the 3-layer version propagation mechanism works end-to-end.