Repository navigation
LevelAccelerator.grabChunkFast does synchronous chunk generation on the server thread resulting in crash #1272
Description
Summary
SableCommonEvents.handleBlockChange (the LevelChunk.setBlockState wrap) bakes a collision shape for every changed block, and during that bake it reads neighbor block states via LevelAccelerator.getBlockState → grabChunkFast → Level.getChunk. When a neighbor falls in an ungenerated/unloaded chunk, grabChunkFast performs a synchronous getChunk(create=true) on the server thread, blocking the main thread on full chunk generation. On a server with heavy/async worldgen this takes minutes, the tick loop stalls, and the watchdog force-kills the server.
This is reproducible from any mod that calls setBlock near a chunk boundary. I hit it twice within 15 minutes from two different callers (Lootr replacing loot block-entities, and a Storage Drawers attack raytrace), both bottoming out in the same LevelAccelerator sync load.
Environment
- Sable 2.0.3 (
sable-neoforge-1.21.1-2.0.3,sable_rapier 1.21.1-2.0.3) - Minecraft 1.21.1 / NeoForge 21.1.234
- C2ME 0.4.0-alpha.0.104 (async worldgen — makes the sync stall very visible)
- Dedicated server
Stack trace (primary: setBlockState neighbor read)
Watchdog reports Sync load chunk [306, 75] to status minecraft:full (create=true) (1.927 min elapsed) on the Server thread:
ServerChunkCache.managedBlock (blocked, waiting for chunk gen)
ServerChunkCache.getChunk
Level.getChunk
dev.ryanhcode.sable.util.LevelAccelerator.grabChunkFast(LevelAccelerator.java:132)
dev.ryanhcode.sable.util.LevelAccelerator.getChunk(LevelAccelerator.java:109)
dev.ryanhcode.sable.util.LevelAccelerator.getBlockState(LevelAccelerator.java:66)
dev.ryanhcode.sable.physics.chunk.VoxelNeighborhoodState.getState(VoxelNeighborhoodState.java:100)
dev.ryanhcode.sable.physics.impl.rapier.RapierPhysicsPipeline.handleBlockChange(RapierPhysicsPipeline.java:394)
dev.ryanhcode.sable.sublevel.system.SubLevelPhysicsSystem.handleBlockChange(SubLevelPhysicsSystem.java:472)
dev.ryanhcode.sable.SableCommonEvents.handleBlockChange(SableCommonEvents.java:91)
net.minecraft.world.level.chunk.LevelChunk.wrapOperation$ede000$sable$setBlockState(LevelChunk.java)
net.minecraft.world.level.chunk.LevelChunk.setBlockState
net.minecraft.world.level.Level.setBlock
-- caller: Lootr BlockEntityTicker.replaceEntitiesInChunk (any setBlock near a chunk border triggers it)
Second path (raytrace), same root
A BlockGetter.clip Sable rewrites (...sable$lambda$originalClip$0 → traverseBlocks → Level.getBlockState) also reaches getChunk and sync-loads. Triggered here by a Storage Drawers eye-raytrace on block attack:
ServerChunkCache.managedBlock (blocked)
... Level.getBlockState
BlockGetter.originalClip / traverseBlocks (sable$lambda$originalClip$0)
StorageDrawers WorldUtils.rayTraceEyes → FaceSlotBlock.attack → handlePlayerAction
Related
This is the same handleBlockChange collision-bake hook that NPEs when a neighbor is null mid-placement (e.g. Immersive Engineering PostBlock.getShape). Both stem from the bake reading neighbor/world state synchronously during setBlockState.
Wasn't able to reproduce the crash in a minimal environment) (since that's probably due to it overloading the server with a bunch of mods) but using this contraption and removing something from the drawer while looking above the horizon seems to reproduce this.
Crash report: https://mclo.gs/KgOsjvd
Activity
[-]LevelAccelerator.grabChunkFast does synchronous chunk generation on the server thread during collision baking → tick-loop hang / watchdog crash[/-][+]LevelAccelerator.grabChunkFast does synchronous chunk generation on the server thread resulting in crash[/+]Grygon commentedon Jun 25, 2026
Looks to be related to #761 - hit this both for IE and Storage Drawers
Grygon commentedon Jun 25, 2026
May just need to be fixed in StorageDrawers' raycasting - related conversation in jaquadro/StorageDrawers#1297
I'll leave this open in case there's appetite to make Sable's raycasting better here, but feel free to close if it just needs to be handled downstream
ShinjiraCode commentedon Sep 19, 2026
Still happening on Sable 2.0.5 (NeoForge 21.1.251, C2ME 0.4.0-alpha.0.120).
In our case the trigger is fluids. A player flies into new terrain, fluid spreads or a Creating Space fluid interaction sets a block near the edge of the loaded area, and Sable's
handleBlockChangereads the neighbourhood.LevelAccelerator.grabChunkFastthen callsLevel.getChunk, which loads the neighbouring chunk synchronously and parks the server thread (same chain as #1149).Stack from spark (async profiler):
About 1.2-1.3 s of server-thread blocking across ~2 minutes of flying.
Profiles:
A non-loading lookup in this path (e.g.
getChunkNow, skipping neighbours whose chunk isn't loaded to FULL) would avoid blocking the server thread.ColinVaughn commentedon Oct 23, 2026
i have a pr for this but they don't pull shit