▶️ ЗАБЕРИ СВОИ 8 ПОДАРКОВ 🎁 ПРИ СОЗДАНИИ СВОЕГО МАЙНКРАФТ СЕРВЕРА
Моды/BetterAutoSave
BetterAutoSave

BetterAutoSave

BetterAutoSave - серверный мод для Forge 1.20.1 и NeoForge 1.21.1, который выносит автосохранение чанков, сущностей и SavedData в фоновые потоки. Убирает лаг-спайки от автосейва и телепортов.

Оцените первым
31.1K
6
Все версии→Shinoyuki-BetterAutoSave 0.11.0

Shinoyuki-BetterAutoSave 0.11.0

Release12.06.2026

Список изменений

v0.11.0 — Relay Protocol Redesign

The pendingSnapshot relay protocol (the mechanism that relays the latest-generation snapshot to disk when in-flight IO collides with new edits) used to spread its state across four independent atomic units with no single linearization point; multiple rounds of interleaving attacks proved that patching could not converge. This release consolidates it into a single-word SlotWord CAS state machine and systematically seals the collision, terminal and shutdown paths — 10 Critical / 3 Major / 1 Minor fixes, unit tests 139 -> 179. Features and config unchanged. Each entry reads "call site: 0.10 consequence; current behavior"

Relay protocol (chunk)

  • ChunkMapSaveMixin collision branch, old order capture -> dispatch -> register: in 0.10, an in-flight IO landing during dispatch found an empty slot, and the relay snapshot registered afterwards became an orphan with no consumer — the latest generation was silently lost; registration now precedes dispatch, so the callback can see the relay slot at any moment
  • Same branch with register moved first, while a third-party ChunkDataEvent.Save listener is still mutating the tag on the main thread during dispatch: in 0.10 the in-flight callback could take the not-yet-ready tag and resubmit it, three threads sharing one NBT without barriers, potentially corrupting what hit the disk; the relay slot is now a three-state EMPTY/PREPARING/READY machine — consumption rights never leave the main thread before dispatch returns, callbacks only mark missed and the main-thread publish self-kicks
  • ChunkSaveTask terminal-death exits (onUnhandledError / retries exhausted) used drainPendingSnapshot to seize the slot unconditionally: in 0.10 they could equally seize a PREPARING tag still being mutated by the main thread; replaced by takeReadyForTerminalConsumer with atomic three-way consumption — READY is relayed directly, PREPARING is handed back to the main thread, only EMPTY falls through to the vanilla fallback
  • Stale missed marker surviving across save cycles at enterSerializing: in 0.10 a new cycle could inherit it and the main thread would resubmit concurrently before the current generation's IO had landed — with multiple workers, an older generation could overwrite the newest one, silently losing data; replaced by a generational fence (globally monotonic cycleSeq), missed is only valid within its own cycle
  • Landing and slot-take split into two steps (ioCompletedSuccessfully + takeReadyPendingSnapshot): in 0.10, getting descheduled between the two steps stamped missed into the wrong cycle, causing premature relays or a false CLEAN that settled the books early; landAndTake merges both into a single CAS, missed is always stamped with the cycle at landing time
  • Protocol fields (phase / mustDrain / relay slot / in-flight generation) consolidated into the single-word SlotWord: in 0.10, the interleaving of a terminal consumer clearing mustDrain with the main thread's begin occupying the slot could drop the gauge to 0, so the shutdown join stopped waiting for an in-flight relay and lost its increment; with the single CAS, a non-empty slot now guarantees gauge >= 1
  • begin landing after the callback has already exited via CLEAN_LANDED (phase==CLEAN): in 0.10 neither self-kick trigger fired and publish released a READY orphan with no consumer ever coming — increments were silently lost on the unload path and mustDrainPending leaked +1 permanently; begin now sets a no-in-flight-consumer flag in the same CAS, and publish uses it to hand the pending back to the main thread for an immediate resubmit
  • markMustDrain in the terminal-failure HANDED_TO_MAIN branch overwrote TERMINAL_HANDED: in 0.10 this deactivated one of publish's self-kick triggers (the redundant trigger covered it, no data loss); the overwrite is removed, both triggers now work independently

Entity path

  • EntitySaveTask CLEAN_LANDED branch evicted without taking the slot: in 0.10, when the callback misjudged CLEAN from a stale generation read, the freshest entity list just registered by the main thread was garbage-collected together with the state object — the entities had already been evicted by vanilla and there is no recovery channel, a permanent silent loss; CLEAN_LANDED now always takes the slot before evicting, and relays if non-empty
  • EntityStorageMixin registration and callback slot-take invisible to each other: in 0.10, a REQUEUE callback taking the slot before registration got null and skipped the resubmit, and the pending registered afterwards fell into a dead state — stuck forever with mustDrainPending leaking +1; a write-then-read duality is now established (callback writes the terminal phase then takes the slot, main thread writes the slot then re-reads phase) so at least one side always sees the other, getAndSet arbitrates the sole consumer, and the main thread self-kicks a resubmit when it detects no in-flight consumer
  • EntitySaveState.ioFailed published a transient DIRTY during in-place REQUEUE resubmits: in 0.10 a main-thread re-read hitting that window misjudged "no in-flight consumer" and stole the relay slot, running a second in-flight cycle against the retrying callback and overwriting inFlightGeneration; the resubmit now stays IO_PENDING throughout, and DIRTY is narrowed to the true terminal "no consumer, wait for next cycle"
  • Entity collision branch not clearing mustDrain after capturePending throws (typically OOM): in 0.10 the gauge leaked +1 permanently for that position; the catch now performs the CAS cleanup, symmetric with the chunk-side cancellation path

IO failure and shutdown

  • A synchronous throw from submit inside submitIo was swallowed by a dependent future nobody joins: in 0.10 the task was stuck in IO_PENDING forever, inFlightIoPending leaked +1, and the shutdown drainPending never saw zero; submit is now wrapped in its own try — a synchronous throw rebalances the gauge and goes through the existing IO-failure terminal compensation
  • A synchronous throw (OOM) at the REQUEUE relay-resubmit sink: in 0.10 the already-taken relay generation was lost outright and the serializing gauge leak poisoned the shutdown verdict; resubmits are wrapped in safeReoffer, the sink balances its own inc/dec, and an exceptionally handler at the end of the callback chain guarantees no path stays silent
  • The workersStopping residual window (a relay arriving after shutdown began) only logged without settling: in 0.10 mustDrain became an ownerless orphan — the diagnostics idle gate never closed, drain-unload reported false timeouts, and the Prometheus gauge drifted permanently; the residual-window branch now performs the real CAS cleanup, idempotent against double decrements

Upgrading

Drop-in jar replacement, config unchanged

Файлы

shinoyuki_betterautosave-0.11.0.jar(454.72 KiB)
Основной
Скачать

Метаданные

Канал релиза

Release

Номер версии

0.11.0

Загрузчики

Forge

Версии игры

1.20.1

Загрузок

36

Дата публикации

12.06.2026

Загрузил

ID версии

Главная