Add Folia support - #6635
Open
lewisgibson wants to merge 13 commits into
Open
Add Folia support#6635lewisgibson wants to merge 13 commits into
lewisgibson wants to merge 13 commits into
Conversation
PaperLib 1.0.6 reads the server version with a regex that only accepts a single digit major version, so on 26.x it sees version 0 and falls back to its synchronous handlers. Every PaperLib.teleportAsync, getChunkAtAsync and getBedSpawnLocationAsync call in EssentialsX then ran a blocking teleport or chunk load instead of the asynchronous Paper API. When PaperLib cannot read the version of a server that is newer than 1.13, install an environment that selects the handlers PaperLib would have chosen for a modern Paper. Co-authored-by: Raw2d <255907405+Raw2d@users.noreply.github.com>
Folia does not support the Bukkit scheduler and splits the world between regions, each of which ticks on its own thread. A task must run on the thread that owns the entity or location it touches, and state that no region owns runs on a global thread. TaskSchedulerProvider schedules global, entity, location and asynchronous tasks and answers whether the current thread owns an entity or location. BukkitTaskSchedulerProvider queues everything onto the main thread exactly as the Bukkit scheduler calls it replaces did, and is used on every server but Folia. FoliaTaskSchedulerProvider uses the region, entity, global and async schedulers, and is selected only where Folia's RegionizedServer class exists. It lives in PaperProvider so that Folia's API is not loaded anywhere else. The Bukkit scheduler methods on IEssentials are deprecated, since they cannot work on Folia. Call sites are moved over in the following commits. Co-authored-by: Raw2d <255907405+Raw2d@users.noreply.github.com>
Replace the Bukkit scheduler calls in the plugin core with TaskSchedulerProvider, choosing for each task the thread that owns what it touches: - Tasks that act on a player (join flow, MOTD, inventory refreshes, power tools, flight, AFK activity from chat) run on that player's entity thread. - Timers and one-off tasks that touch no player (backups, command map updates, the economy layer hook) run on the global thread. - Blocking work (IO, user disposal, update checks) runs asynchronously. The once a second EssentialsTimer runs on the global thread and hands each player's AFK, mute and jail checks to that player's own thread, and commands that the console has to dispatch (kit commands, the AFK timeout commands) are dispatched from the global thread. On Bukkit, Spigot and Paper all of this runs inline on the main thread as before. The history behind the TPS average is read by commands on other threads on Folia, so it is now accessed under a lock. Co-authored-by: Raw2d <255907405+Raw2d@users.noreply.github.com>
On Folia the player being teleported, the chunk they are going to and the player who ran the command can all belong to different regions, and none of them may be touched from the wrong thread. - A passenger is ejected on the teleportee's own thread, and the teleport carries on from there instead of blocking the calling thread until the eject has run. If the player leaves before it runs the teleport completes as false. - Safety checks and the teleport itself run on the thread that owns the destination chunk once it has loaded. Without safety checks the teleport is requested through teleportAsync, as Folia has no synchronous teleport. - The search for a safe location treats blocks owned by another region as unsafe, so it carries on in the region it is allowed to read. - The warm-up timer ticks asynchronously and hands the teleport to the teleportee's thread. If the teleportee has left by then the timer stops itself. - Finding a random location reads the surface on the thread that owns the chunk it picked, and the default centre of a random teleport location is worked out by the thread that owns it. None of this changes where tasks run on Bukkit, Spigot and Paper, where every thread check passes on the main thread. Co-authored-by: Raw2d <255907405+Raw2d@users.noreply.github.com>
/tpa and /tpahere add to the target's request queue from the requester's thread, while the target reads, expires and removes requests from their own thread and tab completion copies the keys. The queue is a plain LinkedHashMap, so on Folia those accesses can corrupt it or throw a ConcurrentModificationException. Every method that touches the queue now holds the user's monitor, and the pending keys are returned as a copy. Co-authored-by: Raw2d <255907405+Raw2d@users.noreply.github.com>
The sender of a message writes the reply recipient and message time of the user they are messaging, which can be on another thread on Folia. Make both fields volatile so /r on the other side sees them. Co-authored-by: Raw2d <255907405+Raw2d@users.noreply.github.com>
…target them Commands such as /heal, /feed, /kill, /gamemode, /lightning, /vanish, /fly and /god apply a change to every player matched by their argument, from the thread of whoever ran the command. On Folia that is not the thread that owns the player being changed, and for example killing a player drops their items from the wrong thread. When the current thread does not own a matched player, EssentialsLoopCommand and EssentialsToggleCommand now make the change on that player's own thread, and an error it raises is shown to the sender from there. On Bukkit, Spigot and Paper every player is owned by the current thread, so the change is made inline and errors propagate as before.
The world clock and the weather belong to the global thread on Folia, and entities and blocks to the thread of the region they are in. Commands that run from a player's region cannot change either directly. - /time, /weather, /thunder and the time and weather signs set the clock and the weather from the global thread. /time re-applies relative player times from each player's own thread. - /remove visits the loaded chunks from the thread that owns each of them and reports the total once the last chunk is done. - /nuke and /antioch spawn their TNT, and /spawnmob spawns its mobs, from the thread that owns where they land. - /gc reads the tile entity count of a world from the world, since a region cannot read the chunks of other regions on Folia. On Bukkit, Spigot and Paper all of this still runs inline on the main thread. Co-authored-by: Raw2d <255907405+Raw2d@users.noreply.github.com>
- /beezooka and /kittycannon remove their projectile from the thread of the entity. - /skull and /sudo hand their result to the thread of the player it applies to. - /balancetop from a command block runs on the global thread. - /seen runs its lookup asynchronously. - /nyan, which used a BukkitRunnable, plays its tune from a global timer and each note from the thread of the player hearing it. Co-authored-by: Raw2d <255907405+Raw2d@users.noreply.github.com>
Teleporting a new player to the newbie spawn, sending a returning player to the spawn of their group, and announcing and kitting a new player all run on the thread of that player. Co-authored-by: Raw2d <255907405+Raw2d@users.noreply.github.com>
…heduler - Discord messages, commands run from Discord and slash commands run on the global thread, and chat relayed to Discord on the thread of the player who sent it. - The console relay and the command response buffer tick asynchronously. - Account link status events are called from the global thread, and a player kicked for not being linked is kicked from their own thread. - Role sync ticks asynchronously. Co-authored-by: Raw2d <255907405+Raw2d@users.noreply.github.com>
Presence updates, relayed messages and commands received over XMPP run on the global thread. Co-authored-by: Raw2d <255907405+Raw2d@users.noreply.github.com>
Folia only loads plugins that set folia-supported in their plugin.yml. Set it on every EssentialsX module, now that all of their scheduled work and thread-bound access goes through the task scheduler, and mention Folia as a supported server in the README. Co-authored-by: Raw2d <255907405+Raw2d@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Information
This PR closes #6633.
Depends on #6634, whose commit is the first one here. The diff shows it until that one is merged.
Details
Proposed feature:
Make EssentialsX run on Folia, and change nothing on Bukkit, Spigot and Paper.
Folia refuses to load a plugin that does not set
folia-supported, and it does not support the Bukkit scheduler. It also splits the world into regions that tick on their own threads, so an entity, block or chunk can only be touched from the thread that owns it, and the world clock, the weather and the command map belong to a global thread.How it works:
TaskSchedulerProvideris a new provider that schedules global, entity, location and asynchronous tasks and says whether the current thread owns an entity or location.BukkitTaskSchedulerProvider(BaseProviders) queues everything onto the main thread, exactly as the Bukkit scheduler calls it replaces did, and is used on every server except Folia.FoliaTaskSchedulerProvider(PaperProvider) uses Folia's region, entity, global and async schedulers and is only selected whereRegionizedServerexists. Folia's classes are never loaded elsewhere and the API floor does not change.executeGlobal,executeEntityandexecuteLocationrun the task inline when the current thread already owns what it touches and otherwise hand it to the thread that does. On Bukkit that is always inline, so code written with them behaves as before.IEssentialsstay, are deprecated, and are unsupported on Folia./time,/weather,/thunder,/remove,/nuke,/gc, the loop commands that change other players (/heal,/kill,/gamemode,/lightningand so on), kit commands, and the console commands EssentialsX dispatches run on the thread that owns what they change.Each commit covers one area, and the details are in the commit messages.
Not covered: EssentialsXDiscord, DiscordLink and XMPP need accounts, so only their scheduling was changed and they were only boot tested. An error raised inside a task handed to another thread is logged by the server instead of being shown to the command sender.
Environments tested:
OS: Ubuntu 24.04 (WSL2)
Java version: Temurin 25.0.4.1
CraftBukkit/Spigot/Paper 1.12.2CraftBukkit 1.8.8Demonstration:
./gradlew buildpasses, including checkstyle and 52 unit tests (35 on2.x). The new ones cover both scheduler providers (inline on Bukkit, the right Folia scheduler andretiredhandling on Folia), the region guard in the safe location search, the teleport request queue under concurrent access (it fails without the change), and the loop and toggle commands changing a player on their own thread.I also ran EssentialsX from a bot client against Folia 26.3 and Paper 26.3 (build 140), with two bots about 12000 blocks apart so that they are in different regions, and compared Paper against an unmodified 2.x build:
/tpposacross regions,/msgand/r,/home,/spawn,/tpa,/tpahere,/backafter death, a chicken riding the teleportee) passes on Folia, and on Paper with this change and without it./essentials reload, chat and rejoining): 249 checks on Folia and on Paper with and without this change, where a check fails on any exception or "Thread failed main thread check" in the log. All pass except one check of my own that is wrong the same way on all three. The replies are the same on Paper with and without this change apart from the version string and a/skullthat depends on how long an HTTP lookup takes.teleport-delay), cancelling one by taking damage, a player disconnecting during a warm-up, unsafe destinations,force-disable-teleport-safety, jail and release (including sending the player to a jail in another region), death and respawn withrespawn-listener-priorityon,/back,/tpr: run on Folia and Paper. The warm-up cancel by moving check fails identically on Paper with and without this change.On Folia the same commands without this change either do not load (
not marked as supporting Folia) or fail on the first Bukkit scheduler call.