Add periodic consistency checking.

Mainly this is now an "ability", in terms of allowing for registration
f a ConsistencyChecker component. With this commit DataMan will check
the online-players map for consistency, LetterEngine(chat.text) will
check expiration of data, NCPExemptionManager will check entity-ids.
This commit is contained in:
asofold 2013-02-27 18:00:44 +01:00
parent e0b2eb753c
commit 74a935ccd6
8 changed files with 252 additions and 37 deletions

View File

@ -30,6 +30,7 @@ import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.permissions.Permissible;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitScheduler;
import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.blockbreak.BlockBreakListener;
@ -46,6 +47,7 @@ import fr.neatmonster.nocheatplus.command.INotifyReload;
import fr.neatmonster.nocheatplus.compat.MCAccess;
import fr.neatmonster.nocheatplus.compat.MCAccessFactory;
import fr.neatmonster.nocheatplus.components.ComponentWithName;
import fr.neatmonster.nocheatplus.components.ConsistencyChecker;
import fr.neatmonster.nocheatplus.components.INeedConfig;
import fr.neatmonster.nocheatplus.components.MCAccessHolder;
import fr.neatmonster.nocheatplus.components.NCPListener;
@ -263,13 +265,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
return done.size();
}
/** The event listeners. */
private final List<Listener> listeners = new ArrayList<Listener>();
/** Components that need notification on reloading.
* (Kept here, for if during runtime some might get added.)*/
private final List<INotifyReload> notifyReload = new LinkedList<INotifyReload>();
/** Is the configuration outdated? */
private boolean configOutdated = false;
@ -279,6 +274,10 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
/** Player data future stuff. */
protected final DataManager dataMan = new DataManager();
private int dataManTaskId = -1;
protected Metrics metrics = null;
/**
* Commands that were changed for protecting them against tab complete or
* use.
@ -290,14 +289,27 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
private boolean manageListeners = true;
/** The event listeners. */
private final List<Listener> listeners = new ArrayList<Listener>();
/** Components that need notification on reloading.
* (Kept here, for if during runtime some might get added.)*/
private final List<INotifyReload> notifyReload = new LinkedList<INotifyReload>();
/** Permission states stored on a per-world basis, updated with join/quit/kick. */
protected final List<PermStateReceiver> permStateReceivers = new ArrayList<PermStateReceiver>();
/** Components that check consistency. */
protected final List<ConsistencyChecker> consistencyCheckers = new ArrayList<ConsistencyChecker>();
/** Index at which to continue. */
protected int consistencyCheckerIndex = 0;
protected int consistencyCheckerTaskId = -1;
/** All registered components. */
protected Set<Object> allComponents = new LinkedHashSet<Object>(50);
protected Metrics metrics = null;
private int dataManTaskId = -1;
@Override
public boolean addComponent(final Object obj) {
@ -324,13 +336,18 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
added = true;
}
if (obj instanceof MCAccessHolder){
// Add to allComponents.
// These will get notified in initMcAccess (iterates over allComponents).
((MCAccessHolder) obj).setMCAccess(getMCAccess());
added = true;
}
if (obj instanceof ConsistencyChecker){
consistencyCheckers.add((ConsistencyChecker) obj);
added = true;
}
// Also add to DataManager, which will pick what it needs.
// TODO: This is fishy in principle, something more concise?
if (dataMan.addComponent(obj)) added = true;
// Add to allComponents if in fact added.
if (added) allComponents.add(obj);
return added;
}
@ -381,6 +398,9 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
if (obj instanceof INotifyReload) {
notifyReload.remove(obj);
}
if (obj instanceof ConsistencyChecker){
consistencyCheckers.remove(obj);
}
dataMan.removeComponent(obj);
allComponents.remove(obj);
}
@ -408,8 +428,13 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
listenerManager.setRegisterDirectly(false);
listenerManager.clear();
BukkitScheduler sched = getServer().getScheduler();
// Stop data-man task.
if (dataManTaskId != -1) getServer().getScheduler().cancelTask(dataManTaskId);
if (dataManTaskId != -1){
sched.cancelTask(dataManTaskId);
dataManTaskId = -1;
}
// Stop the tickTask.
if (verbose) LogUtil.logInfo("[NoCheatPlus] Stop TickTask...");
@ -425,10 +450,15 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
metrics.cancel();
metrics = null;
}
// Stop consistency checking task.
if (consistencyCheckerTaskId != -1){
sched.cancelTask(consistencyCheckerTaskId);
}
// Just to be sure nothing gets left out.
if (verbose) LogUtil.logInfo("[NoCheatPlus] Stop all remaining tasks...");
getServer().getScheduler().cancelTasks(this);
sched.cancelTasks(this);
// Exemptions cleanup.
if (verbose) LogUtil.logInfo("[NoCheatPlus] Reset ExemptionManager...");
@ -566,9 +596,16 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
// Reset Command protection.
undoCommandChanges();
if (config.getBoolean(ConfPaths.MISCELLANEOUS_PROTECTPLUGINS)) setupCommandProtection();
scheduleConsistencyCheckers();
}
},
NCPExemptionManager.getListener(),
new ConsistencyChecker() {
@Override
public void checkConsistency(final Player[] onlinePlayers) {
NCPExemptionManager.checkConsistency(onlinePlayers);
}
},
dataMan,
new BlockInteractListener(),
new BlockBreakListener(),
@ -598,6 +635,9 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
dataMan.checkExpiration();
}
}, 1207, 1207);
// Set up consistency checking.
scheduleConsistencyCheckers();
// Setup the graphs, plotters and start Metrics.
@ -718,25 +758,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
// TODO: if (online.lenght > 0) LogUtils.logInfo("[NCP] Updated " + online.length + "players (post-enable).")
}
// public void onPlayerJoinLow(final PlayerJoinEvent event) {
// /*
// * ____ _ _ _
// * | _ \| | __ _ _ _ ___ _ __ | | ___ (_)_ __
// * | |_) | |/ _` | | | |/ _ \ '__| _ | |/ _ \| | '_ \
// * | __/| | (_| | |_| | __/ | | |_| | (_) | | | | |
// * |_| |_|\__,_|\__, |\___|_| \___/ \___/|_|_| |_|
// * |___/
// */
// // Change the NetServerHandler of the player if requested in the configuration.
// final ConfigFile configFile = ConfigManager.getConfigFile();
// if (configFile.getBoolean(ConfPaths.MISCELLANEOUS_NOMOVEDTOOQUICKLY_ENABLED, false))
// NCPNetServerHandler.changeNetServerHandler(event.getPlayer(),
// configFile.getBoolean(ConfPaths.MISCELLANEOUS_NOMOVEDTOOQUICKLY_USEPROXY, false));
// }
/**
* Quick solution to hide the listener methods, expect refactoring.
* @return
@ -822,5 +843,72 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
}
}
}
protected void scheduleConsistencyCheckers(){
BukkitScheduler sched = getServer().getScheduler();
if (consistencyCheckerTaskId != -1){
sched.cancelTask(consistencyCheckerTaskId);
}
ConfigFile config = ConfigManager.getConfigFile();
if (!config.getBoolean(ConfPaths.DATA_CONSISTENCYCHECKS_CHECK, true)) return;
// Schedule task in seconds.
final long delay = 20L * config.getInt(ConfPaths.DATA_CONSISTENCYCHECKS_INTERVAL, 1, 3600, 10);
consistencyCheckerTaskId = sched.scheduleSyncRepeatingTask(this, new Runnable() {
@Override
public void run() {
runConsistencyChecks();
}
}, delay, delay );
}
/**
* Run consistency checks for at most the configured duration. If not finished, a task will be scheduled to continue.
*/
protected void runConsistencyChecks(){
final long tStart = System.currentTimeMillis();
final ConfigFile config = ConfigManager.getConfigFile();
if (!config.getBoolean(ConfPaths.DATA_CONSISTENCYCHECKS_CHECK) || consistencyCheckers.isEmpty()){
consistencyCheckerIndex = 0;
return;
}
final long tEnd = tStart + config.getLong(ConfPaths.DATA_CONSISTENCYCHECKS_MAXTIME, 1, 50, 2);
if (consistencyCheckerIndex >= consistencyCheckers.size()) consistencyCheckerIndex = 0;
final Player[] onlinePlayers = getServer().getOnlinePlayers();
// Loop
while (consistencyCheckerIndex < consistencyCheckers.size()){
final ConsistencyChecker checker = consistencyCheckers.get(consistencyCheckerIndex);
try{
checker.checkConsistency(onlinePlayers);
}
catch (Throwable t){
LogUtil.logSevere("[NoCheatPlus] ConsistencyChecker(" + checker.getClass().getName() + ") encountered an exception:");
LogUtil.logSevere(t);
}
consistencyCheckerIndex ++; // Do not remove :).
final long now = System.currentTimeMillis();
if (now < tStart || now >= tEnd){
break;
}
}
// (The index might be bigger than size by now.)
final boolean debug = config.getBoolean(ConfPaths.LOGGING_DEBUG);
// If not finished, schedule further checks.
if (consistencyCheckerIndex < consistencyCheckers.size()){
getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() {
@Override
public void run() {
runConsistencyChecks();
}
});
if (debug){
LogUtil.logInfo("[NoCheatPlus] Re-scheduled consistency-checks.");
}
}
else if (debug){
LogUtil.logInfo("[NoCheatPlus] Consistency-checks run.");
}
}
}

