From e5114144635961371bb1efe868330f3b939e6149 Mon Sep 17 00:00:00 2001 From: laforetbrut Date: Thu, 26 Mar 2026 22:06:38 +0100 Subject: [PATCH] Final hardening: online=0 in finally + auto-save race fix CRITICAL-1: online=0 moved to finally block in logout handler. If store() threw an exception, online=0 was never written and the player was permanently locked out of all servers. CRITICAL-2: Same fix for shutdown handler. Any save failure during shutdown left the player permanently stuck as online=1. IMPORTANT: Auto-save background DB write now acquires tryLock() before writing. If logout already saved newer data and holds/held the lock, the stale auto-save snapshot is skipped. Prevents overwriting correct logout data with an older snapshot. Vyrriox --- .../fubuki/playersync/sync/VanillaSync.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java index 6c9cb31..1deb683 100644 --- a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java +++ b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java @@ -731,11 +731,17 @@ public class VanillaSync { if (ModList.get().isLoaded("refinedstorage")) { ModsSupport.storeRefinedStorageDisks(player); } - JDBCsetUp.executePreparedUpdate("UPDATE player_data SET online=0 WHERE uuid=?", puuid); PlayerSync.LOGGER.info("Saved player {} data on server shutdown", player.getUUID()); } catch (Exception e) { PlayerSync.LOGGER.error("Error saving player {} on shutdown", player.getUUID(), e); } finally { + // CRITICAL: online=0 MUST be in finally - if any save throws, + // player gets permanently locked as online=1 + try { + JDBCsetUp.executePreparedUpdate("UPDATE player_data SET online=0 WHERE uuid=?", puuid); + } catch (Exception e2) { + PlayerSync.LOGGER.error("CRITICAL: Failed to mark player {} offline on shutdown", puuid, e2); + } lock.unlock(); } } @@ -794,10 +800,16 @@ public class VanillaSync { ModCompatSync.storeAll(player); // Save main inventory + effects + advancements (main thread - safe) store(player, false); - JDBCsetUp.executePreparedUpdate("UPDATE player_data SET online=0 WHERE uuid=?", player_uuid); } catch (Exception e) { PlayerSync.LOGGER.error("Error during player logout save for {}", player_uuid, e); } finally { + // CRITICAL: online=0 MUST be in finally - if store() throws, player gets + // permanently locked as online=1 and can never reconnect. + try { + JDBCsetUp.executePreparedUpdate("UPDATE player_data SET online=0 WHERE uuid=?", player_uuid); + } catch (Exception e2) { + PlayerSync.LOGGER.error("CRITICAL: Failed to mark player {} offline", player_uuid, e2); + } lock.unlock(); removePlayerLock(player_uuid); } @@ -1137,11 +1149,16 @@ public class VanillaSync { } // === BACKGROUND THREAD: Write main snapshot to DB (slow, off main thread) === + // Use tryLock in the background task to skip if logout already saved newer data executorService.submit(() -> { + ReentrantLock bgLock = getPlayerLock(puuid); + if (!bgLock.tryLock()) return; // logout won the race, skip stale snapshot try { writeSnapshotToDB(snapshot); } catch (Exception e) { PlayerSync.LOGGER.error("Error auto-saving player {}", puuid, e); + } finally { + bgLock.unlock(); } }); } catch (Exception e) {