SQLDatabaseManager optimizations, async profile loading -t00thpick1, zreed

This commit changes our shared connection into a connection pool utility to prevent
thread locks from multiple actions attempting to access the database at the same time.
In additon,  profile loading has been moved off the main thread at login time, to
allieviate the performance issues caused by it.

Fixes #2138, Fixes #2119, Fixes #1982, Fixes #1953
This commit is contained in:
t00thpick1 2014-08-01 13:35:36 -04:00 committed by TfT_02
parent c10525ada9
commit 857e12b96e
20 changed files with 1227 additions and 1161 deletions

View File

@ -10,6 +10,7 @@ Key:
Version 1.5.01-dev
+ Added new child skill; Salvage
+ Added UUID support!
+ Added SQL connection pooling and async loading!
+ Added new feature to Herbalism. Instantly-regrown crops are protected from being broken for 1 second
+ Added option to config.yml to show the /mcstats scoreboard automatically after logging in
+ Added option to config.yml for Alchemy. Skills.Alchemy.Prevent_Hopper_Transfer_Bottles

15
pom.xml
View File

@ -76,6 +76,8 @@
<artifactSet>
<includes>
<include>com.turt2live.metrics:MetricsExtension</include>
<include>commons-logging:commons-logging</include>
<include>net.snaq:dbpool</include>
</includes>
</artifactSet>
<relocations>
@ -83,6 +85,14 @@
<pattern>com.turt2live.metrics</pattern>
<shadedPattern>com.gmail.nossr50.metrics.mcstats</shadedPattern>
</relocation>
<relocation>
<pattern>org.apache.commons.logging</pattern>
<shadedPattern>com.gmail.nossr50.commons.logging</shadedPattern>
</relocation>
<relocation>
<pattern>net.snaq</pattern>
<shadedPattern>com.gmail.nossr50.dbpool</shadedPattern>
</relocation>
</relocations>
</configuration>
<executions>
@ -136,6 +146,11 @@
<artifactId>MetricsExtension</artifactId>
<version>0.0.5-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.snaq</groupId>
<artifactId>dbpool</artifactId>
<version>5.1</version>
</dependency>
</dependencies>
<distributionManagement>
<repository>

View File

