fix(rapier): don't panic on JNI calls referencing removed physics bodies - #1432
Closed
Bazzirovan wants to merge 2 commits into
Closed
fix(rapier): don't panic on JNI calls referencing removed physics bodies#1432Bazzirovan wants to merge 2 commits into
Bazzirovan wants to merge 2 commits into
Conversation
Replace expect()/index panics in the rapier JNI bridge with Option guards. A Rust panic crossing the JNI boundary aborts the whole JVM, which on a dedicated server manifests as instant shutdowns or ServerHangWatchdog freezes whenever a rigid body, kinematic contraption or joint endpoint was already removed (e.g. sublevel unload races) but is still referenced by a pending JNI call. - get_rigid_body(_mut): return Option instead of expect() - getPose: fall back to identity pose instead of panicking - removeSubLevel/removeBox/removeKinematicContraption: no-op if missing - setMassProperties/teleport/wakeUp/velocities/forces: no-op if missing - createKinematicContraption: drop mount reference if mount id is stale - add*Constraint: return invalid handle (-1) if an endpoint is missing - rope tick: drop rope attachments whose endpoint collider is gone The invalid handle was changed from 0 to -1 because the first valid impulse joint naturally receives handle 0 (index 0, generation 0), making 0 ambiguous as a failure sentinel. -1 is impossible as a native handle (requires an index of all-ones) and the Java side already guards it via assertValid / isConstraintValid checks. Verified on a live dedicated server (Windows): rope/sublevel crashes and native step hangs are gone after rebuilding the natives with this patch.
|
Bazzirovan seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
Bazzirovan
force-pushed
the
fix/rapier-jni-panic-guards
branch
from
August 3, 2026 18:47
2501d98 to
11c9684
Compare
Owner
|
I'm not a fan of lots of stuff here, like falling back to the identity pose if the body is missing, or just blindly covering up any places where we're still referencing removed bodies. Also not fond of AI generated contributions. What is the actual crash you were encountering? I'd like to fix the callsite that is keeping hold of removed bodies rather than covering this up in the natives. |
Bazzirovan
force-pushed
the
fix/rapier-jni-panic-guards
branch
2 times, most recently
from
August 3, 2026 21:08
688be31 to
b10ec4c
Compare
setCenterOfMass, setLocalBounds and addChunk could panic (Option::unwrap on a None value) when the Java side still held a stale sub-level id after the level collider had already been removed from the physics scene, aborting the whole JVM (non-unwinding panic). Replace the unwraps with early-return no-ops, matching the guards used for ropes and joints.
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.
What happened
I run a dedicated NeoForge 1.21.1 server (Windows) with Sable, and my server kept dying during normal play. Two crash signatures, same underlying cause:
ServerHangWatchdogreports where a tick took 60000+ seconds, with the stuck frame inRapier3D.step(Native Method)NullPointerExceptionon the Java side (e.g.RopePhysicsObject.setFirstSegmentLength) that followed a native abortRoot cause
The Rust JNI bridge panics when a call references a physics object that is already gone:
rigid_bodies[&id],.expect("No rigid body for id"),.expect("No kinematic contraption with given ID!"), and a few.unwrap()s on removed collider info.A panic in Rust that crosses the JNI boundary doesn't unwind cleanly — it kills the whole JVM. On my server this triggered whenever a sublevel unloaded (or unloaded and re-created quickly) while a JNI call still referenced a stale ID. The most common trigger in practice was rope endpoints: a rope anchored to a sublevel body whose collider had already been removed from the scene.
Fix
Replaced the panicking lookups with Option guards, keeping the existing behavior for everything that resolves correctly:
get_rigid_body/get_rigid_body_mut: returnOptioninstead ofexpect()getPose: fall back to the identity pose if the body is missingremoveSubLevel/removeBox/removeKinematicContraption: no-op when the entry is missingsetMassProperties,teleportObject,wakeUpObject, velocity/force applicators: no-op when the body is missingcreateKinematicContraption: drop the stale mount reference instead of panickingadd*Constraint: return an invalid handle (-1) when an endpoint is missing. On the Java sideisConstraintValid(-1)resolves tofalse,assertValid()guards mutation, and the pipeline turns it into a failed constraint creation rather than a crash. The sentinel was chosen as-1(not0) because the first valid impulse joint naturally receives handle 0 (index 0, generation 0), making 0 ambiguous as an error value;-1is impossible as a native handlerope::tick: drop rope attachments whose endpoint collider is no longer registered, same as the existing handling for joints that vanishqueryRope,removeRope,setRopeFirstSegmentLength,removeRopePointAtStart,addRopePointAtStart,wakeUpRope,setRopeAttachment): no-op when the rope id is stale or the attachment's sub-level body is gone, instead of unwrapping and aborting the JVMsetCenterOfMass,setLocalBounds,addChunk(sub-level collider upload): no-op when the level collider with that id has already been removed from the scene (race:SubLevelPhysicsSystem.onStatsChangedticking after the collider was unloaded)The
-1ground-body special case in the constraint functions is untouched.Verification
Rebuilt the Windows natives with these changes and ran the result on the live server. Before the patch: repeated
RopeHandleNPE crashes and watchdog freezes. After: several days of uptime with ropes, sublevel ships and constraints in regular use, no crashes. I also built on the Rust side to make sure nothing else breaks (cargo buildclean).Scope note: I only touched the paths that actually crashed on my server — the dispatcher/contact-event code that indexes colliders during a step is left alone since those colliders are still alive while the step runs.
Files changed:
sable_rapier/src/main/rust/rapier/src/lib.rssable_rapier/src/main/rust/rapier/src/contraptions.rssable_rapier/src/main/rust/rapier/src/boxes.rssable_rapier/src/main/rust/rapier/src/joints.rssable_rapier/src/main/rust/rapier/src/rope.rs