Skip to content

Add scene disposal checks to prevent errors during cleanup#648

Merged
tracygardner merged 2 commits into
mainfrom
claude/vibrant-bohr-Xtadk
May 22, 2026
Merged

Add scene disposal checks to prevent errors during cleanup#648
tracygardner merged 2 commits into
mainfrom
claude/vibrant-bohr-Xtadk

Conversation

@tracygardner
Copy link
Copy Markdown
Contributor

@tracygardner tracygardner commented May 22, 2026

Summary

Add defensive checks throughout the model loading and object creation pipeline to gracefully handle cases where the scene is disposed or becomes unavailable during asynchronous operations. This prevents errors and unhandled promise rejections when operations complete after scene cleanup.

Key Changes

  • Early scene validation: Added checks at the start of createCharacter and createObject to bail out if no active scene exists
  • Promise rejection handling: Attached a catch handler to readyPromise to prevent unhandled rejection warnings when loads are aborted without consumers
  • Post-load scene checks: Added disposal checks in .then() blocks after container loading completes, ensuring we don't attempt to add meshes to a disposed scene
  • Graceful cleanup: When a scene is disposed during loading, containers are properly disposed and internal state (like modelsBeingLoaded) is cleaned up
  • Error suppression: Wrapped disposal calls in try-catch to suppress non-critical errors during cleanup

Implementation Details

The changes follow a consistent pattern: check if flock.scene is null or flock.scene.isDisposed at critical points where the scene is accessed. This is particularly important in promise chains where the scene state can change between when the promise was created and when it resolves. The implementation ensures that even if a scene is disposed mid-operation, no errors propagate and resources are properly cleaned up.

https://claude.ai/code/session_01ENptDkxsw6UEFt5BDdydPt

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced scene validity checks when creating characters and objects, returning appropriate error codes when scenes are unavailable.
    • Suppressed unhandled rejection warnings during asset loading.
    • Improved cleanup and resource disposal when scenes are disposed during character or object creation.
    • Fixed edge cases where asset containers could remain unmanaged if scenes were disposed while loading.

Review Change Stack

Loading characters in a tight loop and re-running could leave in-flight
LoadAssetContainerAsync calls outliving disposeOldScene(). This caused an
unhandled "aborted" rejection, "No scene available" warnings, and a null
scene crash in setupMesh.

- Attach a benign catch to the readiness promise so aborted loads don't
  surface as unhandled rejections when no whenModelReady consumer exists.
- Bail out of createCharacter/createObject early when no live scene.
- Skip the load .then handlers (dispose the container instead) when the
  scene was disposed or the load aborted mid-flight.

https://claude.ai/code/session_01ENptDkxsw6UEFt5BDdydPt
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

Warning

Rate limit exceeded

@tracygardner has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 48 minutes and 18 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: da176438-b6a1-4643-9950-2b09626c1724

📥 Commits

Reviewing files that changed from the base of the PR and between 4c72c73 and a008936.

📒 Files selected for processing (1)
  • api/models.js
📝 Walkthrough

Walkthrough

Added disposal-safe checks throughout the model loading pipeline. createCharacter and createObject now validate scene availability upfront; during async asset loading, both detect scene disposal and clean up resources without touching an invalid scene. A no-op .catch() suppresses unhandled-rejection warnings.

Changes

Scene Disposal Resilience in Model Loading

Layer / File(s) Summary
Entry-point scene validation
api/models.js
createCharacter and createObject both check for scene availability at entry; if missing or disposed, they log a warning and return "error_no_scene".
createCharacter async disposal handling and error suppression
api/models.js
A no-op .catch() is attached to readyPromise to prevent unhandled-rejection noise. During LoadAssetContainerAsync resolution, disposal or abort detection triggers best-effort container cleanup and early return.
createObject async disposal handling during model loading
api/models.js
When scene disposal is detected during in-flight modelsBeingLoaded[modelName] promises, the continuation short-circuits. In the main load path, disposal also triggers container cleanup, cache deletion, and early return.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • flipcomputing/flock#401: Both PRs modify api/models.js's model-loading flow—specifically the flock.modelsBeingLoaded[modelName] lifecycle and cleanup logic when loading is interrupted by scene disposal.

Poem

🐰 A scene that vanishes mid-load,
once crashed the creatures we had sowed.
Now guards and checks prevent the fall,
the rabbit's code protects them all! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding scene disposal checks to prevent errors during cleanup, which is precisely what the changeset does.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/vibrant-bohr-Xtadk

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 22, 2026

Deploying flockxr with  Cloudflare Pages  Cloudflare Pages

Latest commit: a008936
Status: ✅  Deploy successful!
Preview URL: https://3d6566fb.flockxr.pages.dev
Branch Preview URL: https://claude-vibrant-bohr-xtadk.flockxr.pages.dev

