Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
706102c
Preserve storage worker ownership during Spigot shutdown
BenCodez Oct 10, 2026
427693a
Admit plugin shutdown work before user storage retirement
BenCodez Oct 10, 2026
60984db
Retire shared storage after failed shutdown preparation
BenCodez Oct 10, 2026
bad9b56
Run reload calendar checks on their background owner
BenCodez Oct 10, 2026
f8d2615
Defer storage retirement after asynchronous preparation callbacks
BenCodez Oct 10, 2026
f840c4a
Retain storage admission until consumer shutdown preparation settles
BenCodez Oct 10, 2026
cffba08
Settle queued calendar checks during deferred shutdown
BenCodez Oct 11, 2026
03f5524
Interrupt fenced calendar work while consumer preparation is pending
BenCodez Oct 12, 2026
89a82c2
Retain admitted storage through deferred unload
BenCodez Oct 12, 2026
3289cde
Finish native storage publication and bounded shutdown ownership
BenCodez Oct 13, 2026
fd53fd3
Preserve final mutations for legacy storage owners
BenCodez Oct 13, 2026
8faac95
Retain admitted reward and storage work beyond shutdown caller deadline
BenCodez Oct 13, 2026
9dcfa67
Separate shutdown lifecycle admission from replay persistence locking
BenCodez Oct 14, 2026
425805f
Preserve visible ServerData publications through write failures
BenCodez Oct 14, 2026
ca7a446
Publish storage retirement admission before worker scheduling
BenCodez Oct 14, 2026
c4bbc57
Merge master and retain bounded native persistence during JVM shutdown
BenCodez Oct 15, 2026
778800f
Fence persisted replay checks before shutdown snapshots
BenCodez Oct 16, 2026
79bd967
Settle final snapshot publications in order
BenCodez Oct 16, 2026
df034e6
Await delayed worker termination in shutdown fixture
BenCodez Oct 16, 2026
b4a4be4
Retain timed replay admission through storage handoff
BenCodez Oct 16, 2026
6719a7c
Use actual publication monitors for live ServerData transactions
BenCodez Oct 16, 2026
acef758
Merge current master into shutdown publication ownership work
BenCodez Oct 17, 2026
c5fba2c
Retain admitted offline replay across queued capture and storage hand…
BenCodez Oct 17, 2026
ad72cc6
Select published scheduler correction in the shared SDK
BenCodez Oct 18, 2026
b4fae4b
Reconcile shared dependency selection with current master
BenCodez Oct 18, 2026
052daff
Preserve shutdown cleanup when ServerData is disabled
BenCodez Oct 18, 2026
9491420
Confirm replay overflow publication before acknowledging delivery
BenCodez Oct 19, 2026
4f41f34
Derive reward scheduler fixture from the declared API type
BenCodez Oct 19, 2026
0e3ba82
Merge current master scheduler compatibility into shutdown ownership
BenCodez Oct 22, 2026
5da4c2b
Expose ordered ordinary YAML mutations for live projections
BenCodez Oct 23, 2026
f596f4b
Serialize overflow snapshots through owner settlement
BenCodez Oct 25, 2026
41080b4
Register test owner callbacks before exposing completion
BenCodez Oct 25, 2026
5537d94
Isolate shutdown observations from persistence completion
BenCodez Oct 25, 2026
7b46a93
Await the actual permission-start event in lifecycle tests
BenCodez Oct 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ For compatibility-sensitive changes, keep regression coverage for established be
2. Do not access Bukkit entities, inventories, worlds, or other thread-confined state from arbitrary async callbacks.
3. Reward execution must not be lost or duplicated across delay, disconnect, reload, retry, or partial failure. Preserve the component's documented delivery contract.
4. Read-only lookups must not create users, mutate caches, or trigger persistence unless that behavior is part of the established API.
5. Keep user identity and UUID resolution consistent across online/offline paths. Coordinate cache updates, database writes, resets, and shutdown flushing.
6. Reload and disable must cancel or retire tasks, listeners, executors, connections, and callbacks without allowing stale work to mutate new state.
5. Keep user identity and UUID resolution consistent across online/offline paths. Coordinate cache updates, database writes, resets, and shutdown flushing. Final shared-runtime retirement fences queued notifications before marking storage retiring; callbacks already holding lifecycle admission may finish and re-enter storage. The optional pre-storage shutdown stage must finish before native storage retirement. Platform cleanup and the completion hook may run earlier on deferred shutdown; consumers must retain their own persistence owner until their stage settles.
6. Reload and disable must cancel or retire tasks, listeners, executors, connections, and callbacks without allowing stale work to mutate new state. Native ServerData admission precedes early enable hooks and reads and is shared across shaded classloaders through bounded opaque JVM Properties tokens. Release only after successful publication/preparation and actual captured native executor termination; failed or cancelled stages retain admission until JVM restart because settlement alone cannot prove drain. Retain the originating Properties object for release. Repeated disable must not submit stale final writes after release. Final whole-file publications settle in order even for asynchronous owners. A visible native final replacement gets one bounded settlement retry; if unconfirmed, fence that owner against subsequent writes/reload/setup so another save cannot commit its unacknowledged values.
7. GUI/editor actions must recheck permissions and ownership and must not duplicate items, lose configuration, or save to the wrong path or user.
8. Optional integrations must be guarded against absent classes and incompatible versions. One platform must not load another platform's API accidentally.

