Skip to content

Queue fragile block breaks till after the physics step - #1632

Open
LunarECL wants to merge 1 commit into
ryanhcode:mainfrom
LunarECL:fix/defer-fragile-block-breaks
Open

LunarECL wants to merge 1 commit into
ryanhcode:mainfrom
LunarECL:fix/defer-fragile-block-breaks

Conversation

@LunarECL

Copy link
Copy Markdown

Fixes #1603.

Problem

FragileBlockCallback.onHit is called from the contact callback while Rapier3D.step is running, and it breaks the block right there. The neighbour updates from destroyBlock go through SubLevelPhysicsSystem.handleBlockChange → RapierPhysicsPipeline.handleBlockChange → RapierVoxelColliderBakery.getPhysicsDataForBlock. When the neighbouring block ends up in a state that has no collider yet (leaves changing distance, vines losing a face, crops, …), Rapier3D.newVoxelCollider is called on the server thread while that same thread is still inside step. The thread never comes back, and the server watchdog kills it after 60 s.

We hit this on a NeoForge 21.1.248 dedicated server (Sable 2.0.6, Create Aeronautics) when ships hit trees. Every crash report had the same stack:

Rapier3D.newVoxelCollider (Native Method)
RapierVoxelColliderBakery.buildPhysicsDataForBlock
RapierPhysicsPipeline.handleBlockChange
SubLevelPhysicsSystem.handleBlockChange
LevelChunk.setBlockState / Level.destroyBlock / Block.updateOrDestroy (neighbour shape updates)
FragileBlockCallback.onHit
FragileBlockCallback.sable$onCollision
Rapier3D.step (Native Method)
SubLevelPhysicsSystem.tickPipelinePhysics

Change

Same idea as 6cd9a29 ("Queue rope wake-ups till after the physics tick"):

  • While IN_PHYSICS_STEP is set, FragileBlockCallback.onHit queues the break on the stepping SubLevelPhysicsSystem and returns the same result it does today (zero tangent motion, remove collision). Outside the step it still breaks the block straight away.
  • SubLevelPhysicsSystem breaks the queued blocks right after pipeline.physicsTick returns, in the same substep and before processSubLevelRemovals, so sub-levels removed by the breaks are still handled in that substep as before.
  • A queued block is only broken if its state is still the one that was hit. An earlier break in the same batch may already have changed it.
  • The breaking itself, including melting ice, is moved unchanged into FragileBlockCallback.breakFragileBlock.

Callbacks that override onHit (bells, beehives, explosives, belts) still change the world during the step. This PR doesn't touch them.

Testing

  • :common:compileJava, :neoforge:compileJava, :fabric:compileJava.
  • I reproduced the hang on a copy of our server (NeoForge 21.1.248, Sable 2.0.6) with a flat world. I put a 3×3 stone platform on top of oak leaves with vines attached, used /sable assemble area, and dropped it about 15 blocks. The server hung and the watchdog crash showed the stack above.
  • With the same deferral applied as a server-side mixin on Sable 2.0.6, I did the same drop three times (oak, jungle and spruce leaves). The leaves broke, the server kept responding, and there was no watchdog crash and no Can't keep up. That mixin has the same logic as this patch, but I haven't run this exact jar. A full build needs the Rust natives (Docker), which I didn't set up.

@CLAassistant

CLAassistant commented Oct 15, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

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.

Server hang (watchdog) when a sub-level hits crops: re-entrant Rapier call from FragileBlockCallback

2 participants