PC-267 PC-545 PC-1253
This is to fix a troublesome race condition occurring between Mineplexer and Core. Simply put, the following steps occur normally: 1) Player logs in via Bungee 2) If there is no PlayerInfo entry, PlayerStats inserts a PlayerInfo entry into Redis 3) CoreClientManager looks for the PlayerInfo entry and finds it 4) If the PlayerInfo entry was just inserted, it will have accountId=0. CoreClientManager sees this and updates it to a valid accountId, then reinserts into Redis 5) All is good However, sometimes Step 3 occurs before Step 2 (perhaps latency to Redis is a factor), and so CoreClientManager sees a null entry and ignores it. Then, an invalid PlayerInfo entry is inserted with accountId=0, which then breaks any SQL queries relying on an valid accountId
This commit is contained in:
parent
cd387284bf
commit
7e18989fb7
|
@ -4,37 +4,43 @@ import java.util.UUID;
|
||||||
|
|
||||||
import mineplex.serverdata.Region;
|
import mineplex.serverdata.Region;
|
||||||
import mineplex.serverdata.redis.RedisDataRepository;
|
import mineplex.serverdata.redis.RedisDataRepository;
|
||||||
|
import mineplex.serverdata.redis.atomic.RedisStringRepository;
|
||||||
import mineplex.serverdata.servers.ServerManager;
|
import mineplex.serverdata.servers.ServerManager;
|
||||||
|
|
||||||
public class PlayerCache
|
public enum PlayerCache
|
||||||
{
|
{
|
||||||
private static PlayerCache _instance = null;
|
INSTANCE;
|
||||||
|
|
||||||
private RedisDataRepository<PlayerInfo> _repository;
|
|
||||||
|
|
||||||
public static PlayerCache getInstance()
|
public static PlayerCache getInstance()
|
||||||
{
|
{
|
||||||
if (_instance == null)
|
return INSTANCE;
|
||||||
_instance = new PlayerCache();
|
|
||||||
|
|
||||||
return _instance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlayerCache()
|
private final RedisDataRepository<PlayerInfo> _playerInfoRepository;
|
||||||
|
private final RedisStringRepository _accountIdRepository;
|
||||||
|
|
||||||
|
PlayerCache()
|
||||||
{
|
{
|
||||||
_repository = new RedisDataRepository<PlayerInfo>(
|
_playerInfoRepository = new RedisDataRepository<PlayerInfo>(
|
||||||
ServerManager.getMasterConnection(),
|
ServerManager.getMasterConnection(),
|
||||||
ServerManager.getSlaveConnection(),
|
ServerManager.getSlaveConnection(),
|
||||||
Region.ALL,
|
Region.ALL,
|
||||||
PlayerInfo.class,
|
PlayerInfo.class,
|
||||||
"playercache");
|
"playercache");
|
||||||
|
|
||||||
|
_accountIdRepository = new RedisStringRepository(
|
||||||
|
ServerManager.getMasterConnection(),
|
||||||
|
ServerManager.getSlaveConnection(),
|
||||||
|
Region.ALL,
|
||||||
|
"accountid"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPlayer(PlayerInfo player)
|
public void addPlayer(PlayerInfo player)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_repository.addElement(player, 60 * 60 * 6); // 6 Hours
|
_playerInfoRepository.addElement(player, 60 * 60 * 6); // 6 Hours
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
|
@ -42,12 +48,12 @@ public class PlayerCache
|
||||||
exception.printStackTrace();
|
exception.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PlayerInfo getPlayer(UUID uuid)
|
public PlayerInfo getPlayer(UUID uuid)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
PlayerInfo playerInfo = _repository.getElement(uuid.toString());
|
PlayerInfo playerInfo = _playerInfoRepository.getElement(uuid.toString());
|
||||||
return playerInfo;
|
return playerInfo;
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
|
@ -55,23 +61,49 @@ public class PlayerCache
|
||||||
System.out.println("Error retrieving player info in PlayerCache : " + exception.getMessage());
|
System.out.println("Error retrieving player info in PlayerCache : " + exception.getMessage());
|
||||||
exception.printStackTrace();
|
exception.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to grab a player's account ID from the cache
|
* Attempts to grab a player's account ID from the cache
|
||||||
|
*
|
||||||
* @param uuid Minecraft Account UUID
|
* @param uuid Minecraft Account UUID
|
||||||
* @return The account id of the player, or -1 if the player is not in the cache
|
* @return The account id of the player, or -1 if the player is not in the cache
|
||||||
*/
|
*/
|
||||||
public int getAccountId(UUID uuid)
|
public int getAccountId(UUID uuid)
|
||||||
{
|
{
|
||||||
PlayerInfo info = getPlayer(uuid);
|
String accountIdStr = _accountIdRepository.get(uuid.toString());
|
||||||
return info == null ? -1 : info.getAccountId();
|
|
||||||
|
if (accountIdStr == null)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int accountId = Integer.parseInt(accountIdStr);
|
||||||
|
if (accountId <= 0)
|
||||||
|
{
|
||||||
|
// remove invalid account id
|
||||||
|
_accountIdRepository.del(uuid.toString());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return accountId;
|
||||||
|
}
|
||||||
|
catch (NumberFormatException ex)
|
||||||
|
{
|
||||||
|
// remove invalid account id
|
||||||
|
_accountIdRepository.del(uuid.toString());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateAccountId(UUID uuid, int newId)
|
||||||
|
{
|
||||||
|
_accountIdRepository.set(uuid.toString(), String.valueOf(newId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clean()
|
public void clean()
|
||||||
{
|
{
|
||||||
_repository.clean();
|
_playerInfoRepository.clean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,11 +36,6 @@ public class PlayerInfo implements Data
|
||||||
return _id;
|
return _id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAccountId()
|
|
||||||
{
|
|
||||||
return _accountId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getUUID()
|
public UUID getUUID()
|
||||||
{
|
{
|
||||||
return _uuid;
|
return _uuid;
|
||||||
|
@ -90,11 +85,6 @@ public class PlayerInfo implements Data
|
||||||
{
|
{
|
||||||
_version = version;
|
_version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAccountId(int accountId)
|
|
||||||
{
|
|
||||||
_accountId = accountId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateLoginTime()
|
public void updateLoginTime()
|
||||||
{
|
{
|
||||||
|
|
|
@ -31,7 +31,6 @@ import com.google.common.collect.Sets;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
import mineplex.cache.player.PlayerCache;
|
import mineplex.cache.player.PlayerCache;
|
||||||
import mineplex.cache.player.PlayerInfo;
|
|
||||||
import mineplex.core.MiniPlugin;
|
import mineplex.core.MiniPlugin;
|
||||||
import mineplex.core.account.command.TestRank;
|
import mineplex.core.account.command.TestRank;
|
||||||
import mineplex.core.account.command.UpdateRank;
|
import mineplex.core.account.command.UpdateRank;
|
||||||
|
@ -301,13 +300,7 @@ public class CoreClientManager extends MiniPlugin
|
||||||
|
|
||||||
if (client.getAccountId() > 0)
|
if (client.getAccountId() > 0)
|
||||||
{
|
{
|
||||||
PlayerInfo playerInfo = PlayerCache.getInstance().getPlayer(uuid);
|
PlayerCache.getInstance().updateAccountId(uuid, client.getAccountId());
|
||||||
|
|
||||||
if (playerInfo != null)
|
|
||||||
{
|
|
||||||
playerInfo.setAccountId(client.getAccountId());
|
|
||||||
PlayerCache.getInstance().addPlayer(playerInfo);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loaded.set(client);
|
loaded.set(client);
|
||||||
|
@ -367,13 +360,7 @@ public class CoreClientManager extends MiniPlugin
|
||||||
|
|
||||||
if (client.getAccountId() > 0)
|
if (client.getAccountId() > 0)
|
||||||
{
|
{
|
||||||
PlayerInfo playerInfo = PlayerCache.getInstance().getPlayer(uuid);
|
PlayerCache.getInstance().updateAccountId(uuid, client.getAccountId());
|
||||||
|
|
||||||
if (playerInfo != null)
|
|
||||||
{
|
|
||||||
playerInfo.setAccountId(client.getAccountId());
|
|
||||||
PlayerCache.getInstance().addPlayer(playerInfo);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
|
@ -458,14 +445,7 @@ public class CoreClientManager extends MiniPlugin
|
||||||
|
|
||||||
if (client.getAccountId() > 0)
|
if (client.getAccountId() > 0)
|
||||||
{
|
{
|
||||||
PlayerInfo playerInfo = PlayerCache.getInstance().getPlayer(uuid);
|
PlayerCache.getInstance().updateAccountId(uuid, client.getAccountId());
|
||||||
|
|
||||||
if (playerInfo != null)
|
|
||||||
{
|
|
||||||
client.setNetworkSessionLoginTime(playerInfo.getLoginTime());
|
|
||||||
playerInfo.setAccountId(client.getAccountId());
|
|
||||||
PlayerCache.getInstance().addPlayer(playerInfo);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return !CLIENT_LOGIN_LOCKS.containsKey(client.getName());
|
return !CLIENT_LOGIN_LOCKS.containsKey(client.getName());
|
||||||
|
|
|
@ -11,8 +11,6 @@ import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.dbcp2.BasicDataSource;
|
import org.apache.commons.dbcp2.BasicDataSource;
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,7 @@ public class PetTagPage extends ShopPageBase<CosmeticManager, CosmeticShop>
|
||||||
if (getClientManager().Get(getPlayer()) != null)
|
if (getClientManager().Get(getPlayer()) != null)
|
||||||
token.AccountId = getClientManager().Get(getPlayer()).getAccountId();
|
token.AccountId = getClientManager().Get(getPlayer()).getAccountId();
|
||||||
else
|
else
|
||||||
token.AccountId = PlayerCache.getInstance().getPlayer(getPlayer().getUniqueId()).getAccountId();
|
token.AccountId = PlayerCache.getInstance().getAccountId(getPlayer().getUniqueId());
|
||||||
|
|
||||||
token.Name = getPlayer().getName();
|
token.Name = getPlayer().getName();
|
||||||
token.PetType = _petType.toString();
|
token.PetType = _petType.toString();
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package mineplex.core.inventory;
|
package mineplex.core.inventory;
|
||||||
|
|
||||||
import mineplex.cache.player.PlayerCache;
|
import mineplex.cache.player.PlayerCache;
|
||||||
import mineplex.cache.player.PlayerInfo;
|
|
||||||
import mineplex.core.MiniDbClientPlugin;
|
import mineplex.core.MiniDbClientPlugin;
|
||||||
import mineplex.core.account.CoreClientManager;
|
import mineplex.core.account.CoreClientManager;
|
||||||
import mineplex.core.common.util.Callback;
|
import mineplex.core.common.util.Callback;
|
||||||
|
@ -144,10 +143,10 @@ public class InventoryManager extends MiniDbClientPlugin<ClientInventory>
|
||||||
{
|
{
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
PlayerInfo playerInfo = PlayerCache.getInstance().getPlayer(uuid);
|
int accountId = PlayerCache.getInstance().getAccountId(uuid);
|
||||||
if (playerInfo != null)
|
if (accountId != -1)
|
||||||
{
|
{
|
||||||
addItemToInventoryForOffline(callback, playerInfo.getAccountId(), item, count);
|
addItemToInventoryForOffline(callback, accountId, item, count);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class PetReward extends UnknownPackageReward
|
||||||
if (_inventoryManager.getClientManager().Get(player) != null)
|
if (_inventoryManager.getClientManager().Get(player) != null)
|
||||||
token.AccountId = _inventoryManager.getClientManager().Get(player).getAccountId();
|
token.AccountId = _inventoryManager.getClientManager().Get(player).getAccountId();
|
||||||
else
|
else
|
||||||
token.AccountId = PlayerCache.getInstance().getPlayer(player.getUniqueId()).getAccountId();
|
token.AccountId = PlayerCache.getInstance().getAccountId(player.getUniqueId());
|
||||||
|
|
||||||
token.Name = player.getName();
|
token.Name = player.getName();
|
||||||
token.PetType = _petType.toString();
|
token.PetType = _petType.toString();
|
||||||
|
|
|
@ -4,12 +4,14 @@ import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import mineplex.cache.player.PlayerCache;
|
import mineplex.cache.player.PlayerCache;
|
||||||
import mineplex.core.MiniDbClientPlugin;
|
import mineplex.core.MiniDbClientPlugin;
|
||||||
import mineplex.core.account.CoreClientManager;
|
import mineplex.core.account.CoreClientManager;
|
||||||
import mineplex.core.common.util.Callback;
|
import mineplex.core.common.util.Callback;
|
||||||
import mineplex.core.common.util.NautHashMap;
|
import mineplex.core.common.util.NautHashMap;
|
||||||
|
import mineplex.core.common.util.UtilTasks;
|
||||||
import mineplex.core.task.repository.TaskRepository;
|
import mineplex.core.task.repository.TaskRepository;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
@ -54,7 +56,7 @@ public class TaskManager extends MiniDbClientPlugin<TaskClient>
|
||||||
return new TaskClient();
|
return new TaskClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addTaskForOfflinePlayer(final Callback<Boolean> callback, final UUID uuid, final String task)
|
public void addTaskForOfflinePlayer(Consumer<Boolean> callback, final UUID uuid, final String task)
|
||||||
{
|
{
|
||||||
Bukkit.getServer().getScheduler().runTaskAsynchronously(getPlugin(), new Runnable()
|
Bukkit.getServer().getScheduler().runTaskAsynchronously(getPlugin(), new Runnable()
|
||||||
{
|
{
|
||||||
|
@ -75,16 +77,20 @@ public class TaskManager extends MiniDbClientPlugin<TaskClient>
|
||||||
updateTasks();
|
updateTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean success = _repository.addAccountTask(PlayerCache.getInstance().getPlayer(uuid).getAccountId(), getTaskId(task));
|
int accountId = PlayerCache.getInstance().getAccountId(uuid);
|
||||||
|
|
||||||
if (callback != null)
|
if (accountId != -1)
|
||||||
{
|
{
|
||||||
Bukkit.getServer().getScheduler().runTask(getPlugin(), new Runnable()
|
UtilTasks.onMainThread(callback).accept(_repository.addAccountTask(accountId, getTaskId(task)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ClientManager.loadAccountIdFromUUID(uuid, id ->
|
||||||
{
|
{
|
||||||
public void run()
|
if (id > 0)
|
||||||
{
|
UtilTasks.onMainThread(callback).accept(_repository.addAccountTask(accountId, getTaskId(task)));
|
||||||
callback.run(success);
|
else
|
||||||
}
|
UtilTasks.onMainThread(callback).accept(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,27 +120,24 @@ public class TaskManager extends MiniDbClientPlugin<TaskClient>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addTaskForOfflinePlayer(new Callback<Boolean>()
|
addTaskForOfflinePlayer(success ->
|
||||||
{
|
{
|
||||||
public void run(Boolean success)
|
if (!success)
|
||||||
{
|
{
|
||||||
if (!success.booleanValue())
|
System.out.println("Add task FAILED for " + player.getName());
|
||||||
|
|
||||||
|
synchronized (_taskLock)
|
||||||
{
|
{
|
||||||
System.out.println("Add task FAILED for " + player.getName());
|
if (_tasks.containsKey(taskName))
|
||||||
|
|
||||||
synchronized (_taskLock)
|
|
||||||
{
|
{
|
||||||
if (_tasks.containsKey(taskName))
|
Get(player).TasksCompleted.remove(_tasks.get(taskName));
|
||||||
{
|
|
||||||
Get(player).TasksCompleted.remove(_tasks.get(taskName));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (callback != null)
|
|
||||||
{
|
if (callback != null)
|
||||||
callback.run(success);
|
{
|
||||||
}
|
callback.run(success);
|
||||||
}
|
}
|
||||||
}, player.getUniqueId(), taskName);
|
}, player.getUniqueId(), taskName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
package mineplex.serverdata.redis.atomic;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
import mineplex.serverdata.redis.RedisRepository;
|
||||||
|
import mineplex.serverdata.servers.ConnectionData;
|
||||||
|
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
import redis.clients.jedis.Response;
|
||||||
|
import redis.clients.jedis.Transaction;
|
||||||
|
import static mineplex.serverdata.Utility.currentTimeMillis;
|
||||||
|
|
||||||
|
public class RedisStringRepository extends RedisRepository
|
||||||
|
{
|
||||||
|
private final String _dataKey;
|
||||||
|
|
||||||
|
public RedisStringRepository(ConnectionData writeConn, ConnectionData readConn, Region region, String dataKey)
|
||||||
|
{
|
||||||
|
super(writeConn, readConn, region);
|
||||||
|
this._dataKey = dataKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(String key, String value)
|
||||||
|
{
|
||||||
|
try (Jedis jedis = getResource(true))
|
||||||
|
{
|
||||||
|
jedis.set(generateKey(key), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get(String key)
|
||||||
|
{
|
||||||
|
String element;
|
||||||
|
|
||||||
|
try (Jedis jedis = getResource(false))
|
||||||
|
{
|
||||||
|
element = jedis.get(generateKey(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void del(String key)
|
||||||
|
{
|
||||||
|
try (Jedis jedis = getResource(true))
|
||||||
|
{
|
||||||
|
jedis.del(generateKey(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getElementSetKey()
|
||||||
|
{
|
||||||
|
return concatenate("data", _dataKey, getRegion().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateKey(String dataId)
|
||||||
|
{
|
||||||
|
return concatenate(getElementSetKey(), dataId);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue