Plugin
SmartSpawner 1.6.6
Environment
| Field |
Value |
| Server software |
CanvasMC 26.1.2 (Folia fork, build 752-85cc447) |
| Minecraft version |
1.21.x |
| Java |
OpenJDK 21 |
Bug Description
SpawnerStorageAction.onInventoryClose and SpawnerStackerHandler.onInventoryClose both call CraftInventory.getHolder(), which internally calls CraftBlock.getState() → BlockEntity.getOwner(). On Folia, getBlockState() asserts it runs on the region-owning tick thread. When InventoryCloseEvent fires from the global region scheduler thread (e.g. triggered by another plugin opening an inventory via FoliaGlobalRegionScheduler), both handlers throw IllegalStateException: Cannot read world asynchronously.
The event itself arrives from a valid Folia thread — the problem is that the global scheduler thread owns no region, so any world-read inside the handler fails.
Steps to Reproduce
- Run CanvasMC 26.1.2 (or any Folia build) with SmartSpawner 1.6.6.
- Have a player open a SmartSpawner storage GUI.
- A second plugin opens a new inventory for that player from
FoliaGlobalRegionScheduler (e.g. via runLater), which implicitly fires InventoryCloseEvent for the previous GUI.
- Both SmartSpawner listeners throw the error below.
Stack Traces
SpawnerStorageAction (SpawnerStorageAction.java:927):
[Folia Region Scheduler Thread #0/ERROR]: Could not pass event InventoryCloseEvent to SmartSpawner v1.6.6
java.lang.IllegalStateException: Thread failed main thread check: Cannot read world asynchronously,
context=[thread=Folia Region Scheduler Thread #0, region={null}], world=minecraft:overworld, block_pos=BlockPos{x=-1460, y=87, z=2461}
at ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(TickThread.java:62)
at org.bukkit.craftbukkit.block.CraftBlock.getBlockState(CraftBlock.java:84)
at org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(CraftBlockStates.java:197)
at org.bukkit.craftbukkit.block.CraftBlock.getState(CraftBlock.java:322)
at net.minecraft.world.level.block.entity.BlockEntity.getOwner(BlockEntity.java:412)
at org.bukkit.craftbukkit.inventory.CraftInventory.getHolder(CraftInventory.java:552)
at SmartSpawner-1.6.6.jar//github.nighter.smartspawner.spawner.gui.storage.SpawnerStorageAction.onInventoryClose(SpawnerStorageAction.java:927)
at io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler$GlobalScheduledTask.run(FoliaGlobalRegionScheduler.java:178)
SpawnerStackerHandler (SpawnerStackerHandler.java:209):
[Folia Region Scheduler Thread #0/ERROR]: Could not pass event InventoryCloseEvent to SmartSpawner v1.6.6
java.lang.IllegalStateException: Thread failed main thread check: Cannot read world asynchronously,
context=[thread=Folia Region Scheduler Thread #0, region={null}], world=minecraft:overworld, block_pos=BlockPos{x=-1460, y=87, z=2461}
at ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(TickThread.java:62)
at org.bukkit.craftbukkit.block.CraftBlock.getBlockState(CraftBlock.java:84)
at org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(CraftBlockStates.java:197)
at org.bukkit.craftbukkit.block.CraftBlock.getState(CraftBlock.java:322)
at net.minecraft.world.level.block.entity.BlockEntity.getOwner(BlockEntity.java:412)
at org.bukkit.craftbukkit.inventory.CraftInventory.getHolder(CraftInventory.java:552)
at SmartSpawner-1.6.6.jar//github.nighter.smartspawner.spawner.gui.stacker.SpawnerStackerHandler.onInventoryClose(SpawnerStackerHandler.java:209)
at io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler$GlobalScheduledTask.run(FoliaGlobalRegionScheduler.java:178)
Fix Direction
In both onInventoryClose handlers, before calling inventory.getHolder(), check that the caller is on the correct region thread. If not, either skip or reschedule via Bukkit.getRegionScheduler().run(plugin, location, task) using the spawner's block location. Alternatively, cache the spawner reference at GUI open time (guaranteed correct thread) rather than re-deriving it from the inventory holder on close.
Plugin
SmartSpawner 1.6.6
Environment
Bug Description
SpawnerStorageAction.onInventoryCloseandSpawnerStackerHandler.onInventoryCloseboth callCraftInventory.getHolder(), which internally callsCraftBlock.getState()→BlockEntity.getOwner(). On Folia,getBlockState()asserts it runs on the region-owning tick thread. WhenInventoryCloseEventfires from the global region scheduler thread (e.g. triggered by another plugin opening an inventory viaFoliaGlobalRegionScheduler), both handlers throwIllegalStateException: Cannot read world asynchronously.The event itself arrives from a valid Folia thread — the problem is that the global scheduler thread owns no region, so any world-read inside the handler fails.
Steps to Reproduce
FoliaGlobalRegionScheduler(e.g. viarunLater), which implicitly firesInventoryCloseEventfor the previous GUI.Stack Traces
SpawnerStorageAction (SpawnerStorageAction.java:927):
SpawnerStackerHandler (SpawnerStackerHandler.java:209):
Fix Direction
In both
onInventoryClosehandlers, before callinginventory.getHolder(), check that the caller is on the correct region thread. If not, either skip or reschedule viaBukkit.getRegionScheduler().run(plugin, location, task)using the spawner's block location. Alternatively, cache the spawner reference at GUI open time (guaranteed correct thread) rather than re-deriving it from the inventory holder on close.