Skip to content

fix: do not lose island progress when the server restarts - #18

Merged
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress
Aug 8, 2026
Merged

fix: do not lose island progress when the server restarts#18
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress

Conversation

@tastybento

Copy link
Copy Markdown
Member

Ported from AOneBlock, which this addon is forked from. ChunkBlock carries the bug verbatim — same code, same line numbers. See BentoBoxWorld/AOneBlock#550 for the original report.

The bug

Reported on Discord (against AOneBlock): a player breaks 30 blocks, the server restarts while they are still online, and they come back with those 30 blocks un-broken. Every restart, repeatedly.

The shutdown save was queued rather than written. ChunkBlock.onDisable() called saveCache(), which uses saveObjectAsync(). This addon is a Pladdon, so the server disables it before BentoBox — the write went into a queue that BentoBox had to drain on its way out. BentoBox 3.22.0 added that drain (AbstractDatabaseHandler.flushAll()), but every earlier version discarded it silently.

With the shutdown save lost, the count fell back to the last periodic checkpoint, which was a hardcoded every-50-blocks. Break fewer than 50 between restarts and nothing persists at all.

The fix

Write directly on shutdown. New BlockListener.saveCacheNow() using saveObjectNow(), called from onDisable(). This removes the dependency on core behaviour rather than relying on a particular BentoBox version getting it right. saveCache() is unchanged and still used for onReload(), where async is correct.

island.save-every, default 10 (was a hardcoded SAVE_EVERY = 50). Nothing helps if the server is SIGKILLed — there is no shutdown path to run — but this caps what an unclean kill can cost at 9 blocks instead of 49. getSaveEvery() clamps to ≥1 since it is a modulo divisor.

⚠️ Minimum BentoBox version is now 3.22.0

saveObjectNow() is 3.22.0 API, so bentobox.version is bumped and addon.yml api-version goes 3.13.0 → 3.22.0.

The api-version bump is required, not cosmetic. Without it the addon would still load on an older core and throw NoSuchMethodError at shutdown — worse than the bug being fixed. With it, BentoBox refuses to load the addon and says NOTE: Please update BentoBox.

Verification

Full suite: 620 tests, 0 failures.

Scope caveat: unlike the AOneBlock PR, this port was not boot-tested on a real server. The equivalent AOneBlock change was verified on two (BentoBox 3.15.1 refuses to load it with a clear message and no linkage error; 3.22.1-SNAPSHOT enables, runs and disables cleanly), and the code here is identical, but ChunkBlock itself has only been checked against its test suite.

Test fallout

The BentoBox bump broke the same 5 PhasesPanelTest tests it broke in AOneBlock, at the same line numbers. None were real regressions. All called when(user.getTranslation(...)) on a real User (from User.getInstance(mockPlayer)), which stubs nothing on the User — Mockito attaches the stub to whichever mock the real method last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon(world) first, moving the target.

Ported the stubTranslation() helper that stubs the LocalesManager these actually read from.

Follow-up worth doing: roughly a dozen more when(user.getTranslation(...)) calls remain in that class, passing today by the same accident. They will break on some future BentoBox internal change.

🤖 Generated with Claude Code

https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ

tastybento and others added 2 commits August 8, 2026 12:35
Ported from AOneBlock, which this addon is forked from and shares the bug
with verbatim - same code, same line numbers.
See BentoBoxWorld/AOneBlock#550.

A player breaking blocks and then sitting through a restart would come back
to their block count rolled back to the last checkpoint - up to 49 blocks of
progress gone, repeatedly, on every restart.

The shutdown save was queued, not written. onDisable() called saveCache(),
which uses saveObjectAsync(), and this addon is a Pladdon: the server disables
it before BentoBox, so the write landed in a queue that BentoBox had to drain
on its way out. BentoBox 3.22.0 added that drain (flushAll), but every earlier
version discarded it silently.

Write directly on shutdown instead, via a new saveCacheNow() using
saveObjectNow(). That removes the dependency on core behaviour entirely rather
than relying on a specific BentoBox version getting it right.

saveObjectNow() is BentoBox 3.22.0 API, so bump the dependency and raise
api-version to match. Without the api-version bump the addon would still load
on an older core and throw NoSuchMethodError at shutdown, which is worse than
the bug being fixed. It now refuses to load with "Please update BentoBox".

Also make the periodic save interval configurable as island.save-every,
defaulting to 10 rather than the hardcoded 50. Nothing helps if the server is
SIGKILLed, but this caps what an unclean kill can cost at 9 blocks.

Test notes: the BentoBox bump broke the same 5 PhasesPanelTest tests it broke
in AOneBlock, at the same line numbers. None were regressions - all called
when(user.getTranslation(...)) on a real User rather than a mock, which stubs
nothing on the User and instead attaches to whichever mock the real method
last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon()
first, moving the target. Ported the stubTranslation() helper that stubs the
LocalesManager these actually read from. The same pattern remains elsewhere in
that class and is worth a follow-up sweep.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
BentoBox 3.18.0 onwards is compiled for Java 25 (Minecraft 26.x), so its class
files are version 69. A JDK 21 javac cannot parse those at all, and the build
dies with "class file has wrong version 69.0, should be 65.0" against every
BentoBox type before it reaches any of our code.

This only surfaces once the dependency moves to 3.22.0, as it does in this
branch. It did not show up locally because the dev machine is already on
JDK 25 - the compiler reads the newer class files happily and
<release>21</release> still emits Java 21 bytecode, which is what the addon
ships.

The addon's own target is unchanged: still Java 21 via <release> in the pom.
Only the JDK doing the compiling moves.

Also moves setup-java to v4 and the 'adopt' distribution to 'temurin', since
neither v3 nor adopt offers a Java 25 build.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
@tastybento
tastybento merged commit 366a5fe into develop Aug 8, 2026
1 check passed
@tastybento
tastybento deleted the fix/shutdown-save-progress branch August 8, 2026 19:54
@sonarqubecloud

sonarqubecloud Bot commented Aug 8, 2026

Copy link
Copy Markdown

@tastybento tastybento mentioned this pull request Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant