Skip to content

Recursive update crash in RapierVoxelColliderBakery when Create rails place fake tracks (1.2.2) #820

@joanmarcel

Description

@joanmarcel

Crash: IllegalStateException: Recursive update in RapierVoxelColliderBakery.getPhysicsDataForBlock when Create rails place fake tracks

Versions

  • Sable: sable-neoforge-1.21.1-1.2.2
  • Minecraft: 1.21.1
  • Loader: NeoForge
  • Create: 6.0.10
  • Steam 'n Rails (railways): 0.2.0-beta.2+neoforge
  • Also present in stack: aeronautics and simulated (both inject mixins into SableCommonEvents.handleBlockChange)

What happens

The integrated server crashes during a tick while a Create track block entity is being initialised. Create's TrackBlockEntity.manageFakeTracksAlong calls Level.setBlock to place its connecting "fake track" blocks. That setBlock triggers Sable's block-change listener, which calls RapierPhysicsPipeline.handleBlockChangeRapierVoxelColliderBakery.getPhysicsDataForBlock. Inside that method, ConcurrentHashMap.computeIfAbsent (via net.minecraft.Util$8.apply) is called recursively for the same key, which Java explicitly forbids:

java.lang.IllegalStateException: Recursive update
	at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1742)
	at TRANSFORMER/minecraft@1.21.1/net.minecraft.Util$8.apply(Util.java:795)
	at TRANSFORMER/sable@1.2.2/dev.ryanhcode.sable.physics.impl.rapier.collider.RapierVoxelColliderBakery.getPhysicsDataForBlock(RapierVoxelColliderBakery.java:98)
	at TRANSFORMER/sable@1.2.2/dev.ryanhcode.sable.physics.impl.rapier.RapierPhysicsPipeline.handleBlockChange(RapierPhysicsPipeline.java:513)
	at TRANSFORMER/sable@1.2.2/dev.ryanhcode.sable.sublevel.system.SubLevelPhysicsSystem.handleBlockChange(SubLevelPhysicsSystem.java:451)
	at TRANSFORMER/sable@1.2.2/dev.ryanhcode.sable.SableCommonEvents.handleBlockChange(SableCommonEvents.java:91)
	at TRANSFORMER/minecraft@1.21.1/net.minecraft.world.level.chunk.LevelChunk.wrapOperation$iip000$sable$setBlockState(LevelChunk.java:8302)
	at TRANSFORMER/minecraft@1.21.1/net.minecraft.world.level.chunk.LevelChunk.setBlockState(LevelChunk.java:249)
	at TRANSFORMER/minecraft@1.21.1/net.minecraft.world.level.Level.setBlock(Level.java:236)
	at TRANSFORMER/create@6.0.10/com.simibubi.create.content.trains.track.TrackBlockEntity.manageFakeTracksAlong(TrackBlockEntity.java:396)
	at TRANSFORMER/create@6.0.10/com.simibubi.create.content.trains.track.TrackBlockEntity.lazyTick(TrackBlockEntity.java:89)
	at TRANSFORMER/create@6.0.10/com.simibubi.create.foundation.blockEntity.SmartBlockEntity.initialize(SmartBlockEntity.java:71)
	at TRANSFORMER/create@6.0.10/com.simibubi.create.content.trains.track.TrackBlockEntity.initialize(TrackBlockEntity.java:74)
	at TRANSFORMER/create@6.0.10/com.simibubi.create.foundation.blockEntity.SmartBlockEntity.tick(SmartBlockEntity.java:76)
	at TRANSFORMER/create@6.0.10/com.simibubi.create.content.trains.track.TrackBlockEntity.tick(TrackBlockEntity.java:81)

Looks like the collider cache (a ConcurrentHashMap accessed via Util$8 / memoize) is being entered recursively for the same block key while baking colliders for a chain of related blocks during a single block-change event.

Block entity at crash

  • Block{create:track}[shape=xo,turn=true,waterlogged=false]
  • World pos (10139, 65, -11154)

Reproduction

Single-player integrated server, world has Create rails. Crash occurs deterministically on tick when the chunk containing the curved track is loaded (the lazyTick of the track triggers manageFakeTracksAlong).

Possible root cause

computeIfAbsent on ConcurrentHashMap cannot be re-entered for the same key. If getPhysicsDataForBlock ends up resolving another block that itself resolves back into the same cached entry (or the same block needs nested collider data during baking), the recursive call into Util.memoize/computeIfAbsent throws.

Possible fixes:

  • Pre-populate the cache outside of computeIfAbsent (compute first, then putIfAbsent).
  • Detect re-entrance and bail to a non-cached compute path.
  • Defer collider baking from inside the block-change listener to a later tick step instead of synchronously while the world is mid-setBlock.

Workaround

Downgrading to Sable 1.1.3 (testing this now). Removing Sable also avoids the crash.

Related

Same Recursive update symptom and same code path (RapierVoxelColliderBakery / Util.memoize computeIfAbsent) as #678 (Twilight Forest ForceFieldBlock) and #538 (Lootr DecoratedPot), but triggered here by Create's rail system instead. Filing separately because the trigger mod and call site are different and #678 is closed.

Attachments

Full crash report will be attached as a comment after issue creation (CLI doesn't support file attachments at create time).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions