Ensure NameManager caches stay consistent with multiple threads accessing them

This commit is contained in:
Phoenix616 2023-03-24 00:08:48 +01:00
parent d52c329618
commit 013a21159f
No known key found for this signature in database
GPG Key ID: 40E2321E71738EB0
2 changed files with 68 additions and 57 deletions

View File

@ -1,20 +1,21 @@
package com.Acrobot.Breeze.Collection; package com.Acrobot.Breeze.Collection;
import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
public class SimpleCache<K, V> { public class SimpleCache<K, V> {
private final LinkedHashMap<K, V> map; private final Map<K, V> map;
public SimpleCache(int cacheSize) { public SimpleCache(int cacheSize) {
map = new LinkedHashMap<K, V>(cacheSize * 10/9, 0.7f, true) { map = Collections.synchronizedMap(new LinkedHashMap<K, V>(cacheSize * 10/9, 0.7f, true) {
@Override @Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > cacheSize; return size() > cacheSize;
} }
}; });
} }
public V put(K key, V value) { public V put(K key, V value) {

View File

@ -36,6 +36,8 @@ import java.util.logging.Level;
*/ */
@SuppressWarnings("UnusedAssignment") // I deliberately set the variables to null while initializing @SuppressWarnings("UnusedAssignment") // I deliberately set the variables to null while initializing
public class NameManager implements Listener { public class NameManager implements Listener {
private static final Object accountsLock = new Object();
private static Dao<Account, String> accounts; private static Dao<Account, String> accounts;
private static SimpleCache<String, Account> usernameToAccount = new SimpleCache<>(Properties.CACHE_SIZE); private static SimpleCache<String, Account> usernameToAccount = new SimpleCache<>(Properties.CACHE_SIZE);
@ -97,20 +99,22 @@ public class NameManager implements Listener {
*/ */
public static Account getAccount(UUID uuid) { public static Account getAccount(UUID uuid) {
try { try {
return uuidToAccount.get(uuid, () -> { synchronized (accountsLock) {
try { return uuidToAccount.get(uuid, () -> {
Account account = accounts.queryBuilder().orderBy("lastSeen", false).where().eq("uuid", new SelectArg(uuid)).queryForFirst(); try {
if (account != null) { Account account = accounts.queryBuilder().orderBy("lastSeen", false).where().eq("uuid", new SelectArg(uuid)).queryForFirst();
account.setUuid(uuid); // HOW IS IT EVEN POSSIBLE THAT UUID IS NOT SET EVEN IF WE HAVE FOUND THE PLAYER?! if (account != null) {
shortToAccount.put(account.getShortName(), account); account.setUuid(uuid); // HOW IS IT EVEN POSSIBLE THAT UUID IS NOT SET EVEN IF WE HAVE FOUND THE PLAYER?!
usernameToAccount.put(account.getName(), account); shortToAccount.put(account.getShortName(), account);
return account; usernameToAccount.put(account.getName(), account);
return account;
}
} catch (SQLException e) {
ChestShop.getBukkitLogger().log(Level.WARNING, "Error while getting account for " + uuid + ":", e);
} }
} catch (SQLException e) { throw new Exception("Could not find account for " + uuid);
ChestShop.getBukkitLogger().log(Level.WARNING, "Error while getting account for " + uuid + ":", e); });
} }
throw new Exception("Could not find account for " + uuid);
});
} catch (ExecutionException ignored) { } catch (ExecutionException ignored) {
return null; return null;
} }
@ -127,19 +131,21 @@ public class NameManager implements Listener {
Preconditions.checkNotNull(fullName, "fullName cannot be null!"); Preconditions.checkNotNull(fullName, "fullName cannot be null!");
Preconditions.checkArgument(!fullName.isEmpty(), "fullName cannot be empty!"); Preconditions.checkArgument(!fullName.isEmpty(), "fullName cannot be empty!");
try { try {
return usernameToAccount.get(fullName, () -> { synchronized (accountsLock) {
try { return usernameToAccount.get(fullName, () -> {
Account account = accounts.queryBuilder().orderBy("lastSeen", false).where().eq("name", new SelectArg(fullName)).queryForFirst(); try {
if (account != null) { Account account = accounts.queryBuilder().orderBy("lastSeen", false).where().eq("name", new SelectArg(fullName)).queryForFirst();
account.setName(fullName); // HOW IS IT EVEN POSSIBLE THAT THE NAME IS NOT SET EVEN IF WE HAVE FOUND THE PLAYER?! if (account != null) {
shortToAccount.put(account.getShortName(), account); account.setName(fullName); // HOW IS IT EVEN POSSIBLE THAT THE NAME IS NOT SET EVEN IF WE HAVE FOUND THE PLAYER?!
return account; shortToAccount.put(account.getShortName(), account);
return account;
}
} catch (SQLException e) {
ChestShop.getBukkitLogger().log(Level.WARNING, "Error while getting account for " + fullName + ":", e);
} }
} catch (SQLException e) { throw new Exception("Could not find account for " + fullName);
ChestShop.getBukkitLogger().log(Level.WARNING, "Error while getting account for " + fullName + ":", e); });
} }
throw new Exception("Could not find account for " + fullName);
});
} catch (ExecutionException ignored) { } catch (ExecutionException ignored) {
return null; return null;
} }
@ -167,18 +173,20 @@ public class NameManager implements Listener {
Account account = null; Account account = null;
try { try {
account = shortToAccount.get(shortName, () -> { synchronized (accountsLock) {
try { account = shortToAccount.get(shortName, () -> {
Account a = accounts.queryBuilder().where().eq("shortName", new SelectArg(shortName)).queryForFirst(); try {
if (a != null) { Account a = accounts.queryBuilder().where().eq("shortName", new SelectArg(shortName)).queryForFirst();
a.setShortName(shortName); // HOW IS IT EVEN POSSIBLE THAT THE NAME IS NOT SET EVEN IF WE HAVE FOUND THE PLAYER?! if (a != null) {
return a; a.setShortName(shortName); // HOW IS IT EVEN POSSIBLE THAT THE NAME IS NOT SET EVEN IF WE HAVE FOUND THE PLAYER?!
return a;
}
} catch (SQLException e) {
ChestShop.getBukkitLogger().log(Level.WARNING, "Error while getting account for " + shortName + ":", e);
} }
} catch (SQLException e) { throw new Exception("Could not find account for " + shortName);
ChestShop.getBukkitLogger().log(Level.WARNING, "Error while getting account for " + shortName + ":", e); });
} }
throw new Exception("Could not find account for " + shortName);
});
} catch (ExecutionException ignored) {} } catch (ExecutionException ignored) {}
return account; return account;
} }
@ -222,27 +230,29 @@ public class NameManager implements Listener {
final UUID uuid = player.getUniqueId(); final UUID uuid = player.getUniqueId();
Account latestAccount = null; Account latestAccount = null;
try { synchronized (accountsLock) {
latestAccount = accounts.queryBuilder().where().eq("uuid", new SelectArg(uuid)).and().eq("name", new SelectArg(player.getName())).queryForFirst(); try {
} catch (SQLException e) { latestAccount = accounts.queryBuilder().where().eq("uuid", new SelectArg(uuid)).and().eq("name", new SelectArg(player.getName())).queryForFirst();
ChestShop.getBukkitLogger().log(Level.WARNING, "Error while searching for latest account of " + player.getName() + "/" + uuid + ":", e); } catch (SQLException e) {
} ChestShop.getBukkitLogger().log(Level.WARNING, "Error while searching for latest account of " + player.getName() + "/" + uuid + ":", e);
}
if (latestAccount == null) { if (latestAccount == null) {
latestAccount = new Account(player.getName(), getNewShortenedName(player), player.getUniqueId()); latestAccount = new Account(player.getName(), getNewShortenedName(player), player.getUniqueId());
} }
latestAccount.setLastSeen(new Date()); latestAccount.setLastSeen(new Date());
try { try {
accounts.createOrUpdate(latestAccount); accounts.createOrUpdate(latestAccount);
} catch (SQLException e) { } catch (SQLException e) {
ChestShop.getBukkitLogger().log(Level.WARNING, "Error while updating account " + latestAccount + ":", e); ChestShop.getBukkitLogger().log(Level.WARNING, "Error while updating account " + latestAccount + ":", e);
return null; return null;
} }
usernameToAccount.put(latestAccount.getName(), latestAccount); usernameToAccount.put(latestAccount.getName(), latestAccount);
uuidToAccount.put(uuid, latestAccount); uuidToAccount.put(uuid, latestAccount);
shortToAccount.put(latestAccount.getShortName(), latestAccount); shortToAccount.put(latestAccount.getShortName(), latestAccount);
}
return latestAccount; return latestAccount;
} }