View logs

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
api/models.js (1)

367-377: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

These disposal exits leak modelReadyPromises and reserved names.

Both new return paths leave the per-call readiness promise pending forever and never release meshName. That can strand whenModelReady callers and block future reuse after scene teardown. createObject needs the same failure cleanup contract as createCharacter here.

Proposed fix
-      let resolveReady;
-      const readyPromise = new Promise((res) => {
-        resolveReady = res;
-      });
+      let resolveReady, rejectReady;
+      const readyPromise = new Promise((res, rej) => {
+        resolveReady = res;
+        rejectReady = rej;
+      });
       flock.modelReadyPromises.set(meshName, readyPromise);
+
+      const failReady = (error) => {
+        rejectReady?.(error);
+        flock.modelReadyPromises.delete(meshName);
+        flock._releaseName?.(meshName);
+      };
...
       if (flock.modelsBeingLoaded[modelName]) {
         flock.modelsBeingLoaded[modelName].then(() => {
-          if (!flock.scene || flock.scene.isDisposed) return;
+          if (!flock.scene || flock.scene.isDisposed) {
+            failReady(new Error("scene disposed"));
+            return;
+          }
           flock._recycleOldestByKey(modelName);
           const mesh = flock.modelCache[modelName].clone(
             flock.modelCache[modelName].name,
           );
...
       loadPromise.then((container) => {
         // Scene disposed while the container was loading: drop it.
         if (!flock.scene || flock.scene.isDisposed) {
           try {
             container?.dispose?.();
           } catch (_) {
             console.warn("Suppressed non-critical error:", _);
           }
+          failReady(new Error("scene disposed"));
           delete flock.modelsBeingLoaded[modelName];
           return;
         }

Also applies to: 390-400

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/models.js` around lines 367 - 377, The then() callbacks inside
flock.modelsBeingLoaded[modelName] currently bail early on scene/disposed checks
and never call the per-call readiness resolver or release the reserved meshName,
leaking modelReadyPromises and names; update those early-return paths (both the
block around lines shown and the similar block at 390-400) to perform the same
cleanup as createCharacter: call the per-invocation resolve/reject helper
(resolveReady/rejectReady or the local promise resolver used to fulfill
modelReadyPromises) so the waiting promise completes, remove the meshName
reservation (the data structure that records reserved names), and ensure any
modelReadyPromises entry for that call is cleared before returning; mirror the
cleanup sequence used in createCharacter (finalizing the promise and releasing
the name) so no promises or name reservations are left stranded when flock.scene
is missing or disposed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/models.js`:
- Around line 145-157: The disposed-scene branch currently just disposes the
container and doesn't perform the same abort cleanup, leaving readyPromise
unresolved and meshName reserved; modify the branch handling signal?.aborted ||
!flock.scene || flock.scene.isDisposed to invoke the same abort cleanup used for
an explicit abort (i.e., call the routine that rejects readyPromise and releases
meshName — the same behavior as the abort path), ensuring you use the existing
cleanupAbort/onAbort helper (or explicitly reject readyPromise and free
meshName) so any whenModelReady waiters are settled and the reserved name is
released.

---

Outside diff comments:
In `@api/models.js`:
- Around line 367-377: The then() callbacks inside
flock.modelsBeingLoaded[modelName] currently bail early on scene/disposed checks
and never call the per-call readiness resolver or release the reserved meshName,
leaking modelReadyPromises and names; update those early-return paths (both the
block around lines shown and the similar block at 390-400) to perform the same
cleanup as createCharacter: call the per-invocation resolve/reject helper
(resolveReady/rejectReady or the local promise resolver used to fulfill
modelReadyPromises) so the waiting promise completes, remove the meshName
reservation (the data structure that records reserved names), and ensure any
modelReadyPromises entry for that call is cleared before returning; mirror the
cleanup sequence used in createCharacter (finalizing the promise and releasing
the name) so no promises or name reservations are left stranded when flock.scene
is missing or disposed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d8553aa5-1fda-4a69-9c1a-fb316cf3cb08

📥 Commits

Reviewing files that changed from the base of the PR and between 074bf5f and 4c72c73.

📒 Files selected for processing (1)
  • api/models.js

Comment thread api/models.js
CodeRabbit flagged that createCharacter's disposed-scene branch only
disposed the container. When the scene is disposed without an abort,
readyPromise never settles and the reserved name leaks, hanging any
whenModelReady waiter. Mirror the abort cleanup in that branch.

https://claude.ai/code/session_01ENptDkxsw6UEFt5BDdydPt
@tracygardner tracygardner merged commit 695a15d into main May 22, 2026
13 checks passed
@tracygardner tracygardner deleted the claude/vibrant-bohr-Xtadk branch May 22, 2026 13:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants