@@ -79,6 +79,17 @@ public class UserDataManager {
7979 // concurrent Folia snapshot from re-adding a player whose quit event won the
8080 // race; a later snapshot removes tombstones once Bukkit no longer lists them.
8181 private final ConcurrentHashMap <UUID , Boolean > onlineUserSessions = new ConcurrentHashMap <>();
82+ private final Object [] onlineSessionLocks = createOnlineSessionLocks ();
83+
84+ private static Object [] createOnlineSessionLocks () {
85+ Object [] locks = new Object [64 ];
86+ java .util .Arrays .setAll (locks , ignored -> new Object ());
87+ return locks ;
88+ }
89+
90+ private Object onlineSessionLock (UUID uuid ) {
91+ return onlineSessionLocks [(uuid .hashCode () & Integer .MAX_VALUE ) % onlineSessionLocks .length ];
92+ }
8293
8394 private static final class SharedCachePopulationState {
8495 private long generation ;
@@ -1180,12 +1191,19 @@ public void clearNonNeededCachedUsers() {
11801191 }
11811192 java .util .HashSet <UUID > online = new java .util .HashSet <>();
11821193 for (UUID uuid : platformOnline ) {
1183- Boolean state = onlineUserSessions .compute (uuid ,
1184- (ignored , current ) -> Boolean .FALSE .equals (current ) ? Boolean .FALSE : Boolean .TRUE );
1185- if (Boolean .TRUE .equals (state )) online .add (uuid );
1194+ synchronized (onlineSessionLock (uuid )) {
1195+ Boolean state = onlineUserSessions .compute (uuid ,
1196+ (ignored , current ) -> Boolean .FALSE .equals (current ) ? Boolean .FALSE : Boolean .TRUE );
1197+ if (Boolean .TRUE .equals (state )) online .add (uuid );
1198+ }
1199+ }
1200+ for (UUID uuid : Set .copyOf (onlineUserSessions .keySet ())) {
1201+ synchronized (onlineSessionLock (uuid )) {
1202+ if (Boolean .FALSE .equals (onlineUserSessions .get (uuid )) && !platformOnline .contains (uuid )) {
1203+ onlineUserSessions .remove (uuid , Boolean .FALSE );
1204+ }
1205+ }
11861206 }
1187- onlineUserSessions .entrySet ().removeIf (entry -> Boolean .FALSE .equals (entry .getValue ())
1188- && !platformOnline .contains (entry .getKey ()));
11891207 try {
11901208 timer .execute (() -> {
11911209 try { clearNonNeededCachedUsers (online ); }
@@ -1220,7 +1238,9 @@ private UUID onlineStorageUuid(Player player) {
12201238 }
12211239
12221240 public void markUserOnline (UUID uuid ) {
1223- if (uuid != null ) onlineUserSessions .put (uuid , Boolean .TRUE );
1241+ if (uuid != null ) synchronized (onlineSessionLock (uuid )) {
1242+ onlineUserSessions .put (uuid , Boolean .TRUE );
1243+ }
12241244 }
12251245
12261246 /** Capture an online session from a Bukkit/Folia-owned player event. */
@@ -1229,7 +1249,9 @@ public void markUserOnline(Player player) {
12291249 }
12301250
12311251 public void markUserOffline (UUID uuid ) {
1232- if (uuid != null ) onlineUserSessions .put (uuid , Boolean .FALSE );
1252+ if (uuid != null ) synchronized (onlineSessionLock (uuid )) {
1253+ onlineUserSessions .put (uuid , Boolean .FALSE );
1254+ }
12331255 }
12341256
12351257 /** Remove an online session from a Bukkit/Folia-owned player event. */
@@ -1257,16 +1279,22 @@ private void clearNonNeededCachedUsers(Set<UUID> onlineSnapshot) {
12571279 }
12581280 try {
12591281 current .clearCache ();
1260- if (sharedSqlRoute != null ) current .retireAfterSharedFlush ();
12611282 } catch (RuntimeException | Error failure ) {
12621283 current .cancelRemoval ();
12631284 throw failure ;
12641285 }
1265- boolean cacheRemoved = retireSharedCache (uuid , current );
1266- Consumer <UUID > listener = sharedCacheRemovalListener ;
1267- if (cacheRemoved && listener != null ) listener .accept (uuid );
1268- retired .set (cacheRemoved );
1286+ synchronized (onlineSessionLock (uuid )) {
1287+ if (Boolean .TRUE .equals (onlineUserSessions .get (uuid ))) {
1288+ current .cancelRemoval ();
1289+ return ;
1290+ }
1291+ if (sharedSqlRoute != null ) current .retireAfterSharedFlush ();
1292+ boolean cacheRemoved = retireSharedCache (uuid , current );
1293+ retired .set (cacheRemoved );
1294+ }
12691295 });
1296+ Consumer <UUID > listener = sharedCacheRemovalListener ;
1297+ if (retired .get () && listener != null ) listener .accept (uuid );
12701298 if (retired .get ()) removed ++;
12711299 }
12721300 if (removed > 0 ) plugin .devDebug ("Removed " + removed + " cached users who are no longer online" );
0 commit comments