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 Version 1.5.01-dev
+ Added new child skill; Salvage + Added new child skill; Salvage
+ Added UUID support! + 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 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 to show the /mcstats scoreboard automatically after logging in
+ Added option to config.yml for Alchemy. Skills.Alchemy.Prevent_Hopper_Transfer_Bottles + Added option to config.yml for Alchemy. Skills.Alchemy.Prevent_Hopper_Transfer_Bottles

15
pom.xml
View File

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

View File

@ -930,7 +930,7 @@ public final class ExperienceAPI {
} }
private static PlayerProfile getOfflineProfile(UUID uuid) { private static PlayerProfile getOfflineProfile(UUID uuid) {
PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, false); PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid);
if (!profile.isLoaded()) { if (!profile.isLoaded()) {
throw new InvalidPlayerException(); throw new InvalidPlayerException();
@ -942,7 +942,7 @@ public final class ExperienceAPI {
@Deprecated @Deprecated
private static PlayerProfile getOfflineProfile(String playerName) { private static PlayerProfile getOfflineProfile(String playerName) {
UUID uuid = mcMMO.p.getServer().getOfflinePlayer(playerName).getUniqueId(); UUID uuid = mcMMO.p.getServer().getOfflinePlayer(playerName).getUniqueId();
PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, false); PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid);
if (!profile.isLoaded()) { if (!profile.isLoaded()) {
throw new InvalidPlayerException(); 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.datatypes.player.PlayerProfile;
import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.locale.LocaleLoader;
import com.gmail.nossr50.runnables.database.DatabaseConversionTask; import com.gmail.nossr50.runnables.database.DatabaseConversionTask;
import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask;
import com.gmail.nossr50.util.player.UserManager; import com.gmail.nossr50.util.player.UserManager;
public class ConvertDatabaseCommand implements CommandExecutor { public class ConvertDatabaseCommand implements CommandExecutor {
@ -55,13 +56,13 @@ public class ConvertDatabaseCommand implements CommandExecutor {
UserManager.clearAll(); UserManager.clearAll();
for (Player player : mcMMO.p.getServer().getOnlinePlayers()) { for (Player player : mcMMO.p.getServer().getOnlinePlayers()) {
PlayerProfile profile = oldDatabase.loadPlayerProfile(player.getUniqueId(), false); PlayerProfile profile = oldDatabase.loadPlayerProfile(player.getUniqueId());
if (profile.isLoaded()) { if (profile.isLoaded()) {
mcMMO.getDatabaseManager().saveUser(profile); 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); 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.datatypes.experience.FormulaType;
import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.locale.LocaleLoader;
import com.gmail.nossr50.runnables.database.FormulaConversionTask; import com.gmail.nossr50.runnables.database.FormulaConversionTask;
import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask;
import com.gmail.nossr50.util.player.UserManager; import com.gmail.nossr50.util.player.UserManager;
public class ConvertExperienceCommand implements CommandExecutor { public class ConvertExperienceCommand implements CommandExecutor {
@ -37,7 +38,7 @@ public class ConvertExperienceCommand implements CommandExecutor {
new FormulaConversionTask(sender, newType).runTaskLater(mcMMO.p, 1); new FormulaConversionTask(sender, newType).runTaskLater(mcMMO.p, 1);
for (Player player : mcMMO.p.getServer().getOnlinePlayers()) { 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; return true;

View File

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

View File

@ -73,7 +73,7 @@ public interface DatabaseManager {
/** /**
* Load a player from the database. * 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 playerName The name of the player to load from the database
* @param createNew Whether to create a new record if the player is not * @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. * Load a player from the database.
* *
* @param uuid The uuid of the player to load 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 * @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 * 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 * @return The type of database
*/ */
public DatabaseType getDatabaseType(); 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.BufferedReader;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
@ -10,6 +9,7 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; 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()); mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
} }
finally { finally {
tryClose(in); if (in != null) {
tryClose(out); 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()); mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
} }
finally { finally {
tryClose(in); if (in != null) {
tryClose(out); 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()); mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
} }
finally { finally {
tryClose(in); if (in != null) {
tryClose(out); 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; return false;
} }
finally { finally {
tryClose(in); if (in != null) {
tryClose(out); 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) { public Map<SkillType, Integer> readRank(String playerName) {
updateLeaderboards(); 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) { for (SkillType skill : SkillType.NON_CHILD_SKILLS) {
skills.put(skill, getPlayerRank(playerName, playerStatHash.get(skill))); skills.put(skill, getPlayerRank(playerName, playerStatHash.get(skill)));
@ -381,18 +437,25 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
e.printStackTrace(); e.printStackTrace();
} }
finally { finally {
tryClose(out); if (out != null) {
try {
out.close();
}
catch (IOException e) {
// Ignore
}
}
} }
} }
} }
@Deprecated @Deprecated
public PlayerProfile loadPlayerProfile(String playerName, boolean create) { public PlayerProfile loadPlayerProfile(String playerName, boolean create) {
return loadPlayerProfile(playerName, "", create); return loadPlayerProfile(playerName, "", false);
} }
public PlayerProfile loadPlayerProfile(UUID uuid, boolean create) { public PlayerProfile loadPlayerProfile(UUID uuid) {
return loadPlayerProfile("", uuid.toString(), create); return loadPlayerProfile("", uuid.toString(), false);
} }
public PlayerProfile loadPlayerProfile(String playerName, UUID uuid, boolean create) { public PlayerProfile loadPlayerProfile(String playerName, UUID uuid, boolean create) {
@ -448,7 +511,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
in.close(); in.close();
} }
catch (IOException e) { catch (IOException e) {
e.printStackTrace(); // Ignore
} }
} }
} }
@ -491,7 +554,14 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
e.printStackTrace(); e.printStackTrace();
} }
finally { 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()); mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
} }
finally { finally {
tryClose(in); if (in != null) {
tryClose(out); 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()); mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
} }
finally { finally {
tryClose(in); if (in != null) {
tryClose(out); 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(); e.printStackTrace();
} }
finally { finally {
tryClose(in); if (in != null) {
try {
in.close();
}
catch (IOException e) {
// Ignore
}
}
} }
} }
return users; 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()); mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " during user " + playerName + " (Are you sure you formatted it correctly?) " + e.toString());
} }
finally { 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()); mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
} }
finally { finally {
tryClose(in); if (in != null) {
tryClose(out); 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) { private Integer getPlayerRank(String playerName, List<PlayerStat> statsList) {
if (statsList == null) { if (statsList == null) {
return null; return null;
@ -967,8 +1081,8 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
private PlayerProfile loadFromLine(String[] character) { private PlayerProfile loadFromLine(String[] character) {
Map<SkillType, Integer> skills = getSkillMapFromLine(character); // Skill levels Map<SkillType, Integer> skills = getSkillMapFromLine(character); // Skill levels
Map<SkillType, Float> skillsXp = new HashMap<SkillType, Float>(); // Skill & XP Map<SkillType, Float> skillsXp = new EnumMap<SkillType, Float>(SkillType.class); // Skill & XP
Map<AbilityType, Integer> skillsDATS = new HashMap<AbilityType, Integer>(); // Ability & Cooldown Map<AbilityType, Integer> skillsDATS = new EnumMap<AbilityType, Integer>(AbilityType.class); // Ability & Cooldown
MobHealthbarType mobHealthbarType; MobHealthbarType mobHealthbarType;
// TODO on updates, put new values in a try{} ? // 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) { 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.TAMING, Integer.valueOf(character[24]));
skills.put(SkillType.MINING, Integer.valueOf(character[1])); skills.put(SkillType.MINING, Integer.valueOf(character[1]));
@ -1041,4 +1155,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
public DatabaseType getDatabaseType() { public DatabaseType getDatabaseType() {
return DatabaseType.FLATFILE; return DatabaseType.FLATFILE;
} }
@Override
public void onDisable() { }
} }

View File

@ -7,12 +7,10 @@ import java.util.UUID;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.Sound; import org.bukkit.Sound;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.scheduler.BukkitRunnable;
import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.config.AdvancedConfig; import com.gmail.nossr50.config.AdvancedConfig;
@ -93,13 +91,13 @@ public class McMMOPlayer {
private boolean isUsingUnarmed; private boolean isUsingUnarmed;
private final FixedMetadataValue playerMetadata; private final FixedMetadataValue playerMetadata;
public McMMOPlayer(Player player) { public McMMOPlayer(Player player, PlayerProfile profile) {
String playerName = player.getName(); String playerName = player.getName();
UUID uuid = player.getUniqueId(); UUID uuid = player.getUniqueId();
this.player = player; this.player = player;
playerMetadata = new FixedMetadataValue(mcMMO.p, playerName); playerMetadata = new FixedMetadataValue(mcMMO.p, playerName);
profile = mcMMO.getDatabaseManager().loadPlayerProfile(playerName, uuid, true); this.profile = profile;
party = PartyManager.getPlayerParty(playerName); party = PartyManager.getPlayerParty(playerName);
ptpRecord = new PartyTeleportRecord(); ptpRecord = new PartyTeleportRecord();
@ -130,70 +128,6 @@ public class McMMOPlayer {
for (ToolType toolType : ToolType.values()) { for (ToolType toolType : ToolType.values()) {
toolMode.put(toolType, false); 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() { 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.datatypes.skills.SkillType;
import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.locale.LocaleLoader;
import com.gmail.nossr50.party.ShareHandler; 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.runnables.skills.BleedTimerTask;
import com.gmail.nossr50.skills.fishing.FishingManager; import com.gmail.nossr50.skills.fishing.FishingManager;
import com.gmail.nossr50.skills.herbalism.HerbalismManager; import com.gmail.nossr50.skills.herbalism.HerbalismManager;
@ -387,9 +387,7 @@ public class PlayerListener implements Listener {
return; return;
} }
McMMOPlayer mcMMOPlayer = 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
mcMMOPlayer.actualizeRespawnATS();
ScoreboardManager.setupPlayer(player);
if (Config.getInstance().getMOTDEnabled() && Permissions.motd(player)) { if (Config.getInstance().getMOTDEnabled() && Permissions.motd(player)) {
Motd.displayAll(player); Motd.displayAll(player);
@ -403,11 +401,6 @@ public class PlayerListener implements Listener {
player.sendMessage(LocaleLoader.getString("UpdateChecker.Outdated")); player.sendMessage(LocaleLoader.getString("UpdateChecker.Outdated"));
player.sendMessage(LocaleLoader.getString("UpdateChecker.NewAvailable")); 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.backups.CleanBackupsTask;
import com.gmail.nossr50.runnables.database.UserPurgeTask; import com.gmail.nossr50.runnables.database.UserPurgeTask;
import com.gmail.nossr50.runnables.party.PartyAutoKickTask; 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.player.PowerLevelUpdatingTask;
import com.gmail.nossr50.runnables.skills.BleedTimerTask; import com.gmail.nossr50.runnables.skills.BleedTimerTask;
import com.gmail.nossr50.skills.alchemy.Alchemy; import com.gmail.nossr50.skills.alchemy.Alchemy;
@ -167,8 +168,7 @@ public class mcMMO extends JavaPlugin {
holidayManager = new HolidayManager(); holidayManager = new HolidayManager();
for (Player player : getServer().getOnlinePlayers()) { for (Player player : getServer().getOnlinePlayers()) {
UserManager.addUser(player); // In case of reload add all users back into UserManager new PlayerProfileLoadingTask(player).runTaskTimerAsynchronously(mcMMO.p, 1, 20); // 1 Tick delay to ensure the player is marked as online before we begin loading
ScoreboardManager.setupPlayer(player);
} }
debug("Version " + getDescription().getVersion() + " is enabled!"); debug("Version " + getDescription().getVersion() + " is enabled!");
@ -242,6 +242,7 @@ public class mcMMO extends JavaPlugin {
} }
} }
databaseManager.onDisable();
debug("Was disabled."); // How informative! 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.mcMMO;
import com.gmail.nossr50.events.items.McMMOItemSpawnEvent; import com.gmail.nossr50.events.items.McMMOItemSpawnEvent;
import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask;
import com.gmail.nossr50.util.player.UserManager; import com.gmail.nossr50.util.player.UserManager;
public final class Misc { public final class Misc {
@ -112,7 +113,7 @@ public final class Misc {
if (player != null) { if (player != null) {
UserManager.remove(player); 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() {} private UserManager() {}
/** /**
* Add a new user. * Track a new user.
* *
* @param player The player to create a user record for * @param mcMMOPlayer the player profile to start tracking
* @return the player's {@link McMMOPlayer} object
*/ */
public static McMMOPlayer addUser(Player player) { public static void track(McMMOPlayer mcMMOPlayer) {
McMMOPlayer mcMMOPlayer = new McMMOPlayer(player); mcMMOPlayer.getPlayer().setMetadata(mcMMO.playerDataKey, new FixedMetadataValue(mcMMO.p, mcMMOPlayer));
player.setMetadata(mcMMO.playerDataKey, new FixedMetadataValue(mcMMO.p, mcMMOPlayer));
return mcMMOPlayer;
} }
/** /**

View File

@ -10,6 +10,8 @@
General: General:
Locale: en_US Locale: en_US
MOTD_Enabled: true 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 # Amount of time (in minutes) to wait between saves of player information
Save_Interval: 10 Save_Interval: 10
# Allow mcMMO to report on basic anonymous usage # Allow mcMMO to report on basic anonymous usage
@ -122,6 +124,12 @@ MySQL:
User_Password: UserPassword User_Password: UserPassword
Name: DataBaseName Name: DataBaseName
TablePrefix: mcmmo_ 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: Server:
Port: 3306 Port: 3306
Address: localhost Address: localhost

View File

@ -960,7 +960,6 @@ Scoreboard.Misc.Cooldown=[[LIGHT_PURPLE]]Cooldown
Scoreboard.Misc.Overall=[[GOLD]]Overall Scoreboard.Misc.Overall=[[GOLD]]Overall
#DATABASE RECOVERY #DATABASE RECOVERY
Recovery.Notice=[[RED]]Notice: mcMMO was [[DARK_RED]]unable to load your data.[[RED]] Retrying 5 times... Profile.Loading.Success=[[GREEN]]Your mcMMO profile has been loaded.
Recovery.Success=[[GREEN]]Success! Your mcMMO data was 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]].
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]]. 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.
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.