Stop threads and subscribers that outlive a plugin disable - #1020
Open
tricrotism wants to merge 4 commits into
Open
tricrotism wants to merge 4 commits into
tricrotism wants to merge 4 commits into
Conversation
The WorldEdit subscriber, the cache sweeper thread, per-click inspector threads and the bStats scheduler all kept running after CoreProtect was disabled, and the Folia shutdown checkpoint could wait 30 seconds on tasks that never run. Shutdown now unregisters and stops each of them, inspector lookups use a small named pool, and the checkpoint waits 5 seconds.
❌ Deploy Preview for coreprotect failed. Why did it fail? →
|
Contributor
|
Thanks -- automated review is requesting the following changes:
|
The inspector executor was a static final that stayed shut down after a disable, so a same-instance enable could never run another lookup. It is now created on each enable. At shutdown, lookups still queued after the drain window release their throttle slot, since shutdownNow() returns them without running their finally block, and running lookups that survive the interrupt are logged by thread and player after a bounded wait. The Folia checkpoint timeout change is reverted to keep this change to thread cleanup.
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.
Summary
CoreProtect starts a handful of threads and subscribers on enable and never stops any of them on disable. They keep running against a disabled plugin and hold on to its classloader after a plugin reload. This change stops each of them during
ShutdownService.safeShutdown.The problem
EditSessionEventsubscriber is only unregistered when/co reloadturns WorldEdit logging off (config/ConfigHandler.java:952). Shutdown never unregisters it. Every later edit session still wraps its extent inCoreProtectLoggerfrom the disabled plugin, and edits that run while CoreProtect drains its queue at stop time still callQueueafterserverRunninghas gone false.PluginInitializationService.java:164startsnew Thread(new CacheHandler()): unnamed, untracked, not a daemon. It only checksserverRunningafter a full 14-slot pass, so it keeps sweeping for up to 14 seconds after disable. Nothing can stop it early, because itscatch (Exception)treats an interrupt as an error and keeps looping.BaseInspector,ArmorStandManipulateListenerandHangingBreakByEntityListenerstart a new raw thread per inspector click. There is no bound on how many run at once, and nothing stops them at shutdown.Metricsobject is created and discarded, so its scheduler thread keeps running until its next submit notices the plugin is disabled.EntitySpawnTracking.java:818schedules a checkpoint task per tracked entity owned by another region, then waits up to 30 seconds for all of them. The comment above that loop already notes that "a newly scheduled task may never run during shutdown". When they do not run, the stop waits the full 30 seconds and then reports theTimeoutExceptionas an error.The fix
services/ShutdownService.java: unregisters the WorldEdit subscriber and stops bStats at the start of shutdown. After the queue drain it stops the cache thread and the inspector pool, beforeperformDisable.worldedit/CoreProtectLogger.java,worldedit/FastAsyncWorldEditLogger.java: pass edits straight through onceserverRunningis false.thread/CacheHandler.java:startThread()andstopThread(long)own a named daemon thread (CoreProtect-Cache). An interrupt ends the loop.listener/player/inspector/BaseInspector.java: inspector lookups run on a pool of two named daemon threads (CoreProtect-Inspector-N) with a queue of 64.runLookupkeeps the existingLookupThrottlehandling. If the queue is full, it releases the throttle and tells the player the database is busy.shutdown()waits up to 5 seconds, then interrupts. The armor stand and hanging listeners userunLookupinstead of starting their own threads.services/PluginInitializationService.java: keeps theMetricsinstance and calls itsshutdown()on disable.utility/EntitySpawnTracking.java: the Folia checkpoint waits 5 seconds and does not report a timeout. A task that has not run after 5 seconds during a stop is the case the existing comment describes, and it does not checkpoint whether the stop waits 5 or 30 seconds for it, so the shorter wait loses nothing the longer one saved.Behaviour change
Risk
unloadWorldEdit()is only called from/co reloadtoday, not fromperformDisable, so it runs once at shutdown.LookupThrottlealready limits each player to one at a time. The pool bounds the total across players, which upstream did not.Testing
Build:
mvn packagepasses.Row parity: the 47-step scenario on Paper 26.2 and Folia 1.21.11 with SQLite, ending in a normal server stop, matches upstream apart from the random plant that bone meal grows. No new errors, and the stop completed normally on both.
Suggested test: enable, inspect a few blocks, then
/plugman reload CoreProtect(or disable and enable through a plugin manager) and take a thread dump. Upstream leaves an unnamedThread-NrunningCacheHandlerand one thread per inspector click. This branch leaves no CoreProtect threads. With WorldEdit installed, a//setafter disabling CoreProtect should no longer go throughCoreProtectLogger.