Skip to content

[Folia] InventoryCloseEvent handlers call CraftInventory.getHolder() from global region thread — Cannot read world asynchronously #236

@DimaSergeew

Description

@DimaSergeew

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

  1. Run CanvasMC 26.1.2 (or any Folia build) with SmartSpawner 1.6.6.
  2. Have a player open a SmartSpawner storage GUI.
  3. A second plugin opens a new inventory for that player from FoliaGlobalRegionScheduler (e.g. via runLater), which implicitly fires InventoryCloseEvent for the previous GUI.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions