Port thread safety/performance optimizations from #1992

This commit is contained in:
Gabriele C
2020-06-26 22:23:50 +02:00
parent 5544fd047d
commit 06be8ea6f4
13 changed files with 146 additions and 91 deletions
@@ -32,40 +32,34 @@ public class AccountsCommand implements ExecutableCommand {
// Assumption: a player name cannot contain '.'
if (playerName.contains(".")) {
bukkitService.runTaskAsynchronously(new Runnable() {
@Override
public void run() {
List<String> accountList = dataSource.getAllAuthsByIp(playerName);
if (accountList.isEmpty()) {
sender.sendMessage("[AuthMe] This IP does not exist in the database.");
} else if (accountList.size() == 1) {
sender.sendMessage("[AuthMe] " + playerName + " is a single account player");
} else {
outputAccountsList(sender, playerName, accountList);
}
bukkitService.runTaskAsynchronously(() -> {
List<String> accountList = dataSource.getAllAuthsByIp(playerName);
if (accountList.isEmpty()) {
sender.sendMessage("[AuthMe] This IP does not exist in the database.");
} else if (accountList.size() == 1) {
sender.sendMessage("[AuthMe] " + playerName + " is a single account player");
} else {
outputAccountsList(sender, playerName, accountList);
}
});
} else {
bukkitService.runTaskAsynchronously(new Runnable() {
@Override
public void run() {
PlayerAuth auth = dataSource.getAuth(playerName.toLowerCase());
if (auth == null) {
commonService.send(sender, MessageKey.UNKNOWN_USER);
return;
} else if (auth.getLastIp() == null) {
sender.sendMessage("No known last IP address for player");
return;
}
bukkitService.runTaskAsynchronously(() -> {
PlayerAuth auth = dataSource.getAuth(playerName.toLowerCase());
if (auth == null) {
commonService.send(sender, MessageKey.UNKNOWN_USER);
return;
} else if (auth.getLastIp() == null) {
sender.sendMessage("No known last IP address for player");
return;
}
List<String> accountList = dataSource.getAllAuthsByIp(auth.getLastIp());
if (accountList.isEmpty()) {
commonService.send(sender, MessageKey.UNKNOWN_USER);
} else if (accountList.size() == 1) {
sender.sendMessage("[AuthMe] " + playerName + " is a single account player");
} else {
outputAccountsList(sender, playerName, accountList);
}
List<String> accountList = dataSource.getAllAuthsByIp(auth.getLastIp());
if (accountList.isEmpty()) {
commonService.send(sender, MessageKey.UNKNOWN_USER);
} else if (accountList.size() == 1) {
sender.sendMessage("[AuthMe] " + playerName + " is a single account player");
} else {
outputAccountsList(sender, playerName, accountList);
}
});
}
@@ -55,15 +55,12 @@ public class ConverterCommand implements ExecutableCommand {
final Converter converter = converterFactory.newInstance(converterClass);
// Run the convert job
bukkitService.runTaskAsynchronously(new Runnable() {
@Override
public void run() {
try {
converter.execute(sender);
} catch (Exception e) {
commonService.send(sender, MessageKey.ERROR);
logger.logException("Error during conversion:", e);
}
bukkitService.runTaskAsynchronously(() -> {
try {
converter.execute(sender);
} catch (Exception e) {
commonService.send(sender, MessageKey.ERROR);
logger.logException("Error during conversion:", e);
}
});
@@ -29,7 +29,7 @@ public class PurgePlayerCommand implements ExecutableCommand {
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
String option = arguments.size() > 1 ? arguments.get(1) : null;
bukkitService.runTaskOptionallyAsync(
bukkitService.runTaskAsynchronously(
() -> executeCommand(sender, arguments.get(0), option));
}
@@ -19,6 +19,7 @@ import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
import java.util.logging.Logger;
import static fr.xephi.authme.service.BukkitService.TICKS_PER_MINUTE;
@@ -94,12 +95,15 @@ public class OnStartupTasks {
return;
}
bukkitService.runTaskTimerAsynchronously(() -> {
for (String playerWithoutMail : dataSource.getLoggedPlayersWithEmptyMail()) {
Player player = bukkitService.getPlayerExact(playerWithoutMail);
if (player != null) {
messages.send(player, MessageKey.ADD_EMAIL_MESSAGE);
List<String> loggedPlayersWithEmptyMail = dataSource.getLoggedPlayersWithEmptyMail();
bukkitService.runTask(() -> {
for (String playerWithoutMail : loggedPlayersWithEmptyMail) {
Player player = bukkitService.getPlayerExact(playerWithoutMail);
if (player != null) {
messages.send(player, MessageKey.ADD_EMAIL_MESSAGE);
}
}
}
});
}, 1, TICKS_PER_MINUTE * settings.getProperty(EmailSettings.DELAY_RECALL));
}
}
@@ -112,7 +112,6 @@ public class PlayerListener implements Listener {
// Non-blocking checks
try {
onJoinVerifier.checkSingleSession(name);
onJoinVerifier.checkIsValidName(name);
} catch (FailedVerificationException e) {
event.setKickMessage(messages.retrieveSingle(name, e.getReason(), e.getArgs()));
@@ -161,6 +160,14 @@ public class PlayerListener implements Listener {
final Player player = event.getPlayer();
final String name = player.getName();
try {
onJoinVerifier.checkSingleSession(name);
} catch (FailedVerificationException e) {
event.setKickMessage(messages.retrieveSingle(name, e.getReason(), e.getArgs()));
event.setResult(PlayerLoginEvent.Result.KICK_OTHER);
return;
}
if (validationService.isUnrestricted(name)) {
return;
}
@@ -1,9 +1,9 @@
package fr.xephi.authme.process.login;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.data.limbo.LimboPlayer;
import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.LoginEvent;
import fr.xephi.authme.events.RestoreInventoryEvent;
import fr.xephi.authme.permission.PermissionsManager;
@@ -40,7 +40,7 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
private TeleportationService teleportationService;
@Inject
private DataSource dataSource;
private PlayerCache playerCache;
@Inject
private CommandManager commandManager;
@@ -88,7 +88,7 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
restoreInventory(player);
}
final PlayerAuth auth = dataSource.getAuth(name);
final PlayerAuth auth = playerCache.getAuth(name);
teleportationService.teleportOnLogin(player, auth, limbo);
// We can now display the join message (if delayed)
@@ -7,6 +7,7 @@ import fr.xephi.authme.permission.AdminPermission;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.ProtectionSettings;
import fr.xephi.authme.util.AtomicIntervalCounter;
import org.bukkit.scheduler.BukkitTask;
import javax.inject.Inject;
@@ -29,14 +30,11 @@ public class AntiBotService implements SettingsDependent {
private final CopyOnWriteArrayList<String> antibotKicked = new CopyOnWriteArrayList<>();
// Settings
private int duration;
private int sensibility;
private int interval;
// Service status
private AntiBotStatus antiBotStatus;
private boolean startup;
private BukkitTask disableTask;
private Instant lastFlaggedJoin;
private int flagged = 0;
private AtomicIntervalCounter flaggedCounter;
@Inject
AntiBotService(Settings settings, Messages messages, PermissionsManager permissionsManager,
@@ -47,7 +45,6 @@ public class AntiBotService implements SettingsDependent {
this.bukkitService = bukkitService;
// Initial status
disableTask = null;
flagged = 0;
antiBotStatus = AntiBotStatus.DISABLED;
startup = true;
// Load settings and start if required
@@ -58,8 +55,9 @@ public class AntiBotService implements SettingsDependent {
public void reload(Settings settings) {
// Load settings
duration = settings.getProperty(ProtectionSettings.ANTIBOT_DURATION);
sensibility = settings.getProperty(ProtectionSettings.ANTIBOT_SENSIBILITY);
interval = settings.getProperty(ProtectionSettings.ANTIBOT_INTERVAL);
int sensibility = settings.getProperty(ProtectionSettings.ANTIBOT_SENSIBILITY);
int interval = settings.getProperty(ProtectionSettings.ANTIBOT_INTERVAL);
flaggedCounter = new AtomicIntervalCounter(sensibility, interval);
// Stop existing protection
stopProtection();
@@ -83,19 +81,25 @@ public class AntiBotService implements SettingsDependent {
}
}
/**
* Transitions the anti bot service to an active status.
*/
private void startProtection() {
// Disable existing antibot session
stopProtection();
// Enable the new session
antiBotStatus = AntiBotStatus.ACTIVE;
// Inform admins
bukkitService.getOnlinePlayers().stream()
.filter(player -> permissionsManager.hasPermission(player, AdminPermission.ANTIBOT_MESSAGES))
.forEach(player -> messages.send(player, MessageKey.ANTIBOT_AUTO_ENABLED_MESSAGE));
if (antiBotStatus == AntiBotStatus.ACTIVE) {
return; // Already activating/active
}
if (disableTask != null) {
disableTask.cancel();
}
// Schedule auto-disable
disableTask = bukkitService.runTaskLater(this::stopProtection, duration * TICKS_PER_MINUTE);
antiBotStatus = AntiBotStatus.ACTIVE;
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() -> {
// Inform admins
bukkitService.getOnlinePlayers().stream()
.filter(player -> permissionsManager.hasPermission(player, AdminPermission.ANTIBOT_MESSAGES))
.forEach(player -> messages.send(player, MessageKey.ANTIBOT_AUTO_ENABLED_MESSAGE));
});
}
/**
@@ -108,7 +112,7 @@ public class AntiBotService implements SettingsDependent {
// Change status
antiBotStatus = AntiBotStatus.LISTENING;
flagged = 0;
flaggedCounter.reset();
antibotKicked.clear();
// Cancel auto-disable task
@@ -158,17 +162,7 @@ public class AntiBotService implements SettingsDependent {
return true;
}
if (lastFlaggedJoin == null) {
lastFlaggedJoin = Instant.now();
}
if (ChronoUnit.SECONDS.between(lastFlaggedJoin, Instant.now()) <= interval) {
flagged++;
} else {
// reset to 1 because this player is also count as not registered
flagged = 1;
lastFlaggedJoin = null;
}
if (flagged > sensibility) {
if (flaggedCounter.handle()) {
startProtection();
return true;
}
@@ -99,7 +99,7 @@ public class PurgeService {
isPurging = true;
PurgeTask purgeTask = new PurgeTask(this, permissionsManager, sender, names, players);
bukkitService.runTaskTimer(purgeTask, 0, 1);
bukkitService.runTaskTimerAsynchronously(purgeTask, 0, 1);
}
/**
@@ -0,0 +1,53 @@
package fr.xephi.authme.util;
/**
* A thread-safe interval counter, allows to detect if an event happens more than 'threshold' times
* in the given 'interval'.
*/
public class AtomicIntervalCounter {
private final int threshold;
private final int interval;
private int count;
private long lastInsert;
/**
* Constructs a new counter.
*
* @param threshold the threshold value of the counter.
* @param interval the counter interval in milliseconds.
*/
public AtomicIntervalCounter(int threshold, int interval) {
this.threshold = threshold;
this.interval = interval;
reset();
}
/**
* Resets the counter count.
*/
public synchronized void reset() {
count = 0;
lastInsert = 0;
}
/**
* Increments the counter and returns true if the current count has reached the threshold value
* in the given interval, this will also reset the count value.
*
* @return true if the count has reached the threshold value.
*/
public synchronized boolean handle() {
long now = System.currentTimeMillis();
if (now - lastInsert > interval) {
count = 1;
} else {
count++;
}
if (count > threshold) {
reset();
return true;
}
lastInsert = now;
return false;
}
}