View File

@ -5,6 +5,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.bukkit.entity.Player;
import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.chat.ChatConfig;
import fr.neatmonster.nocheatplus.checks.chat.ChatData;
@ -16,6 +18,7 @@ import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.Similar
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.WordPrefixes;
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.WordPrefixes.WordPrefixesSettings;
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.WordProcessor;
import fr.neatmonster.nocheatplus.components.ConsistencyChecker;
import fr.neatmonster.nocheatplus.components.IData;
import fr.neatmonster.nocheatplus.components.IHaveCheckType;
import fr.neatmonster.nocheatplus.components.IRemoveData;
@ -29,7 +32,7 @@ import fr.neatmonster.nocheatplus.logging.LogUtil;
* @author mc_dev
*
*/
public class LetterEngine implements IRemoveData, IHaveCheckType{
public class LetterEngine implements IRemoveData, IHaveCheckType, ConsistencyChecker{
/** Global processors */
protected final List<WordProcessor> processors = new ArrayList<WordProcessor>();
@ -122,4 +125,17 @@ public class LetterEngine implements IRemoveData, IHaveCheckType{
public final CheckType getCheckType() {
return CheckType.CHAT_TEXT;
}
@Override
public void checkConsistency(final Player[] onlinePlayers) {
// Use consistency checking to release some memory.
final long now = System.currentTimeMillis();
if (now < dataMap.lastAccess){
dataMap.clear();
return;
}
if (now - dataMap.lastAccess > dataMap.durExpire){
dataMap.expire(now - dataMap.durExpire);
}
}
}

View File

@ -0,0 +1,18 @@
package fr.neatmonster.nocheatplus.components;
import org.bukkit.entity.Player;
/**
* This component might be called periodically. Might not be called ever.
* @author mc_dev
*
*/
public interface ConsistencyChecker {
/**
* Perform consistency checking. Depending on configuration this should clean up inconsistent states and/or log problems.
* @param onlinePlayers Players as returned by Server.getOnlinePlayers, at the point of time before checking.
*/
public void checkConsistency(Player[] onlinePlayers);
// TODO: Might add method to check consistency for single players (on join, on certain check failures).
}

View File

@ -3,7 +3,7 @@ package fr.neatmonster.nocheatplus.components;
/**
* ComponentRegistry:
* <li>Supported components: Listener, TickListener, PermStateReceiver, INotifyReload, INeedConfig, IRemoveData, MCAccessHolder</li>
* <li>Supported components: Listener, TickListener, PermStateReceiver, INotifyReload, INeedConfig, IRemoveData, MCAccessHolder, ConsistencyChecker</li>
* <li>Registering components should be done during onEnable or any time while the plugin is enabled, all components will be unregistered in onDisable.</li>
* <li>References to all components will be held until onDisable is finished.</li>
* <li>Interfaces checked for managed listeners: IHaveMethodOrder (method), ComponentWithName (tag)</li>

View File

@ -56,12 +56,19 @@ public abstract class ConfPaths {
/** TEMP: hidden flag to disable all lag adaption with one flag. */
public static final String MISCELLANEOUS_LAG = MISCELLANEOUS + "lag";
// Extended data-related settings.
@GlobalConfig
private static final String DATA = "data.";
// Expired data removal.
private static final String DATA_EXPIRATION = DATA + "expiration.";
public static final String DATA_EXPIRATION_DURATION = DATA_EXPIRATION + "duration";
public static final String DATA_EXPIRATION_DATA = DATA_EXPIRATION + "data";
public static final String DATA_EXPIRATION_HISTORY = DATA_EXPIRATION + "history";
// Consistency checking.
private static final String DATA_CONSISTENCYCHECKS = DATA + "consistencychecks.";
public static final String DATA_CONSISTENCYCHECKS_CHECK = DATA_CONSISTENCYCHECKS + "active";
public static final String DATA_CONSISTENCYCHECKS_INTERVAL = DATA_CONSISTENCYCHECKS + "interval";
public static final String DATA_CONSISTENCYCHECKS_MAXTIME = DATA_CONSISTENCYCHECKS + "maxtime";
private static final String CHECKS = "checks.";

View File

@ -76,9 +76,14 @@ public class DefaultConfig extends ConfigFile {
// set(ConfPaths.MISCELLANEOUS_NOMOVEDTOOQUICKLY_ENABLED, false);
// set(ConfPaths.MISCELLANEOUS_NOMOVEDTOOQUICKLY_USEPROXY, false);
// Data settings.
// Expired offline players data.
set(ConfPaths.DATA_EXPIRATION_DURATION, 0);
set(ConfPaths.DATA_EXPIRATION_HISTORY, false);
// Consistency checking.
set(ConfPaths.DATA_CONSISTENCYCHECKS_CHECK, true);
set(ConfPaths.DATA_CONSISTENCYCHECKS_INTERVAL, 10);
set(ConfPaths.DATA_CONSISTENCYCHECKS_MAXTIME, 2);
/*
* 888 88b, 888 888 888 88b, 888

View File

@ -3,6 +3,8 @@ package fr.neatmonster.nocheatplus.hooks;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -16,6 +18,8 @@ import org.bukkit.event.player.PlayerQuitEvent;
import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.components.NCPListener;
import fr.neatmonster.nocheatplus.logging.LogUtil;
import fr.neatmonster.nocheatplus.utilities.StringUtil;
/*
* M"""""""`YM MM'""""'YMM MM"""""""`YM MM""""""""`M dP oo
@ -45,7 +49,7 @@ public class NCPExemptionManager {
/** A map associating a check type with the entity ids of its exempted players. */
private static final Map<CheckType, Set<Integer>> exempted = new HashMap<CheckType, Set<Integer>>();
/** A map associating the registred player with their entity id. */
/** A map associating the registered player with their entity id. */
private static final Map<String, Integer> registeredPlayers = new HashMap<String, Integer>();
static {
@ -298,5 +302,34 @@ public class NCPExemptionManager {
final Integer entityId = registeredPlayers.get(playerName);
if (entityId != null) unexempt(entityId, checkType);
}
/**
* Check Entity-id mappings, for internal use.
* @param onlinePlayers
*/
public static void checkConsistency(final Player[] onlinePlayers){
int wrong = 0;
for (int i = 0; i < onlinePlayers.length; i++){
final Player player = onlinePlayers[i];
final int id = player.getEntityId();
final String name = player.getName();
final Integer presentId = registeredPlayers.get(name);
if (presentId == null){
// TODO: Could complain.
}
else if (id != presentId.intValue()){
wrong ++;
registerPlayer(player);
}
// TODO: Consider also checking if numbers don't match.
}
if (wrong != 0){
final List<String> details = new LinkedList<String>();
if (wrong != 0){
details.add("wrong entity-ids (" + + wrong + ")");
}
LogUtil.logWarning("[NoCheatPlus] ExemptionManager inconsistencies: " + StringUtil.join(details, " | "));
}
}
}

View File

@ -6,6 +6,8 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@ -34,6 +36,7 @@ import fr.neatmonster.nocheatplus.checks.moving.MovingConfig;
import fr.neatmonster.nocheatplus.command.INotifyReload;
import fr.neatmonster.nocheatplus.components.ComponentRegistry;
import fr.neatmonster.nocheatplus.components.ComponentWithName;
import fr.neatmonster.nocheatplus.components.ConsistencyChecker;
import fr.neatmonster.nocheatplus.components.IHaveCheckType;
import fr.neatmonster.nocheatplus.components.INeedConfig;
import fr.neatmonster.nocheatplus.components.IRemoveData;
@ -41,6 +44,8 @@ import fr.neatmonster.nocheatplus.config.ConfPaths;
import fr.neatmonster.nocheatplus.config.ConfigFile;
import fr.neatmonster.nocheatplus.config.ConfigManager;
import fr.neatmonster.nocheatplus.hooks.APIUtils;
import fr.neatmonster.nocheatplus.logging.LogUtil;
import fr.neatmonster.nocheatplus.utilities.StringUtil;
/**
* Central access point for a lot of functionality for managing data, especially removing data for cleanup.<br>
@ -53,7 +58,7 @@ import fr.neatmonster.nocheatplus.hooks.APIUtils;
* @author mc_dev
*
*/
public class DataManager implements Listener, INotifyReload, INeedConfig, ComponentRegistry, ComponentWithName{
public class DataManager implements Listener, INotifyReload, INeedConfig, ComponentRegistry, ComponentWithName, ConsistencyChecker{
protected static DataManager instance = null;
@ -413,5 +418,48 @@ public class DataManager implements Listener, INotifyReload, INeedConfig, Compon
public String getComponentName() {
return "NoCheatPlus_DataManager";
}
@Override
public void checkConsistency(final Player[] onlinePlayers) {
// Check online player tracking consistency.
int missing = 0;
int changed = 0;
int expectedSize = 0;
for (int i = 0; i < onlinePlayers.length; i++){
final Player player = onlinePlayers[i];
final String name = player.getName();
// if (player.isOnline()){
expectedSize += 1 + (name.equals(name.toLowerCase()) ? 0 : 1);
if (!this.onlinePlayers.containsKey(name)){
missing ++;
// TODO: Add the player [problem: messy NPC plugins?]?
}
if (player != this.onlinePlayers.get(name)){
changed ++;
// Update the reference.
addOnlinePlayer(player);
// }
}
}
// TODO: Consider checking lastLogout for too long gone players.
final int storedSize = this.onlinePlayers.size();
if (missing != 0 || changed != 0 || expectedSize != storedSize){
final List<String> details = new LinkedList<String>();
if (missing != 0){
details.add("missing online players (" + missing + ")");
}
if (expectedSize != storedSize){
// TODO: Consider checking for not online players and remove them.
details.add("wrong number of online players (" + storedSize + " instead of " + expectedSize + ")");
}
if (changed != 0){
details.add("changed player instances (" + changed + ")");
}
LogUtil.logWarning("[NoCheatPlus] DataMan inconsistencies: " + StringUtil.join(details, " | "));
}
}
}