Expand Down
2 changes: 1 addition & 1 deletion AdvancedCore/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@
<dependency>
<groupId>com.bencodez</groupId>
<artifactId>simpleapi</artifactId>
<!-- Published SQL lifecycle fix; do not resolve an older rolling snapshot. -->
<!-- Shared SQL lifecycle and delayed entity scheduling support. -->
<version>1.0.2-SNAPSHOT</version>
<scope>compile</scope>
<exclusions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,54 @@
public abstract class AdvancedCorePlugin extends JavaPlugin {

private static AdvancedCorePlugin javaPlugin;

/** Lifecycle admission is independent from consumer replay/checkpoint synchronization. */
private final Object lifecycleLock = new Object();
private com.bencodez.advancedcore.lifecycle.ServerDataOwnership serverDataOwnership;
private boolean advancedCoreShutdownStarted;
private boolean advancedCoreEnableStarted;
private boolean serverDataStartupRejected;

/** Native file admission also covers consumer hooks that read ServerData early. */
private void acquireServerDataOwnership() {
synchronized (lifecycleLock) {
if (serverDataOwnership != null && !serverDataOwnership.isReleased()) {
if (!advancedCoreShutdownStarted) return;
throw new IllegalStateException("Previous ServerData shutdown has not finished");
}
try {
serverDataOwnership = com.bencodez.advancedcore.lifecycle.ServerDataOwnership.acquire(
new File(getDataFolder(), "ServerData.yml").toPath());
advancedCoreShutdownStarted = false;
advancedCoreEnableStarted = false;
serverDataStartupRejected = false;
} catch (RuntimeException | Error failure) {
serverDataStartupRejected = true;
throw failure;
}
}
}

/** Internal lifecycle fence, independent of consumer shutdown-hook overrides. */
public final boolean hasServerDataOwnership() {
synchronized (lifecycleLock) {
return serverDataOwnership != null && !serverDataOwnership.isReleased();
}
}

/** Internal lifecycle fence, independent of consumer shutdown-hook overrides. */
public final void releaseServerDataOwnershipAfter(CompletionStage<Void> completion) {
com.bencodez.advancedcore.lifecycle.ServerDataOwnership ownership;
synchronized (lifecycleLock) { ownership = serverDataOwnership; }
if (ownership != null) ownership.releaseAfter(completion);
}

private void cleanUpFailedStartup(Throwable failure) {
try { onDisable(); }
catch (Throwable cleanupFailure) {
if (cleanupFailure != failure) failure.addSuppressed(cleanupFailure);
}
}

/**
* Gets the singleton instance of this plugin.
Expand Down Expand Up @@ -843,7 +891,16 @@ private void loadHandle() {
/**
* Load AdvancedCore hook
*/
public void loadHook() {
public void loadHook() {
acquireServerDataOwnership();
try { loadCoreHook(); }
catch (RuntimeException | Error failure) {
cleanUpFailedStartup(failure);
throw failure;
}
}

private void loadCoreHook() {
serverDataFile = new ServerData(this);

hologramHandler = new HologramHandler(this);
Expand Down Expand Up @@ -1381,22 +1438,81 @@ private void loadVersionFile() {
}

@Override
public void onDisable() {
var presence = playerPresenceService;
playerPresenceService = null;
if (presence != null) presence.close();
var lookup = playerPresenceLookup;
playerPresenceLookup = null;
if (lookup != null) {
lookup.close();
org.bukkit.event.HandlerList.unregisterAll(lookup);
public void onDisable() {
long startedNanos = System.nanoTime();
synchronized (lifecycleLock) {
if (advancedCoreShutdownStarted || serverDataStartupRejected) return;
advancedCoreShutdownStarted = true;
}
try {
if (serverDataOwnership != null) serverDataOwnership.beginShutdown(startedNanos);
var presence = playerPresenceService;
playerPresenceService = null;
try {
if (presence != null) presence.close();
} finally {
var lookup = playerPresenceLookup;
playerPresenceLookup = null;
if (lookup != null) {
try { lookup.close(); }
finally { org.bukkit.event.HandlerList.unregisterAll(lookup); }
}
}
} finally {
try { new com.bencodez.advancedcore.lifecycle.AdvancedCoreLifecycle(this).shutdown(startedNanos); }
finally { if (javaPlugin == this) javaPlugin = null; }
}
new com.bencodez.advancedcore.lifecycle.AdvancedCoreLifecycle(this).shutdown();
javaPlugin = null;
}

/**
* Gives a plugin one asynchronous admission boundary before native user
* storage starts retiring. The default keeps the established shutdown
* sequence unchanged. Implementations must return promptly and may use the
* returned stage to account for work admitted before storage retirement;
* they must not wait synchronously for that work here.
* Exceptional completion or cancellation is reported after shared storage
* retirement settles; it does not skip flushing accepted cache writes.
*
* @return completion of plugin-owned pre-storage-shutdown work
*/
public CompletionStage<Void> onBeforeStorageShutdown() {
return CompletableFuture.completedFuture(null);
}

/**
* Called after platform cleanup and submission of the final permission-data save.
* Deferred storage retirement and final file saves may still be running; implementations must
* retain any owner needed by their pre-storage stage until that stage settles.
* The default is a no-op so existing subclasses retain their lifecycle.
*/
public void onShutdownComplete() { }

/**
* Called on the established lifecycle thread after final file work is admitted.
* The stage settles only when all final file owners confirm publication (or fail).
* Consumers must retain required owners until both their preparation and this
* stage settle, and must not synchronously wait or include owner termination in it.
* Existing zero-argument hooks keep their thread and invocation timing.
*/
public void onShutdownComplete(CompletionStage<Void> finalFilesStage) {
onShutdownComplete();
}

@Override
public void onEnable() {
public void onEnable() {
synchronized (lifecycleLock) {
acquireServerDataOwnership();
if (advancedCoreEnableStarted) throw new IllegalStateException("AdvancedCore is already enabled");
advancedCoreEnableStarted = true;
}
try { enableCore(); }
catch (RuntimeException | Error failure) {
cleanUpFailedStartup(failure);
throw failure;
}
}

private void enableCore() {
javaPlugin = this;
com.bencodez.advancedcore.lifecycle.AdvancedCoreLifecycle.RuntimeExecutors runtimeExecutors = com.bencodez.advancedcore.lifecycle.AdvancedCoreLifecycle
.createRuntimeExecutors(this);
Expand Down Expand Up @@ -1716,7 +1832,7 @@ private void reloadAdvancedCoreNow(boolean userStorage) {
}

private void finishReloadAdvancedCore() {
timeChecker.update();
timeChecker.requestUpdate();
TabCompleteHandler.getInstance().reload();
TabCompleteHandler.getInstance().loadTabCompleteOptions();
getRewardHandler().checkSubRewards();
Expand Down
Loading
Loading