Improve database resilience, load handling, cleanup, and Folia reloads - #4772
Improve database resilience, load handling, cleanup, and Folia reloads#4772Minecraft0122 wants to merge 3 commits into
Conversation
Keep queued transactions during temporary connection failures, add registration-date player removal, and verify SQLite merges into non-empty databases.
Cache unchanged extension metadata and replace recursive lock-timeout retries with bounded iterative attempts that return each connection exactly once.\n\nAffects issues:\n- Fix plan-player-analytics#2755
|
If you make further changes please keep each change to its own PR so that it's easier to review - It's difficult if one part of a PR is good and another isn't |
There was a problem hiding this comment.
I can't unfortunately merge this like this because it has 3 different completely unrelated things in it and it seems to be written by AI.
If it was 3 different PRs I could merge one and cleanup, then merge the next one and cleanup - but with it all in big lump I can't possibly hope of cleaning up the AI code to be up to standard.
Please separate the three things into three different branches and 3 PRs so that it is possible to review properly.
I've left some comments that may help when doing the changes.
I did not review properly because I got the feeling that my comments would be fed to AI to generate the fixes, and the larger the PR, the smaller the chances of success for AI to fix them are.
If you wrote this yourself and did not use AI, I can take another look.
| /** | ||
| * Folia runnable factory that prevents tasks from an old Plan lifecycle from running after reload. | ||
| */ | ||
| public class PlanFoliaRunnableFactory implements RunnableFactory { |
There was a problem hiding this comment.
Changes in this file should be done in https://github.com/plan-player-analytics/Platform-abstraction-layer instead.
| /** | ||
| * Plan-specific Folia platform layer with reload-safe task scheduling. | ||
| */ | ||
| public class PlanFoliaPlatformLayer extends FoliaPlatformLayer { |
There was a problem hiding this comment.
Extra PlatformLayer specificly for Plan is not required in this case, changes to it should be done in https://github.com/plan-player-analytics/Platform-abstraction-layer instead
| runtimeOnly "net.playeranalytics:platform-abstraction-layer-folia:$palVersion" | ||
| implementation "net.playeranalytics:platform-abstraction-layer-folia:$palVersion" | ||
|
|
||
| compileOnly "dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT" |
There was a problem hiding this comment.
Changes in this file are not required after moving the other modifications in folia module to pal library.
| try { | ||
| // Attempt to load and use the Folia library for Java 17+ | ||
| Class<?> foliaPlatformLayer = Class.forName("net.playeranalytics.plugin.FoliaPlatformLayer"); | ||
| Class<?> foliaPlatformLayer = Class.forName("net.playeranalytics.plugin.PlanFoliaPlatformLayer"); |
There was a problem hiding this comment.
| Class<?> foliaPlatformLayer = Class.forName("net.playeranalytics.plugin.PlanFoliaPlatformLayer"); | |
| Class<?> foliaPlatformLayer = Class.forName("net.playeranalytics.plugin.FoliaPlatformLayer"); |
| } | ||
|
|
||
| private static final class MetadataKey { | ||
| private final Database database; |
There was a problem hiding this comment.
Database should not be stored in persistent variables, it is also not required here since DataValueGatherer is recreated at the same time if Database instance changes.
| } | ||
|
|
||
| private static void addIcon(List<Object> values, Icon icon) { | ||
| if (icon == null) { |
There was a problem hiding this comment.
If the icon is null it is not stored, so this if-block is unnecessary
|
This was indeed written by AI.
I just wanted it to solve a few problems that are really important to me, but I didn't notice that it sent a PR.
…---Original---
From: "Aurora ***@***.***>
Date: Sun, Aug 2, 2026 23:38 PM
To: ***@***.***>;
Cc: ***@***.******@***.***>;
Subject: Re: [plan-player-analytics/Plan] Improve database resilience, load handling, cleanup, and Folia reloads (PR #4772)
@AuroraLS3 commented on this pull request.
I can't unfortunately merge this like this because it has 3 different completely unrelated things in it and it seems to be written by AI.
If it was 3 different PRs I could merge one and cleanup, then merge the next one and cleanup - but with it all in big lump I can't possibly hope of cleaning up the AI code to be up to standard.
Please separate the three things into three different branches and 3 PRs so that it is possible to review properly.
I've left some comments that may help when doing the changes.
In Plan/folia/src/main/java/net/playeranalytics/plugin/scheduling/PlanFoliaRunnableFactory.java:
> + * along with Plan. If not, see <https://www.gnu.org/licenses/>. + */ +package net.playeranalytics.plugin.scheduling; + +import io.papermc.paper.threadedregions.scheduler.AsyncScheduler; +import io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Folia runnable factory that prevents tasks from an old Plan lifecycle from running after reload. + */ +public class PlanFoliaRunnableFactory implements RunnableFactory {
Changes in this file should be done in https://github.com/plan-player-analytics/Platform-abstraction-layer instead.
In Plan/folia/src/main/java/net/playeranalytics/plugin/PlanFoliaPlatformLayer.java:
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see <https://www.gnu.org/licenses/>. + */ +package net.playeranalytics.plugin; + +import net.playeranalytics.plugin.scheduling.PlanFoliaRunnableFactory; +import net.playeranalytics.plugin.scheduling.RunnableFactory; +import org.bukkit.plugin.java.JavaPlugin; + +/** + * Plan-specific Folia platform layer with reload-safe task scheduling. + */ +public class PlanFoliaPlatformLayer extends FoliaPlatformLayer {
Extra PlatformLayer specificly for Plan is not required in this case, changes to it should be done in https://github.com/plan-player-analytics/Platform-abstraction-layer instead
In Plan/folia/build.gradle:
> @@ -1,5 +1,12 @@ dependencies { - runtimeOnly "net.playeranalytics:platform-abstraction-layer-folia:$palVersion" + implementation "net.playeranalytics:platform-abstraction-layer-folia:$palVersion" + + compileOnly "dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT"
Changes in this file are not required after moving the other modifications in folia module to pal library.
In Plan/bukkit/src/main/java/com/djrapitops/plan/Plan.java:
> @@ -66,7 +66,7 @@ public void onLoad() { if (isFolia()) { try { // Attempt to load and use the Folia library for Java 17+ - Class<?> foliaPlatformLayer = Class.forName("net.playeranalytics.plugin.FoliaPlatformLayer"); + Class<?> foliaPlatformLayer = Class.forName("net.playeranalytics.plugin.PlanFoliaPlatformLayer"); ⬇️ Suggested change - Class<?> foliaPlatformLayer = Class.forName("net.playeranalytics.plugin.PlanFoliaPlatformLayer"); + Class<?> foliaPlatformLayer = Class.forName("net.playeranalytics.plugin.FoliaPlatformLayer");
In Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/gathering/ExtensionMetadataStorage.java:
> + CompletableFuture<?> completion, + Transaction transaction, + MetadataKey key, + MetadataFingerprint fingerprint + ) { + completion.whenComplete((result, failure) -> { + if (failure != null || !transaction.wasExecuted()) { + synchronized (ExtensionMetadataStorage.this) { + storedMetadata.remove(key, fingerprint); + } + } + }); + } + + private static final class MetadataKey { + private final Database database;
Database should not be stored in persistent variables, it is also not required here since DataValueGatherer is recreated at the same time if Database instance changes.
In Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/gathering/ExtensionMetadataStorage.java:
> + addIcon(values, information.getIcon()); + values.add(information.isShownInPlayersTable()); + values.add(information.getTab().orElse(null)); + values.add(information.getCondition().orElse(null)); + values.add(information.isHidden()); + values.add(information.getProvidedCondition()); + values.add(information.getFormatType().orElse(null)); + values.add(information.isPlayerName()); + values.add(information.getTableColor()); + values.add(information.isPercentage()); + values.add(information.isComponent()); + return values; + } + + private static void addIcon(List<Object> values, Icon icon) { + if (icon == null) {
If the icon is null it is not stored, so this if-block is unnecessary
—
Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!
You are receiving this because you authored the thread.Message ID: ***@***.***>
|
|
Sorry to bother you guys :(
…---Original---
From: "Aurora ***@***.***>
Date: Sun, Aug 2, 2026 23:38 PM
To: ***@***.***>;
Cc: ***@***.******@***.***>;
Subject: Re: [plan-player-analytics/Plan] Improve database resilience, load handling, cleanup, and Folia reloads (PR #4772)
@AuroraLS3 commented on this pull request.
I can't unfortunately merge this like this because it has 3 different completely unrelated things in it and it seems to be written by AI.
If it was 3 different PRs I could merge one and cleanup, then merge the next one and cleanup - but with it all in big lump I can't possibly hope of cleaning up the AI code to be up to standard.
Please separate the three things into three different branches and 3 PRs so that it is possible to review properly.
I've left some comments that may help when doing the changes.
In Plan/folia/src/main/java/net/playeranalytics/plugin/scheduling/PlanFoliaRunnableFactory.java:
> + * along with Plan. If not, see <https://www.gnu.org/licenses/>. + */ +package net.playeranalytics.plugin.scheduling; + +import io.papermc.paper.threadedregions.scheduler.AsyncScheduler; +import io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Folia runnable factory that prevents tasks from an old Plan lifecycle from running after reload. + */ +public class PlanFoliaRunnableFactory implements RunnableFactory {
Changes in this file should be done in https://github.com/plan-player-analytics/Platform-abstraction-layer instead.
In Plan/folia/src/main/java/net/playeranalytics/plugin/PlanFoliaPlatformLayer.java:
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see <https://www.gnu.org/licenses/>. + */ +package net.playeranalytics.plugin; + +import net.playeranalytics.plugin.scheduling.PlanFoliaRunnableFactory; +import net.playeranalytics.plugin.scheduling.RunnableFactory; +import org.bukkit.plugin.java.JavaPlugin; + +/** + * Plan-specific Folia platform layer with reload-safe task scheduling. + */ +public class PlanFoliaPlatformLayer extends FoliaPlatformLayer {
Extra PlatformLayer specificly for Plan is not required in this case, changes to it should be done in https://github.com/plan-player-analytics/Platform-abstraction-layer instead
In Plan/folia/build.gradle:
> @@ -1,5 +1,12 @@ dependencies { - runtimeOnly "net.playeranalytics:platform-abstraction-layer-folia:$palVersion" + implementation "net.playeranalytics:platform-abstraction-layer-folia:$palVersion" + + compileOnly "dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT"
Changes in this file are not required after moving the other modifications in folia module to pal library.
In Plan/bukkit/src/main/java/com/djrapitops/plan/Plan.java:
> @@ -66,7 +66,7 @@ public void onLoad() { if (isFolia()) { try { // Attempt to load and use the Folia library for Java 17+ - Class<?> foliaPlatformLayer = Class.forName("net.playeranalytics.plugin.FoliaPlatformLayer"); + Class<?> foliaPlatformLayer = Class.forName("net.playeranalytics.plugin.PlanFoliaPlatformLayer"); ⬇️ Suggested change - Class<?> foliaPlatformLayer = Class.forName("net.playeranalytics.plugin.PlanFoliaPlatformLayer"); + Class<?> foliaPlatformLayer = Class.forName("net.playeranalytics.plugin.FoliaPlatformLayer");
In Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/gathering/ExtensionMetadataStorage.java:
> + CompletableFuture<?> completion, + Transaction transaction, + MetadataKey key, + MetadataFingerprint fingerprint + ) { + completion.whenComplete((result, failure) -> { + if (failure != null || !transaction.wasExecuted()) { + synchronized (ExtensionMetadataStorage.this) { + storedMetadata.remove(key, fingerprint); + } + } + }); + } + + private static final class MetadataKey { + private final Database database;
Database should not be stored in persistent variables, it is also not required here since DataValueGatherer is recreated at the same time if Database instance changes.
In Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/gathering/ExtensionMetadataStorage.java:
> + addIcon(values, information.getIcon()); + values.add(information.isShownInPlayersTable()); + values.add(information.getTab().orElse(null)); + values.add(information.getCondition().orElse(null)); + values.add(information.isHidden()); + values.add(information.getProvidedCondition()); + values.add(information.getFormatType().orElse(null)); + values.add(information.isPlayerName()); + values.add(information.getTableColor()); + values.add(information.isPercentage()); + values.add(information.isComponent()); + return values; + } + + private static void addIcon(List<Object> values, Icon icon) { + if (icon == null) {
If the icon is null it is not stored, so this if-block is unnecessary
—
Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!
You are receiving this because you authored the thread.Message ID: ***@***.***>
|
Summary
/plan db remove_registered <after-date> <before-date>for confirmed, inclusive bulk deletion by first-join dateCommand details
New command:
/plan db remove_registered <after-date> <before-date>/plan db removeregisteredand/plan db remove_betweenplan.data.clearyyyy-MM-ddformat and uses Plan's configured time zone.after-datethrough the end ofbefore-date.after-dateis later thanbefore-date./plan db remove_registered 2026-01-01 2026-01-31Existing command covered by this PR:
/plan db merge <source> <destination>plan.data.merge/plan db merge SQLite MySQLand confirm the prompt.--on-conflict-deletedeletes the conflicting destination server, while--on-conflict-swapassigns the imported conflicting server a different UUID. They are not needed when server UUIDs do not conflict and were not introduced by this PR.Verification
51ee784f6): https://github.com/Minecraft0122/Plan/actions/runs/30634140383MySQLTest > mergeSQLiteIntoNonEmptyDatabase()andSQLiteTest > mergeSQLiteIntoNonEmptyDatabase()both passed on the current head:common:check, Checkstyle, and targeted regression tests passedThe first CI attempt passed all code and test steps but could not deploy Javadocs because the fork's workflow token was read-only. After enabling read/write workflow permissions on the fork (without PR approval permission), the complete second attempt passed.
The SQLite-to-MySQL merge command already exists as
/plan db merge SQLite MySQL; this PR adds the non-empty destination regression test requested for that workflow.Closes #4727
Closes #4719
Closes #4599
Closes #2755