Server deadlock during worldgen: antsStandOnWater mixin calls Level.getFluidState off-thread when Create contraption loads from structure NBT #18

Open jswxyz.tngl.sh
Open opened by jswxyz.tngl.sh

Disclaimer this was prepared and discovered with the help of Claude. Happy though to do any manual testing required or help in anyway I can.

Summary #

The dedicated server hangs, and the watchdog kills it after 60s, while chunks are being generated. The infinity$antsStandOnWater mixin on BlockStateBase.getCollisionShape calls Level.getFluidState(pos) on the real ServerLevel. When that runs on a worldgen worker thread, the chunk lookup is handed to the server thread and the worker blocks on join(). The server thread then blocks in getChunkBlocking waiting on generation that can't finish, so the two threads deadlock.

The trigger is a structure (Integrated API / Integrated Villages / IDAS) whose template contains a Create contraption entity. Loading that entity calls Contraption.invalidateColliders, which calls getCollisionShape for every block in the contraption. Those calls pass contraption-local positions, so the mixin also looks up fluid at unrelated world coordinates.

Environment #

  • Minecraft 1.21.1, NeoForge 21.1.228, dedicated server (Linux)
  • Infinite Dimensions: 2.7.1+1.21.1-neoforge also reproduced on latest
  • Create 6.0.11
  • Integrated API 1.7.4, Integrated Villages 1.3.2, IDAS 1.13.5
  • Sinytra Connector 2.0.0-beta.14, Lithium 0.15.3, ModernFix 5.27.11
  • Modpack: All of Create 1.21 v1.9
  • Happened during Chunky pregeneration with 0 players online

Relevant thread dump #

Worker thread (blocked waiting on the server thread):

"Worker-Main-3" WAITING on CompletableFuture$Signaller
  at CompletableFuture.join
  at ServerChunkCache.getChunkOffThread(ServerChunkCache.java:2185)
  at ServerChunkCache.getChunk(ServerChunkCache.java:2149)
  at Level.getChunkAt(Level.java:5820)
  at Level.getFluidState(Level.java:392)
  at BlockBehaviour$BlockStateBase.handler$efg000$infinity$antsStandOnWater(BlockBehaviour.java:3463)
  at BlockBehaviour$BlockStateBase.getCollisionShape(BlockBehaviour.java:658)
  at com.simibubi.create.content.contraptions.Contraption.invalidateColliders(Contraption.java:1444)
  at com.simibubi.create.content.contraptions.Contraption.fromNBT(Contraption.java:232)
  at com.simibubi.create.content.contraptions.AbstractContraptionEntity.readAdditional(AbstractContraptionEntity.java:651)
  at com.simibubi.create.content.contraptions.ControlledContraptionEntity.readAdditional(ControlledContraptionEntity.java:82)
  at Entity.load(Entity.java:1815)
  at EntityType.create(EntityType.java:1101)
  at StructureTemplate.addEntitiesToWorld(StructureTemplate.java:474)
  at StructureTemplate.placeInWorld(StructureTemplate.java:362)
  at com.craisinlord.integrated_api.world.structures.pieces.IASinglePoolElement.place(IASinglePoolElement.java:159)
  at PoolElementStructurePiece.place(PoolElementStructurePiece.java:98)
  at StructureStart.placeInChunk(StructureStart.java:92)
  at ChunkGenerator.applyBiomeDecoration(ChunkGenerator.java:356)
  at ChunkStatusTasks.generateFeatures(ChunkStatusTasks.java:154)
  ...

Server thread (blocked waiting on chunk generation):

"Server thread" TIMED_WAITING
  at BlockableEventLoop.managedBlock(BlockableEventLoop.java:133)
  at ServerChunkCache$MainThreadExecutor.managedBlock(ServerChunkCache.java:533)
  at ServerChunkCache.getChunkBlocking(ServerChunkCache.java:2258)
  at ServerChunkCache.getChunk(ServerChunkCache.java:2172)
  at ServerChunkCache.lithium$lambda$getChunkOffThread$0(ServerChunkCache.java:2185)
  at CompletableFuture$AsyncSupply.run
  at BlockableEventLoop.doRunTask(BlockableEventLoop.java:148)
  ...
  at MinecraftServer.runServer(MinecraftServer.java:712)

Full crash report:

Steps to reproduce #

  1. Install Infinite Dimensions, Create, and Integrated Villages (or another Integrated API structure pack that includes Create contraption entities in its templates).
  2. Start a dedicated server and pregenerate (e.g. /chunky start), or explore until one of those structures generates.
  3. The server freezes, and the watchdog crashes it after max-tick-time.

Expected #

Generating a structure that contains a contraption entity shouldn't block, and the server keeps running.

Possible fix #

getCollisionShape can be called with any BlockGetter, from any thread, with positions that don't belong to a loaded world chunk (contraptions, worldgen regions, Sable sub-levels, etc.). Some options:

  • Skip the fluid lookup when not on the server/main thread, or when the BlockGetter isn't the expected level type.
  • Use a non-blocking check such as level.getChunkSource().getChunkNow(x, z) or hasChunk before reading the fluid state, and fall back to the original shape if the chunk isn't loaded.
  • Only apply the behaviour when the collision context is an actual entity that needs it (check CollisionContext), not the empty context Create uses when rebuilding colliders.
Sign up or Sign in to add to the discussion.