Skip to content

LevelAccelerator.grabChunkFast does synchronous chunk generation on the server thread resulting in crash #1272

Open

Description

@Grygon

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.

Image

Crash report: https://mclo.gs/KgOsjvd

Activity

  1. changed the title [-]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[/+] on Jun 25, 2026
  2. Grygon commented on Jun 25, 2026

    @Grygon
    Author

    Looks to be related to #761 - hit this both for IE and Storage Drawers

  3. Grygon commented on Jun 25, 2026

    @Grygon
    Author

    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

  4. ShinjiraCode commented on Sep 19, 2026

    @ShinjiraCode

    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 handleBlockChange reads the neighbourhood. LevelAccelerator.grabChunkFast then calls Level.getChunk, which loads the neighbouring chunk synchronously and parks the server thread (same chain as #1149).

    Stack from spark (async profiler):

    FlowingFluid.spread > FlowingFluid.spreadTo > Level.setBlock > ... > LiquidBlock.neighborChanged
    > FluidInteractionRegistry.canInteract [neoforge] > FluidInit.lambda$registerFluidInteractions$6 [creatingspace]
    > Level.setBlockAndUpdate > LevelChunk.setBlockState > LevelChunk.wrapOperation$...$sable$setBlockState
    > SableCommonEvents.handleBlockChange > SubLevelPhysicsSystem.handleBlockChange > RapierPhysicsPipeline.handleBlockChange
    > VoxelNeighborhoodState.getState > LevelAccelerator.getBlockState > LevelAccelerator.getChunk > LevelAccelerator.grabChunkFast
    > Level.getChunk > ServerChunkCache.getChunk > (C2ME instrumentAwaitChunk) > ServerChunkCache$MainThreadExecutor.managedBlock
    > LockSupport.parkNanos
    

    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.

  5. ColinVaughn commented on Oct 23, 2026

    @ColinVaughn

    i have a pr for this but they don't pull shit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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