@ -930,7 +930,7 @@ public final class ExperienceAPI {
}
private static PlayerProfile getOfflineProfile(UUID uuid) {
PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, false);
PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid);
if (!profile.isLoaded()) {
throw new InvalidPlayerException();
@ -942,7 +942,7 @@ public final class ExperienceAPI {
@Deprecated
private static PlayerProfile getOfflineProfile(String playerName) {
UUID uuid = mcMMO.p.getServer().getOfflinePlayer(playerName).getUniqueId();
PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, false);
PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid);
if (!profile.isLoaded()) {
throw new InvalidPlayerException();

View File

@ -12,6 +12,7 @@ import com.gmail.nossr50.datatypes.database.DatabaseType;
import com.gmail.nossr50.datatypes.player.PlayerProfile;
import com.gmail.nossr50.locale.LocaleLoader;
import com.gmail.nossr50.runnables.database.DatabaseConversionTask;
import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask;
import com.gmail.nossr50.util.player.UserManager;
public class ConvertDatabaseCommand implements CommandExecutor {
@ -55,13 +56,13 @@ public class ConvertDatabaseCommand implements CommandExecutor {
UserManager.clearAll();
for (Player player : mcMMO.p.getServer().getOnlinePlayers()) {
PlayerProfile profile = oldDatabase.loadPlayerProfile(player.getUniqueId(), false);
PlayerProfile profile = oldDatabase.loadPlayerProfile(player.getUniqueId());
if (profile.isLoaded()) {
mcMMO.getDatabaseManager().saveUser(profile);
}
UserManager.addUser(player);
new PlayerProfileLoadingTask(player).runTaskTimerAsynchronously(mcMMO.p, 1, 20); // 1 Tick delay to ensure the player is marked as online before we begin loading
}
new DatabaseConversionTask(oldDatabase, sender, previousType.toString(), newType.toString()).runTaskAsynchronously(mcMMO.p);

View File

@ -9,6 +9,7 @@ import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.datatypes.experience.FormulaType;
import com.gmail.nossr50.locale.LocaleLoader;
import com.gmail.nossr50.runnables.database.FormulaConversionTask;
import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask;
import com.gmail.nossr50.util.player.UserManager;
public class ConvertExperienceCommand implements CommandExecutor {
@ -37,7 +38,7 @@ public class ConvertExperienceCommand implements CommandExecutor {
new FormulaConversionTask(sender, newType).runTaskLater(mcMMO.p, 1);
for (Player player : mcMMO.p.getServer().getOnlinePlayers()) {
UserManager.addUser(player);
new PlayerProfileLoadingTask(player).runTaskTimerAsynchronously(mcMMO.p, 1, 20); // 1 Tick delay to ensure the player is marked as online before we begin loading
}
return true;

View File

@ -232,6 +232,7 @@ public class Config extends AutoUpdateConfigLoader {
/* General Settings */
public String getLocale() { return config.getString("General.Locale", "en_us"); }
public boolean getMOTDEnabled() { return config.getBoolean("General.MOTD_Enabled", true); }
public boolean getShowProfileLoadedMessage() { return config.getBoolean("General.Show_Profile_Loaded", true); }
public boolean getDonateMessageEnabled() { return config.getBoolean("Commands.mcmmo.Donate_Message", true); }
public int getSaveInterval() { return config.getInt("General.Save_Interval", 10); }
public boolean getStatsTrackingEnabled() { return config.getBoolean("General.Stats_Tracking", true); }
@ -313,6 +314,8 @@ public class Config extends AutoUpdateConfigLoader {
public int getMySQLServerPort() { return config.getInt("MySQL.Server.Port", 3306); }
public String getMySQLServerName() { return config.getString("MySQL.Server.Address", "localhost"); }
public String getMySQLUserPassword() { return getStringIncludingInts("MySQL.Database.User_Password"); }
public int getMySQLMaxConnections() { return config.getInt("MySQL.Database.MaxConnections"); }
public int getMySQLMaxPoolSize() { return config.getInt("MySQL.Database.MaxPoolSize"); }
private String getStringIncludingInts(String key) {
String str = config.getString(key);

View File

@ -73,7 +73,7 @@ public interface DatabaseManager {
/**
* Load a player from the database.
*
* @deprecated replaced by {@link #loadPlayerProfile(UUID uuid, boolean createNew)}
* @deprecated replaced by {@link #loadPlayerProfile(String playerName, UUID uuid, boolean createNew)}
*
* @param playerName The name of the player to load from the database
* @param createNew Whether to create a new record if the player is not
@ -88,12 +88,9 @@ public interface DatabaseManager {
* Load a player from the database.
*
* @param uuid The uuid of the player to load from the database
* @param createNew Whether to create a new record if the player is not
* found
* @return The player's data, or an unloaded PlayerProfile if not found
* and createNew is false
*/
public PlayerProfile loadPlayerProfile(UUID uuid, boolean createNew);
public PlayerProfile loadPlayerProfile(UUID uuid);
/**
* Load a player from the database. Attempt to use uuid, fall back on playername
@ -132,4 +129,9 @@ public interface DatabaseManager {
* @return The type of database
*/
public DatabaseType getDatabaseType();
/**
* Called when the plugin disables
*/
public void onDisable();
}

View File

@ -2,7 +2,6 @@ package com.gmail.nossr50.database;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
@ -10,6 +9,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -98,8 +98,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
}
finally {
tryClose(in);
tryClose(out);
if (in != null) {
try {
in.close();
}
catch (IOException e) {
// Ignore
}
}
if (out != null) {
try {
out.close();
}
catch (IOException e) {
// Ignore
}
}
}
}
@ -164,8 +178,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
}
finally {
tryClose(in);
tryClose(out);
if (in != null) {
try {
in.close();
}
catch (IOException e) {
// Ignore
}
}
if (out != null) {
try {
out.close();
}
catch (IOException e) {
// Ignore
}
}
}
}
@ -203,8 +231,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
}
finally {
tryClose(in);
tryClose(out);
if (in != null) {
try {
in.close();
}
catch (IOException e) {
// Ignore
}
}
if (out != null) {
try {
out.close();
}
catch (IOException e) {
// Ignore
}
}
}
}
@ -294,8 +336,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
return false;
}
finally {
tryClose(in);
tryClose(out);
if (in != null) {
try {
in.close();
}
catch (IOException e) {
// Ignore
}
}
if (out != null) {
try {
out.close();
}
catch (IOException e) {
// Ignore
}
}
}
}
}
@ -311,7 +367,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
public Map<SkillType, Integer> readRank(String playerName) {
updateLeaderboards();
Map<SkillType, Integer> skills = new HashMap<SkillType, Integer>();
Map<SkillType, Integer> skills = new EnumMap<SkillType, Integer>(SkillType.class);
for (SkillType skill : SkillType.NON_CHILD_SKILLS) {
skills.put(skill, getPlayerRank(playerName, playerStatHash.get(skill)));
@ -381,18 +437,25 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
e.printStackTrace();
}
finally {
tryClose(out);
if (out != null) {
try {
out.close();
}
catch (IOException e) {
// Ignore
}
}
}
}
}
@Deprecated
public PlayerProfile loadPlayerProfile(String playerName, boolean create) {
return loadPlayerProfile(playerName, "", create);
return loadPlayerProfile(playerName, "", false);
}
public PlayerProfile loadPlayerProfile(UUID uuid, boolean create) {
return loadPlayerProfile("", uuid.toString(), create);
public PlayerProfile loadPlayerProfile(UUID uuid) {
return loadPlayerProfile("", uuid.toString(), false);
}
public PlayerProfile loadPlayerProfile(String playerName, UUID uuid, boolean create) {
@ -448,7 +511,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
in.close();
}
catch (IOException e) {
e.printStackTrace();
// Ignore
}
}
}
@ -491,7 +554,14 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
e.printStackTrace();
}
finally {
tryClose(in);
if (in != null) {
try {
in.close();
}
catch (IOException e) {
// Ignore
}
}
}
}
}
@ -532,8 +602,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
}
finally {
tryClose(in);
tryClose(out);
if (in != null) {
try {
in.close();
}
catch (IOException e) {
// Ignore
}
}
if (out != null) {
try {
out.close();
}
catch (IOException e) {
// Ignore
}
}
}
}
@ -573,8 +657,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
}
finally {
tryClose(in);
tryClose(out);
if (in != null) {
try {
in.close();
}
catch (IOException e) {
// Ignore
}
}
if (out != null) {
try {
out.close();
}
catch (IOException e) {
// Ignore
}
}
}
}
@ -601,7 +699,14 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
e.printStackTrace();
}
finally {
tryClose(in);
if (in != null) {
try {
in.close();
}
catch (IOException e) {
// Ignore
}
}
}
}
return users;
@ -671,7 +776,14 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " during user " + playerName + " (Are you sure you formatted it correctly?) " + e.toString());
}
finally {
tryClose(in);
if (in != null) {
try {
in.close();
}
catch (IOException e) {
// Ignore
}
}
}
}
@ -897,8 +1009,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
}
finally {
tryClose(in);
tryClose(out);
if (in != null) {
try {
in.close();
}
catch (IOException e) {
// Ignore
}
}
if (out != null) {
try {
out.close();
}
catch (IOException e) {
// Ignore
}
}
}
}
@ -923,18 +1049,6 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
}
}
private void tryClose(Closeable c) {
if (c == null) {
return;
}
try {
c.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
private Integer getPlayerRank(String playerName, List<PlayerStat> statsList) {
if (statsList == null) {
return null;
@ -967,8 +1081,8 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
private PlayerProfile loadFromLine(String[] character) {
Map<SkillType, Integer> skills = getSkillMapFromLine(character); // Skill levels
Map<SkillType, Float> skillsXp = new HashMap<SkillType, Float>(); // Skill & XP
Map<AbilityType, Integer> skillsDATS = new HashMap<AbilityType, Integer>(); // Ability & Cooldown
Map<SkillType, Float> skillsXp = new EnumMap<SkillType, Float>(SkillType.class); // Skill & XP
Map<AbilityType, Integer> skillsDATS = new EnumMap<AbilityType, Integer>(AbilityType.class); // Ability & Cooldown
MobHealthbarType mobHealthbarType;
// TODO on updates, put new values in a try{} ?
@ -1019,7 +1133,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
}
private Map<SkillType, Integer> getSkillMapFromLine(String[] character) {
Map<SkillType, Integer> skills = new HashMap<SkillType, Integer>(); // Skill & Level
Map<SkillType, Integer> skills = new EnumMap<SkillType, Integer>(SkillType.class); // Skill & Level
skills.put(SkillType.TAMING, Integer.valueOf(character[24]));
skills.put(SkillType.MINING, Integer.valueOf(character[1]));
@ -1041,4 +1155,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
public DatabaseType getDatabaseType() {
return DatabaseType.FLATFILE;
}
@Override
public void onDisable() { }
}

View File

@ -7,12 +7,10 @@ import java.util.UUID;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.scheduler.BukkitRunnable;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.config.AdvancedConfig;
@ -93,13 +91,13 @@ public class McMMOPlayer {
private boolean isUsingUnarmed;
private final FixedMetadataValue playerMetadata;
public McMMOPlayer(Player player) {
public McMMOPlayer(Player player, PlayerProfile profile) {
String playerName = player.getName();
UUID uuid = player.getUniqueId();
this.player = player;
playerMetadata = new FixedMetadataValue(mcMMO.p, playerName);
profile = mcMMO.getDatabaseManager().loadPlayerProfile(playerName, uuid, true);
this.profile = profile;
party = PartyManager.getPlayerParty(playerName);
ptpRecord = new PartyTeleportRecord();
@ -130,70 +128,6 @@ public class McMMOPlayer {
for (ToolType toolType : ToolType.values()) {
toolMode.put(toolType, false);
}
if (!profile.isLoaded()) {
mcMMO.p.getLogger().warning("Unable to load the PlayerProfile for " + playerName + ". Will retry over the next several seconds.");
new RetryProfileLoadingTask().runTaskTimerAsynchronously(mcMMO.p, 11L, 31L);
}
}
private class RetryProfileLoadingTask extends BukkitRunnable {
private static final int MAX_TRIES = 5;
private final String playerName = McMMOPlayer.this.player.getName();
private final UUID uniqueId = McMMOPlayer.this.player.getUniqueId();
private int attempt = 0;
// WARNING: ASYNC TASK
// DO NOT MODIFY THE McMMOPLAYER FROM THIS CODE
@Override
public void run() {
// Quit if they logged out
if (!player.isOnline()) {
mcMMO.p.getLogger().info("Aborting profile loading recovery for " + playerName + " - player logged out");
this.cancel();
return;
}
// Send the message that we're doing the recovery
if (attempt == 0) {
player.sendMessage(LocaleLoader.getString("Recovery.Notice"));
}
// Increment attempt counter and try
attempt++;
PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uniqueId, true);
// If successful, schedule the apply
if (profile.isLoaded()) {
new ApplySuccessfulProfile(profile).runTask(mcMMO.p);
player.sendMessage(LocaleLoader.getString("Recovery.Success"));
this.cancel();
return;
}
// If we've failed five times, give up
if (attempt >= MAX_TRIES) {
mcMMO.p.getLogger().severe("Giving up on attempting to load the PlayerProfile for " + playerName);
mcMMO.p.getServer().broadcast(LocaleLoader.getString("Recovery.AdminFailureNotice", playerName), Server.BROADCAST_CHANNEL_ADMINISTRATIVE);
player.sendMessage(LocaleLoader.getString("Recovery.Failure").split("\n"));
this.cancel();
return;
}
}
}
private class ApplySuccessfulProfile extends BukkitRunnable {
private final PlayerProfile profile;
private ApplySuccessfulProfile(PlayerProfile profile) {
this.profile = profile;
}
// Synchronized task
// No database access permitted
@Override
public void run() {
McMMOPlayer.this.profile = profile;
}
}
public AcrobaticsManager getAcrobaticsManager() {

View File

@ -41,7 +41,7 @@ import com.gmail.nossr50.datatypes.skills.AbilityType;
import com.gmail.nossr50.datatypes.skills.SkillType;
import com.gmail.nossr50.locale.LocaleLoader;
import com.gmail.nossr50.party.ShareHandler;
import com.gmail.nossr50.runnables.commands.McScoreboardKeepTask;
import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask;
import com.gmail.nossr50.runnables.skills.BleedTimerTask;
import com.gmail.nossr50.skills.fishing.FishingManager;
import com.gmail.nossr50.skills.herbalism.HerbalismManager;
@ -387,9 +387,7 @@ public class PlayerListener implements Listener {
return;
}
McMMOPlayer mcMMOPlayer = UserManager.addUser(player);
mcMMOPlayer.actualizeRespawnATS();
ScoreboardManager.setupPlayer(player);
new PlayerProfileLoadingTask(player).runTaskTimerAsynchronously(mcMMO.p, 1, 20); // 1 Tick delay to ensure the player is marked as online before we begin loading
if (Config.getInstance().getMOTDEnabled() && Permissions.motd(player)) {
Motd.displayAll(player);
@ -403,11 +401,6 @@ public class PlayerListener implements Listener {
player.sendMessage(LocaleLoader.getString("UpdateChecker.Outdated"));
player.sendMessage(LocaleLoader.getString("UpdateChecker.NewAvailable"));
}
if (Config.getInstance().getShowStatsAfterLogin()) {
ScoreboardManager.enablePlayerStatsScoreboard(player);
new McScoreboardKeepTask(player).runTaskLater(mcMMO.p, 1 * Misc.TICK_CONVERSION_FACTOR);
}
}
/**

View File

@ -38,6 +38,7 @@ import com.gmail.nossr50.runnables.UpdaterResultAsyncTask;
import com.gmail.nossr50.runnables.backups.CleanBackupsTask;
import com.gmail.nossr50.runnables.database.UserPurgeTask;
import com.gmail.nossr50.runnables.party.PartyAutoKickTask;
import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask;
import com.gmail.nossr50.runnables.player.PowerLevelUpdatingTask;
import com.gmail.nossr50.runnables.skills.BleedTimerTask;
import com.gmail.nossr50.skills.alchemy.Alchemy;
@ -167,8 +168,7 @@ public class mcMMO extends JavaPlugin {
holidayManager = new HolidayManager();
for (Player player : getServer().getOnlinePlayers()) {
UserManager.addUser(player); // In case of reload add all users back into UserManager
ScoreboardManager.setupPlayer(player);
new PlayerProfileLoadingTask(player).runTaskTimerAsynchronously(mcMMO.p, 1, 20); // 1 Tick delay to ensure the player is marked as online before we begin loading
}
debug("Version " + getDescription().getVersion() + " is enabled!");
@ -242,6 +242,7 @@ public class mcMMO extends JavaPlugin {
}
}
databaseManager.onDisable();
debug("Was disabled."); // How informative!
}

View File

@ -1,39 +0,0 @@
package com.gmail.nossr50.runnables.database;
import java.lang.ref.WeakReference;
import org.bukkit.scheduler.BukkitRunnable;
import com.gmail.nossr50.database.SQLDatabaseManager;
/**
* This task is in charge of sending a MySQL ping over the MySQL connection
* every hour to prevent the connection from timing out and losing players'
* data when they join.
* <p/>
* A WeakReference is used to keep the database instance, because
* {@link com.gmail.nossr50.commands.database.ConvertDatabaseCommand database
* conversion} may create a SQLDatabaseManager that will be thrown out. If a
* normal reference was used, the conversion would cause a combined data and
* resource leak through this task.
*/
public class SQLDatabaseKeepaliveTask extends BukkitRunnable {
WeakReference<SQLDatabaseManager> databaseInstance;
public SQLDatabaseKeepaliveTask(SQLDatabaseManager dbman) {
databaseInstance = new WeakReference<SQLDatabaseManager>(dbman);
}
public void run() {
SQLDatabaseManager dbman = databaseInstance.get();
if (dbman != null) {
dbman.checkConnected();
}
else {
// This happens when the database was started for a conversion,
// or discarded by its creator for any other reason. If this code
// was not present, we would leak the connection resources.
this.cancel();
}
}
}

View File

@ -1,22 +0,0 @@
package com.gmail.nossr50.runnables.database;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.database.SQLDatabaseManager;
import com.gmail.nossr50.util.player.UserManager;
public class SQLReconnectTask extends BukkitRunnable {
@Override
public void run() {
if (((SQLDatabaseManager) mcMMO.getDatabaseManager()).checkConnected()) {
UserManager.saveAll(); // Save all profiles
UserManager.clearAll(); // Clear the profiles
for (Player player : mcMMO.p.getServer().getOnlinePlayers()) {
UserManager.addUser(player); // Add in new profiles, forcing them to 'load' again from MySQL
}
}
}
}

View File

@ -1,51 +0,0 @@
package com.gmail.nossr50.runnables.database;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import org.bukkit.scheduler.BukkitRunnable;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.uuid.UUIDFetcher;
public class UUIDFetcherRunnable extends BukkitRunnable {
private List<String> names;
public UUIDFetcherRunnable(List<String> names) {
this.names = names;
}
public UUIDFetcherRunnable(String name) {
this.names = new ArrayList<String>();
this.names.add(name);
}
@Override
public void run() {
try {
Map<String, UUID> returns = new UUIDFetcher(this.names).call();
new CacheReturnedNames(returns).runTask(mcMMO.p);
}
catch (Exception e) {
e.printStackTrace();
}
}
private class CacheReturnedNames extends BukkitRunnable {
private Map<String, UUID> returns;
public CacheReturnedNames(Map<String, UUID> returns) {
this.returns = returns;
}
@Override
public void run() {
for (Entry<String, UUID> entry : this.returns.entrySet()) {
mcMMO.getDatabaseManager().saveUserUUID(entry.getKey(), entry.getValue());
}
}
}
}

View File

@ -0,0 +1,108 @@
package com.gmail.nossr50.runnables.player;
import java.util.concurrent.locks.ReentrantLock;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.config.Config;
import com.gmail.nossr50.datatypes.player.McMMOPlayer;
import com.gmail.nossr50.datatypes.player.PlayerProfile;
import com.gmail.nossr50.locale.LocaleLoader;
import com.gmail.nossr50.runnables.commands.McScoreboardKeepTask;
import com.gmail.nossr50.util.Misc;
import com.gmail.nossr50.util.player.UserManager;
import com.gmail.nossr50.util.scoreboards.ScoreboardManager;
public class PlayerProfileLoadingTask extends BukkitRunnable {
private static final int MAX_TRIES = 5;
private final Player player;
private int attempt = 0;
private ReentrantLock lock = new ReentrantLock();
private boolean cancelled = false;
public PlayerProfileLoadingTask(Player player) {
this.player = player;
}
// WARNING: ASYNC TASK
// DO NOT MODIFY THE McMMOPLAYER FROM THIS CODE
@Override
public void run() {
lock.lock();
if (this.cancelled) {
return;
}
// Quit if they logged out
if (!player.isOnline()) {
mcMMO.p.getLogger().info("Aborting profile loading recovery for " + player.getName() + " - player logged out");
this.cancel();
cancelled = true;
lock.unlock();
return;
}
// Increment attempt counter and try
attempt++;
PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(player.getName(), player.getUniqueId(), true);
// If successful, schedule the apply
if (profile.isLoaded()) {
new ApplySuccessfulProfile(profile).runTask(mcMMO.p);
this.cancel();
cancelled = true;
lock.unlock();
return;
}
// If we've failed five times, give up
if (attempt >= MAX_TRIES) {
mcMMO.p.getLogger().severe("Giving up on attempting to load the PlayerProfile for " + player.getName());
mcMMO.p.getServer().broadcast(LocaleLoader.getString("Profile.Loading.AdminFailureNotice", player.getName()), Server.BROADCAST_CHANNEL_ADMINISTRATIVE);
player.sendMessage(LocaleLoader.getString("Profile.Loading.Failure").split("\n"));
this.cancel();
cancelled = true;
lock.unlock();
return;
}
lock.unlock();
}
private class ApplySuccessfulProfile extends BukkitRunnable {
private final PlayerProfile profile;
private ApplySuccessfulProfile(PlayerProfile profile) {
this.profile = profile;
}
// Synchronized task
// No database access permitted
@Override
public void run() {
if (!player.isOnline()) {
mcMMO.p.getLogger().info("Aborting profile loading recovery for " + player.getName() + " - player logged out");
return;
}
McMMOPlayer mcMMOPlayer = new McMMOPlayer(player, profile);
UserManager.track(mcMMOPlayer);
mcMMOPlayer.actualizeRespawnATS();
ScoreboardManager.setupPlayer(player);
if (Config.getInstance().getShowProfileLoadedMessage()) {
player.sendMessage(LocaleLoader.getString("Profile.Loading.Success"));
}
if (Config.getInstance().getShowStatsAfterLogin()) {
ScoreboardManager.enablePlayerStatsScoreboard(player);
new McScoreboardKeepTask(player).runTaskLater(mcMMO.p, 1 * Misc.TICK_CONVERSION_FACTOR);
}
}
}
}

View File

@ -13,6 +13,7 @@ import org.bukkit.inventory.ItemStack;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.events.items.McMMOItemSpawnEvent;
import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask;
import com.gmail.nossr50.util.player.UserManager;
public final class Misc {
@ -112,7 +113,7 @@ public final class Misc {
if (player != null) {
UserManager.remove(player);
UserManager.addUser(player);
new PlayerProfileLoadingTask(player).runTaskTimerAsynchronously(mcMMO.p, 1, 20); // 1 Tick delay to ensure the player is marked as online before we begin loading
}
}

View File

@ -18,16 +18,12 @@ public final class UserManager {
private UserManager() {}
/**
* Add a new user.
* Track a new user.
*
* @param player The player to create a user record for
* @return the player's {@link McMMOPlayer} object
* @param mcMMOPlayer the player profile to start tracking
*/
public static McMMOPlayer addUser(Player player) {
McMMOPlayer mcMMOPlayer = new McMMOPlayer(player);
player.setMetadata(mcMMO.playerDataKey, new FixedMetadataValue(mcMMO.p, mcMMOPlayer));
return mcMMOPlayer;
public static void track(McMMOPlayer mcMMOPlayer) {
mcMMOPlayer.getPlayer().setMetadata(mcMMO.playerDataKey, new FixedMetadataValue(mcMMO.p, mcMMOPlayer));
}
/**

View File

@ -10,6 +10,8 @@
General:
Locale: en_US
MOTD_Enabled: true
# Send a message to the player when his profile was successfully loaded
Show_Profile_Loaded: false
# Amount of time (in minutes) to wait between saves of player information
Save_Interval: 10
# Allow mcMMO to report on basic anonymous usage
@ -122,6 +124,12 @@ MySQL:
User_Password: UserPassword
Name: DataBaseName
TablePrefix: mcmmo_
# This setting is the max simultaneous mysql connections allowed at a time, needs to be
# high enough to support multiple player logins in quick succession
MaxConnections: 30
# This setting is the max size of the pool of cached connections that we hold available
# at any given time
MaxPoolSize: 20
Server:
Port: 3306
Address: localhost

View File

@ -960,7 +960,6 @@ Scoreboard.Misc.Cooldown=[[LIGHT_PURPLE]]Cooldown
Scoreboard.Misc.Overall=[[GOLD]]Overall
#DATABASE RECOVERY
Recovery.Notice=[[RED]]Notice: mcMMO was [[DARK_RED]]unable to load your data.[[RED]] Retrying 5 times...
Recovery.Success=[[GREEN]]Success! Your mcMMO data was loaded.
Recovery.Failure=[[RED]]mcMMO still cannot load your data. You may want to [[AQUA]]contact the server owner.\n[[YELLOW]]You can still play on the server, but you will have [[BOLD]]no mcMMO levels[[YELLOW]] and any XP you get [[BOLD]]will not be saved[[YELLOW]].
Recovery.AdminFailureNotice=[[DARK_RED]][A][[RED]] mcMMO was unable to load the player data for [[YELLOW]]{0}[[RED]]. [[LIGHT_PURPLE]]Please inspect your database setup.
Profile.Loading.Success=[[GREEN]]Your mcMMO profile has been loaded.
Profile.Loading.Failure=[[RED]]mcMMO still cannot load your data. You may want to [[AQUA]]contact the server owner.\n[[YELLOW]]You can still play on the server, but you will have [[BOLD]]no mcMMO levels[[YELLOW]] and any XP you get [[BOLD]]will not be saved[[YELLOW]].
Profile.Loading.AdminFailureNotice=[[DARK_RED]][A][[RED]] mcMMO was unable to load the player data for [[YELLOW]]{0}[[RED]]. [[LIGHT_PURPLE]]Please inspect your database setup.