@@ -74,6 +74,8 @@ public class UserDataManager {
7474 private final Set <UUID > sharedCachePopulations = ConcurrentHashMap .newKeySet ();
7575 private final Set <UUID > completedSharedCachePopulations = ConcurrentHashMap .newKeySet ();
7676 private final ConcurrentHashMap <UUID , SharedCachePopulationState > sharedCachePopulationStates = new ConcurrentHashMap <>();
77+ /** Live storage identities, maintained from platform join/quit events. */
78+ private final Set <UUID > onlineUserSessions = ConcurrentHashMap .newKeySet ();
7779
7880 private static final class SharedCachePopulationState {
7981 private long generation ;
@@ -1162,19 +1164,91 @@ public void clearCacheBasic() {
11621164 if (plugin .getStorageType ().equals (UserStorage .MYSQL )) plugin .getMysql ().clearCacheBasic ();
11631165 }
11641166
1167+ /**
1168+ * Capture Bukkit-owned online-player state on the platform scheduler, then do
1169+ * cache/storage eviction on the user-storage worker.
1170+ */
11651171 public void clearNonNeededCachedUsers () {
1172+ Runnable capture = () -> {
1173+ java .util .HashSet <UUID > online = new java .util .HashSet <>();
1174+ for (Player player : Bukkit .getOnlinePlayers ()) {
1175+ UUID storageUuid = onlineStorageUuid (player );
1176+ if (storageUuid != null ) {
1177+ online .add (storageUuid );
1178+ onlineUserSessions .add (storageUuid );
1179+ }
1180+ }
1181+ try {
1182+ timer .execute (() -> {
1183+ try { clearNonNeededCachedUsers (online ); }
1184+ catch (RuntimeException | Error failure ) {
1185+ reportDeferredStorageFailure (failure );
1186+ throw failure ;
1187+ }
1188+ });
1189+ } catch (RejectedExecutionException rejected ) {
1190+ if (plugin != null && plugin .isEnabled ()) reportDeferredStorageFailure (rejected );
1191+ }
1192+ };
1193+ if (Bukkit .getServer () == null ) capture .run ();
1194+ else plugin .getBukkitScheduler ().runTask (plugin , capture );
1195+ }
1196+
1197+ private UUID onlineStorageUuid (Player player ) {
1198+ if (player == null ) return null ;
1199+ if (plugin .getOptions ().isOnlineMode ()) return player .getUniqueId ();
1200+ try {
1201+ String resolved = UuidLookup .getInstance ().getUUID (player .getName ());
1202+ return resolved == null || resolved .isEmpty () ? null : UUID .fromString (resolved );
1203+ } catch (RuntimeException failure ) {
1204+ plugin .debug (failure );
1205+ return null ;
1206+ }
1207+ }
1208+
1209+ public void markUserOnline (UUID uuid ) {
1210+ if (uuid != null ) onlineUserSessions .add (uuid );
1211+ }
1212+
1213+ public void markUserOffline (UUID uuid ) {
1214+ if (uuid != null ) onlineUserSessions .remove (uuid );
1215+ }
1216+
1217+ private void clearNonNeededCachedUsers (Set <UUID > onlineSnapshot ) {
11661218 plugin .devDebug ("Clearing cache for non online players (if any)" );
1167- ArrayList <UUID > onlineUUIDS = new ArrayList <>();
1168- for (Player p : Bukkit .getOnlinePlayers ()) onlineUUIDS .add (p .getUniqueId ());
11691219 int removed = 0 ;
1170- for (UUID uuid : userDataCache .keySet ()) {
1171- if (!onlineUUIDS .contains (uuid )) { removeCache (uuid , null ); removed ++; }
1220+ for (UUID uuid : Set .copyOf (userDataCache .keySet ())) {
1221+ if (onlineSnapshot .contains (uuid ) || onlineUserSessions .contains (uuid )) continue ;
1222+ UserDataCache expected = userDataCache .get (uuid );
1223+ if (expected == null ) continue ;
1224+ long expectedVersion = expected .getSharedSnapshotVersion ();
1225+ java .util .concurrent .atomic .AtomicBoolean retired = new java .util .concurrent .atomic .AtomicBoolean ();
1226+ withSharedSqlBackendExclusive (uuid , () -> {
1227+ if (onlineUserSessions .contains (uuid )) return ;
1228+ UserDataCache current = userDataCache .get (uuid );
1229+ if (current != expected || current .getSharedSnapshotVersion () != expectedVersion ) return ;
1230+ current .beginRemoval ();
1231+ if (onlineUserSessions .contains (uuid )) {
1232+ current .cancelRemoval ();
1233+ return ;
1234+ }
1235+ try {
1236+ current .clearCache ();
1237+ if (sharedSqlRoute != null ) current .retireAfterSharedFlush ();
1238+ } catch (RuntimeException | Error failure ) {
1239+ current .cancelRemoval ();
1240+ throw failure ;
1241+ }
1242+ boolean cacheRemoved = retireSharedCache (uuid , current );
1243+ Consumer <UUID > listener = sharedCacheRemovalListener ;
1244+ if (cacheRemoved && listener != null ) listener .accept (uuid );
1245+ retired .set (cacheRemoved );
1246+ });
1247+ if (retired .get ()) removed ++;
11721248 }
11731249 if (removed > 0 ) plugin .devDebug ("Removed " + removed + " cached users who are no longer online" );
11741250 }
11751251
1176- public boolean containsKey (UUID fromString ) { return userDataCache .containsKey (fromString ); }
1177- /** Return an already-published cache snapshot without creating or populating one. */
11781252 public UserDataCache getPublishedCache (UUID uuid ) {
11791253 UserDataCache cache = userDataCache .get (uuid );
11801254 return cache != null && cache .hasPublishedStorageSnapshot () ? cache : null ;
0 commit comments