[SPIGOT-4578] Using Block.getChunk() inside BlockPhysicsEvent causes errors due to chunkgen Created: 13/Jan/19  Updated: 14/Aug/21  Resolved: 14/Aug/21

Status: Resolved
Project: Spigot
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Minor
Reporter: Irmo van den Berge Assignee: Unassigned
Resolution: Fixed Votes: 0
Labels: BlockPhysicsEvent, Chunk, Error, Generation, getChunkAt

Version: This server is running CraftBukkit version git-Spigot-518206a-c4a67ee (MC: 1.13.2) (Implementing API version 1.13.2-R0.1-SNAPSHOT)
Plugin: Traincarts (since resolved)
Guidelines Read: Yes

 Description   

A bug found by the users of my plugin that is quite interesting. During chunk generation, block physics events fire. Calling Block.getChunk() in that handler that caused a very confusing error:

[20:31:12] [Server thread/ERROR]: Could not pass event BlockPhysicsEvent to Train_Carts v1.13.2-v1
org.bukkit.event.EventException: null
    at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:309) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:62) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at org.bukkit.plugin.SimplePluginManager.fireEvent(SimplePluginManager.java:500) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:485) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at org.bukkit.craftbukkit.v1_13_R2.event.CraftEventFactory.callBlockPhysicsEvent(CraftEventFactory.java:1239) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.BlockPlant.updateState(BlockPlant.java:18) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.BlockTallPlant.updateState(BlockTallPlant.java:17) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.IBlockData.updateState(SourceFile:297) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.DefinedStructure.a(SourceFile:319) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.VoxelShapeDiscrete.a(SourceFile:305) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.VoxelShapeDiscrete.a(SourceFile:282) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.DefinedStructure.a(SourceFile:310) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.DefinedStructure.a(SourceFile:183) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.DefinedStructurePiece.a(SourceFile:59) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.WorldGenShipwreck$a.a(SourceFile:139) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.StructureStart.a(SourceFile:62) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.StructureGenerator.generate(StructureGenerator.java:48) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.WorldGenDecoratorEmpty.a(SourceFile:16) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.WorldGenDecoratorEmpty.a(SourceFile:13) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.WorldGenFeatureComposite.a(SourceFile:27) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.BiomeBase.a(SourceFile:504) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.ChunkGeneratorAbstract.addDecorations(ChunkGeneratorAbstract.java:97) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at org.bukkit.craftbukkit.v1_13_R2.generator.NormalChunkGenerator.addDecorations(NormalChunkGenerator.java:59) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.ChunkTaskDecorate.a(SourceFile:12) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.ChunkTask.a(SourceFile:35) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.ChunkStatus.a(SourceFile:95) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.ChunkTaskScheduler.a(ChunkTaskScheduler.java:75) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.ChunkTaskScheduler.a(ChunkTaskScheduler.java:1) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.Scheduler$a.a(SourceFile:147) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:602) ~[?:1.8.0_151]
    at java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:577) ~[?:1.8.0_151]
    at java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:442) ~[?:1.8.0_151]
    at com.google.common.util.concurrent.MoreExecutors$DirectExecutorService.execute(MoreExecutors.java:260) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at java.util.concurrent.CompletableFuture$UniCompletion.claim(CompletableFuture.java:529) ~[?:1.8.0_151]
    at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:599) ~[?:1.8.0_151]
    at java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:577) ~[?:1.8.0_151]
    at java.util.concurrent.CompletableFuture.uniApplyStage(CompletableFuture.java:617) ~[?:1.8.0_151]
    at java.util.concurrent.CompletableFuture.thenApplyAsync(CompletableFuture.java:1993) ~[?:1.8.0_151]
    at net.minecraft.server.v1_13_R2.Scheduler$a.a(SourceFile:147) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at java.util.Map.computeIfAbsent(Map.java:957) ~[?:1.8.0_151]
    at net.minecraft.server.v1_13_R2.Scheduler$a.a(SourceFile:137) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.Scheduler$a.a(SourceFile:143) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.ChunkStatus.a(SourceFile:105) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.ChunkStatus.a(SourceFile:22) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.Scheduler$a.a(SourceFile:143) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at java.util.Map.computeIfAbsent(Map.java:957) ~[?:1.8.0_151]
    at net.minecraft.server.v1_13_R2.Scheduler$a.a(SourceFile:137) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.Scheduler$a.a(SourceFile:143) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.ChunkStatus.a(SourceFile:105) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.ChunkStatus.a(SourceFile:22) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.Scheduler$a.a(SourceFile:143) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at java.util.Map.computeIfAbsent(Map.java:957) ~[?:1.8.0_151]
    at net.minecraft.server.v1_13_R2.Scheduler$a.a(SourceFile:137) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.Scheduler$a.a(SourceFile:143) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.ChunkStatus.a(SourceFile:105) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.ChunkStatus.a(SourceFile:22) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.Scheduler$a.a(SourceFile:143) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at java.util.Map.computeIfAbsent(Map.java:957) ~[?:1.8.0_151]
    at net.minecraft.server.v1_13_R2.Scheduler$a.a(SourceFile:137) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.Scheduler$a.a(SourceFile:115) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.Scheduler.a(SourceFile:61) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590) [?:1.8.0_151]
    at com.google.common.util.concurrent.MoreExecutors$DirectExecutorService.execute(MoreExecutors.java:260) [my.jar:git-Spigot-f56e2e7-4385562]
    at java.util.concurrent.CompletableFuture.asyncSupplyStage(CompletableFuture.java:1604) [?:1.8.0_151]
    at java.util.concurrent.CompletableFuture.supplyAsync(CompletableFuture.java:1830) [?:1.8.0_151]
    at net.minecraft.server.v1_13_R2.Scheduler.a(SourceFile:62) [my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.SchedulerBatch.a(SourceFile:39) [my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.ChunkProviderServer.getChunkAt(ChunkProviderServer.java:109) [my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.PlayerChunk.a(SourceFile:87) [my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.PlayerChunkMap.flush(PlayerChunkMap.java:135) [my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.WorldServer.doTick(WorldServer.java:291) [my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.MinecraftServer.b(MinecraftServer.java:952) [my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.DedicatedServer.b(DedicatedServer.java:417) [my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.MinecraftServer.a(MinecraftServer.java:831) [my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.MinecraftServer.run(MinecraftServer.java:729) [my.jar:git-Spigot-f56e2e7-4385562]
    at java.lang.Thread.run(Thread.java:748) [?:1.8.0_151]
Caused by: net.minecraft.server.v1_13_R2.ReportedException: Exception generating new chunk
    at net.minecraft.server.v1_13_R2.ChunkProviderServer.a(ChunkProviderServer.java:172) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.ChunkProviderServer.getChunkAt(ChunkProviderServer.java:114) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at org.bukkit.craftbukkit.v1_13_R2.CraftWorld.getChunkAt(CraftWorld.java:144) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at org.bukkit.craftbukkit.v1_13_R2.CraftWorld.getChunkAt(CraftWorld.java:148) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at org.bukkit.craftbukkit.v1_13_R2.block.CraftBlock.getChunk(CraftBlock.java:104) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at com.bergerkiller.bukkit.tc.TCListener.onBlockPhysics(TCListener.java:858) ~[TrainCarts-1.13.2-v1.jar:?]
    at sun.reflect.GeneratedMethodAccessor101.invoke(Unknown Source) ~[?:?]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_151]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_151]
    at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:305) ~[my.jar:git-Spigot-f56e2e7-4385562]
    ... 75 more
Caused by: java.lang.RuntimeException: Batch already started.
    at net.minecraft.server.v1_13_R2.SchedulerBatch.b(SourceFile:27) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.ChunkProviderServer.getChunkAt(ChunkProviderServer.java:108) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at org.bukkit.craftbukkit.v1_13_R2.CraftWorld.getChunkAt(CraftWorld.java:144) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at org.bukkit.craftbukkit.v1_13_R2.CraftWorld.getChunkAt(CraftWorld.java:148) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at org.bukkit.craftbukkit.v1_13_R2.block.CraftBlock.getChunk(CraftBlock.java:104) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at com.bergerkiller.bukkit.tc.TCListener.onBlockPhysics(TCListener.java:858) ~[TrainCarts-1.13.2-v1.jar:?]
    at sun.reflect.GeneratedMethodAccessor101.invoke(Unknown Source) ~[?:?]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_151]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_151]
    at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:305) ~[my.jar:git-Spigot-f56e2e7-4385562]
    ... 75 more

Relevant part:

    at net.minecraft.server.v1_13_R2.SchedulerBatch.b(SourceFile:27) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at net.minecraft.server.v1_13_R2.ChunkProviderServer.getChunkAt(ChunkProviderServer.java:108) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at org.bukkit.craftbukkit.v1_13_R2.CraftWorld.getChunkAt(CraftWorld.java:144) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at org.bukkit.craftbukkit.v1_13_R2.CraftWorld.getChunkAt(CraftWorld.java:148) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at org.bukkit.craftbukkit.v1_13_R2.block.CraftBlock.getChunk(CraftBlock.java:104) ~[my.jar:git-Spigot-f56e2e7-4385562]
    at com.bergerkiller.bukkit.tc.TCListener.onBlockPhysics(TCListener.java:858) ~[TrainCarts-1.13.2-v1.jar:?]

Further digging into it, I found out that this has to do with the fact the chunk is being populated (trees are placed, etc.) and that during this, the chunk can not be obtained using getChunk(), as it is not yet available.

I managed to fix the error on my end by not calling getChunk() inside the block physics event handler, as there was another way to do what it was using getChunk() for.

One of my users did report that this started happening once players wander into the newly introduced ocean biomes, but I can not confirm that. My guess then would be that block physics events are (incorrectly?) fired during the populating of certain ocean biome structures/plants.

For me it's fixed, but I find it quite likely that there will be other plugins that call getChunk() inside that event without thinking about it. Since this bug is not documented, I think it's worth posting a report about so someone more knowledgable in the chunk generation logic can have a look at it.



 Comments   
Comment by md_5 [ 14/Aug/21 ]

Fixed with new generator API

Comment by Irmo van den Berge [ 15/Jan/19 ]

heh, I had no idea entity spawns were part of chunk gen. I suppose it makes sense, since things like paintings are entities too. I always assumed this occurred after the chunks were generated, not during.

Prototype chunks or 'chunks under construction' is a bad idea the more I think about it. Especially because chunk generation occurs on another thread.

I feel it's neater to not expose plugins to events that occur for a chunk that doesn't exist yet. At the same time, plugins want to know what new blocks/entities are introduced when a chunk is generated. So maybe some compromise has to be made. Going through all the entities added after the chunk is generated and firing events then is an option.

I do wonder why block physics events occur during the chunk population stage. That sounds horribly inefficient for large structures...

 

Comment by md_5 [ 15/Jan/19 ]

>I don't see why in this case. This is chunk generation, from my viewpoint, it's as if the blocks were arranged in this way since forever ago. Like they are loaded in. Physics events have no place when the initial values of a chunk are computed. If a plugin wishes to handle certain changes that happen in the pre-generated contents of a chunk, that plugin should be using the chunk populate event for it.

 

What about entity spawns though?

https://hub.spigotmc.org/jira/browse/SPIGOT-4534

 

It's getting hellish to maintain these events during world gen though, especially with 1.14 coming up

Comment by Irmo van den Berge [ 14/Jan/19 ]

I'm fairly sure they occurred on 1.12, they just didn't cause any problems at the time because the Block stored the Chunk inside the event. The many changes to the 'batch scheduler' are since 1.13, so the whole problem of not being able to retrieve the chunk being generated appears to not exist on 1.12 either. I can cook up a quick test on 1.12.2 later today if you like

Comment by md_5 [ 14/Jan/19 ]

>I don't see why in this case. This is chunk generation, from my viewpoint, it's as if the blocks were arranged in this way since forever ago. Like they are loaded in. Physics events have no place when the initial values of a chunk are computed. If a plugin wishes to handle certain changes that happen in the pre-generated contents of a chunk, that plugin should be using the chunk populate event for it.

 

Hmm this is a good point. The reason I said this is I'm pretty sure 1.12 and older did fire these physics events during generation (maybe you could confirm this). Removing event calls (especially the damn physics event) almost invariably makes someone angry as there is no easy way to get them back (whereas it is possible to ignore calls you don't want). Personally I do think your reasoning is correct though.

Comment by Irmo van den Berge [ 14/Jan/19 ]

> Suppressing is bound to make someone angry

I don't see why in this case. This is chunk generation, from my viewpoint, it's as if the blocks were arranged in this way since forever ago. Like they are loaded in. Physics events have no place when the initial values of a chunk are computed. If a plugin wishes to handle certain changes that happen in the pre-generated contents of a chunk, that plugin should be using the chunk populate event for it.

In any case, it's an open issue, time will tell how important a working getChunk() inside these events are.

Comment by md_5 [ 13/Jan/19 ]

>One possible solution I see is to return a 'proto chunk' placeholder for chunks that are being populated/generated at the time. 

I'd rather just throw an exception than return garbage.

 

>Another option is to simply suppress bukkit events during chunk population, or queue them and fire them once the population is completed.

Suppressing is bound to make someone angry, and queuing them isn't really feasible if they've already happened.

Comment by Irmo van den Berge [ 13/Jan/19 ]
As you point out getting the chunk isn’t always necessary.

Correct. The reason I was using getChunk() there was mostly legacy. It used to be that the Block stores the Chunk itself, but since 1.13 this is no longer the case. Some parts of my code still made this assumption, performing a getChunk() and doing stuff with that 'as it is more efficient that way'. In this instance, it was doing getChunk() to then obtain IBlockData for a Block held inside. There is now some form of method I can use in CraftBlock itself that it wasn't using, which uses world.getType(blockposition), where there is proper handling for blocks that are being populated.

One possible solution I see is to return a 'proto chunk' placeholder for chunks that are being populated/generated at the time. Another option is to simply suppress bukkit events during chunk population, or queue them and fire them once the population is completed.

Comment by md_5 [ 13/Jan/19 ]

I’m not sure what the correct fix is for this, however the same bug also affects a couple of events that can be fired in world generation.

 

Ideally the api should be able to handle world generation virtual chunks, but the problem is that Chunk api exposes more than is possible here.

 

Probably the closest that we can get right now is just throwing a clean exception. As you point out getting the chunk isn’t always necessary.

Generated at Tue Apr 15 09:42:10 UTC 2025 using Jira 10.3.3#10030003-sha1:d220e3fefc8dfc6d47f522d3b9a20c1455e12b7b.