Bug Open

Bone items and data trackers are accessed concurrently during model ticking

#10143 opened by chikage_ 2 days ago

chikage_reported this bug
  • Version: ModelEngine R4.2.0, Leaf 1.21.4-525 with async entity tracking and async pathfinding enabled
  • Period covered: Oct 23 to Nov 4, 2026
  • Occurrences: 143

Checked against ModelEngine-R4.2.0.jar. The line numbers in the stack traces match the bytecode of this jar.

Failure Occurrences Location
NullPointerException while editing an item 64 NMSHandler_v1_21_R3.colorStack:147
EncoderException: Failed to encode packet 'clientbound/minecraft:set_entity_data' 45 Packet encoding
NoSuchElementException while hashing an item 15 BoneItems.rebuildMaps:91
ClassCastException: ResourceLocation to CustomModelData 12 colorStack:147
ConcurrentModificationException 6 MapDataTracker.set:22, from ModelBoneImpl.tick:256
ArrayIndexOutOfBoundsException 1 colorStack:147

Logs

Corrupted item read in colorStack

[11:14:43] [ForkJoinPool-6-worker-122/WARN]: java.lang.ClassCastException: class net.minecraft.resources.ResourceLocation cannot be cast to class net.minecraft.world.item.component.CustomModelData [...]
[11:14:43] [ForkJoinPool-6-worker-122/WARN]: 	at org.bukkit.craftbukkit.inventory.CraftMetaItem.updateFromPatch(CraftMetaItem.java:419)
[11:14:43] [ForkJoinPool-6-worker-122/WARN]: 	at org.bukkit.inventory.ItemStack.editMeta(ItemStack.java:618)
[11:14:43] [ForkJoinPool-6-worker-122/WARN]: 	at ModelEngine.jar//com.ticxo.modelengine.v1_21_R3.NMSHandler_v1_21_R3.colorStack(NMSHandler_v1_21_R3.java:147)

Packet encoding fails with the same cast error

[13:16:31] [Netty Epoll Server IO #3/WARN]: io.netty.handler.codec.EncoderException: Failed to encode packet 'clientbound/minecraft:set_entity_data'
[13:16:31] [Netty Epoll Server IO #3/WARN]: Caused by: java.lang.RuntimeException: Error encoding component minecraft:custom_model_data
[13:16:31] [Netty Epoll Server IO #3/WARN]: Caused by: java.lang.ClassCastException: class net.minecraft.resources.ResourceLocation cannot be cast to class net.minecraft.world.item.component.CustomModelData [...]

Item hashing and step flags

[15:24:46] [ForkJoinPool-6-worker-5/WARN]: java.util.NoSuchElementException
[15:24:46] [ForkJoinPool-6-worker-5/WARN]: 	at org.bukkit.inventory.ItemStack.hashCode(ItemStack.java:374)
[15:24:46] [ForkJoinPool-6-worker-5/WARN]: 	at ModelEngine.jar//com.ticxo.modelengine.api.model.bone.BoneItems.rebuildMaps(BoneItems.java:91)

[17:45:34] [ForkJoinPool-6-worker-47/WARN]: java.util.ConcurrentModificationException
[17:45:34] [ForkJoinPool-6-worker-47/WARN]: 	at ModelEngine.jar//com.ticxo.modelengine.api.utils.data.tracker.MapDataTracker.set(MapDataTracker.java:22)

Long lines are shortened with [...], and frames between the lines shown are omitted.

Problem

Model ticks run in parallel. ModelUpdaters.tick submits each modeled entity to a work-stealing pool. ModelBoneImpl.tick then runs on those workers.

Tinting edits the live ItemStack in place. tryTintModel calls BoneItems.forEachMutating, and colorStack runs editMeta directly on the stack in bakedStacks, with no lock. These stacks are not thread-safe. The observed cast, iterator and array errors are typical signs of a component map used by two threads at once.

The same stacks are reachable outside the tint path. For example, setEnchanted edits them through BoneItems.forEach, and update, clear and add replace them. rebuildMaps then hashes the stacks and uses them as keys in toHash, so a concurrent edit also breaks the hash step.

The second thread is not identified. The default createStack path builds a new stack on each call, so it does not share a stack between bones. The code confirms the unsafe edit, but not which caller overlaps with it on these servers. The async entity tracking in Leaf may also play a part.

stepFlags is a plain HashMap. At ModelBoneImpl.tick:256, MapDataTracker.set copies stepFlags with putAll. The exception is raised while reading that source map, so stepFlags changed during the copy. markStep writes to it with no synchronization.

The effects reach the client. DisplayParser builds the metadata packet from a copy of the bone stack, and Netty fails to encode it. I have not checked whether this also disconnects players.

Solution

  1. Stop editing live stacks. Build a tinted copy, then publish it to BoneItems as a new baked array.
  2. Route setEnchanted, update, clear and add through the same single-writer update, or make them queue work for the owning tick.
  3. Avoid using a mutable ItemStack as a map key in toHash.
  4. Give stepFlags one owner, or use a structure that is safe to read during writes.
Sign in to join the discussion.