fix: don't re-enter configure() while an async gl factory is pending#3783
fix: don't re-enter configure() while an async gl factory is pending#3783tyrauber wants to merge 1 commit into
Conversation
configure() runs from the Canvas layout effect on every commit. When an async gl factory (e.g. a WebGPURenderer that awaits init) is still initializing, a re-render re-enters the body past the `if (!state.gl)` guard and invokes the factory a second time: two renderers share one canvas and every frame fails GPU validation. Re-entrant calls now wait for the in-flight configure to settle, so they run against settled state and every one-time guard holds. Synchronous gl configs are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit fe21c1b:
|
|
One deliberate scoping note for reviewers. The latch clears on the success path, so if the async Making it fully throw-safe is a one-liner in spirit: wrap the body in |
Fixes #3782.
Why:
configure()runs from the Canvas layout effect on every commit and guards renderer creation withif (!state.gl). That check runs before theawaitof an asyncglfactory resolves. When the owner re-renders while the factory is still pending (asetStatefrom a sibling effect is enough), a secondconfigure()slips past the guard and invokes the factory again. With a WebGPU renderer that means two renderers on one canvas, and every frame fails GPU validation on a depth-buffer size mismatch. We also traced silent WebGPU canvas death on client-side navigation in static exports to this same race.The fix:
serialize
configure()on a per-root latch. A re-entrant call waits for the in-flight configure to settle, then runs against settled state, seesstate.glis set, and skips renderer creation. The latch clears synchronously at the end, so a synchronousglconfig stays synchronous and the existing XR path is unaffected.Test plan:
configure()calls with an asyncglfactory invoke it once, not twice. Fails on master (factory called twice), passes with the fix.