[BREAKING ] Lazy permission updating.

* Each asynchronous permission check yields an update request anyway,
thus frequent bulk update requests have been removed.
* With join and world change, lazy permission updating is requested.

Breaking:
* Remove CheckConfig.getCachePermissions().
This commit is contained in:
asofold 2018-02-08 01:27:47 +01:00
parent 4bfc4f2cd2
commit 6dbb7d4299
10 changed files with 170 additions and 87 deletions

View File

@ -16,7 +16,6 @@ package fr.neatmonster.nocheatplus.checks.access;
import fr.neatmonster.nocheatplus.config.ConfPaths;
import fr.neatmonster.nocheatplus.config.ConfigFile;
import fr.neatmonster.nocheatplus.permissions.RegisteredPermission;
/**
* Minimal implementation, doing nothing.
@ -31,36 +30,17 @@ public abstract class ACheckConfig implements ICheckConfig {
/** If to adapt to server side lag. */
public final boolean lag;
/** Permissions to hold in player data cache, not final for flexibility. */
protected RegisteredPermission[] cachePermissions;
/**
*
* @param config
* @param pathPrefix Path prefix for the check section (example for use: prefix+"debug").
*/
public ACheckConfig(final ConfigFile config, final String pathPrefix){
this(config, pathPrefix, null);
}
/**
*
* @param config
* @param pathPrefix Path prefix for the check section (example for use: prefix+"debug").
* @param cachePermissions cachePermissions Permissions to hold in player data cache. Can be null.
*/
public ACheckConfig(final ConfigFile config, final String pathPrefix,
final RegisteredPermission[] cachePermissions){
public ACheckConfig(final ConfigFile config, final String pathPrefix){
// TODO: Path prefix construction is somewhat inconsistent with debug hierarchy ?
debug = config.getBoolean(pathPrefix + ConfPaths.SUB_DEBUG, config.getBoolean(ConfPaths.CHECKS_DEBUG, false));
// TODO: Use lag flag where appropriate and document it (or get rid of it).
lag = config.getBoolean(pathPrefix + ConfPaths.SUB_LAG, true) && config.getBoolean(ConfPaths.MISCELLANEOUS_LAG, true);
this.cachePermissions = cachePermissions;
}
@Override
public RegisteredPermission[] getCachePermissions() {
return cachePermissions;
}
@Override

View File

@ -15,7 +15,6 @@
package fr.neatmonster.nocheatplus.checks.access;
import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.permissions.RegisteredPermission;
/**
* This interface must be implemented by all configuration classes.
@ -39,10 +38,4 @@ public interface ICheckConfig {
/** On the fly debug flags, to be set by commands and similar. */
public void setDebug(boolean debug);
/**
* Retrieve the permissions that have to be updated for this check.
* @return An array of permissions, may be null.
*/
public RegisteredPermission[] getCachePermissions();
}

View File

@ -51,9 +51,21 @@ public class ChatConfig extends ACheckConfig {
}
};
private static RegisteredPermission[] preferKeepUpdatedPermissions = new RegisteredPermission[]{
// Only the permissions needed for async. checking.
Permissions.CHAT_COLOR,
Permissions.CHAT_TEXT,
Permissions.CHAT_CAPTCHA,
// TODO: COMMANDS, in case of handleascommand?
};
/** The map containing the configurations per world. */
private static final Map<String, ChatConfig> worldsMap = new HashMap<String, ChatConfig>();
public static RegisteredPermission[] getPreferKeepUpdatedPermissions() {
return preferKeepUpdatedPermissions;
}
/**
* Clear all the configurations.
*/
@ -161,12 +173,7 @@ public class ChatConfig extends ACheckConfig {
* the data
*/
public ChatConfig(final ConfigFile config) {
super(config, ConfPaths.CHAT, new RegisteredPermission[]{
// Only the permissions needed for async. checking.
Permissions.CHAT_COLOR,
Permissions.CHAT_TEXT,
Permissions.CHAT_CAPTCHA,
});
super(config, ConfPaths.CHAT);
captchaCheck = config.getBoolean(ConfPaths.CHAT_CAPTCHA_CHECK);
captchaSkipCommands = config.getBoolean(ConfPaths.CHAT_CAPTCHA_SKIP_COMMANDS);

View File

@ -103,8 +103,6 @@ public class ChatListener extends CheckListener implements INotifyReload, JoinLe
@EventHandler(priority=EventPriority.MONITOR)
public void onPlayerChangedWorld(final PlayerChangedWorldEvent event) {
final Player player = event.getPlayer();
final PlayerData pData = DataManager.getPlayerData(player);
pData.requestLazyPermissionUpdate(ChatConfig.getConfig(player).getCachePermissions());
ChatData.getData(player).currentWorldName = player.getWorld().getName();
}
@ -125,7 +123,6 @@ public class ChatListener extends CheckListener implements INotifyReload, JoinLe
// (Might omit this if already cancelled.)
final PlayerData pData = DataManager.getPlayerData(player);
final ChatConfig cc = ChatConfig.getConfig(player);
pData.requestLazyPermissionUpdate(cc.getCachePermissions());
// First the color check.
if (!alreadyCancelled && color.isEnabled(player)) {
@ -153,7 +150,6 @@ public class ChatListener extends CheckListener implements INotifyReload, JoinLe
// Tell TickTask to update cached permissions.
final PlayerData pData = DataManager.getPlayerData(player);
final ChatConfig cc = ChatConfig.getConfig(player);
pData.requestLazyPermissionUpdate(cc.getCachePermissions());
// Checks that replace parts of the message (color).
if (color.isEnabled(player)) {
@ -262,8 +258,6 @@ public class ChatListener extends CheckListener implements INotifyReload, JoinLe
final ChatConfig cc = ChatConfig.getConfig(player);
final ChatData data = ChatData.getData(player);
// Tell TickTask to update cached permissions.
pData.requestLazyPermissionUpdate(cc.getCachePermissions());
// (No forced permission update, because the associated permissions are treated as hints rather.)
// Reset captcha of player if needed.

View File

@ -31,6 +31,22 @@ import fr.neatmonster.nocheatplus.permissions.RegisteredPermission;
*/
public class NetConfig extends ACheckConfig {
private static RegisteredPermission[] preferKeepUpdatedPermissions = new RegisteredPermission[] {
Permissions.NET_ATTACKFREQUENCY,
Permissions.NET_FLYINGFREQUENCY,
Permissions.NET_KEEPALIVEFREQUENCY,
Permissions.NET_PACKETFREQUENCY,
};
public static RegisteredPermission[] getPreferKeepUpdatedPermissions() {
// TODO: Individual checks might want to register these, or just on permission checking.
return preferKeepUpdatedPermissions;
}
/////////////
// Instance
/////////////
public final boolean attackFrequencyActive;
public final float attackFrequencyLimitSecondsHalf;
public final float attackFrequencyLimitSecondsOne;
@ -63,12 +79,7 @@ public class NetConfig extends ACheckConfig {
public NetConfig(final ConfigFile config) {
// TODO: These permissions should have default policies.
super(config, ConfPaths.NET, new RegisteredPermission[] {
Permissions.NET_ATTACKFREQUENCY,
Permissions.NET_FLYINGFREQUENCY,
Permissions.NET_KEEPALIVEFREQUENCY,
Permissions.NET_PACKETFREQUENCY,
});
super(config, ConfPaths.NET);
final ConfigFile globalConfig = ConfigManager.getConfigFile();

View File

@ -0,0 +1,37 @@
package fr.neatmonster.nocheatplus.components.registry.exception;
/**
* An item is not registered, although that is demanded in this context.
*
* @author asofold
*
*/
public class NotRegisteredException extends RegistryException {
/**
*
*/
private static final long serialVersionUID = 6240601169826276653L;
public NotRegisteredException() {
super();
}
public NotRegisteredException(String message, Throwable cause,
boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public NotRegisteredException(String message, Throwable cause) {
super(message, cause);
}
public NotRegisteredException(String message) {
super(message);
}
public NotRegisteredException(Throwable cause) {
super(cause);
}
}

View File

@ -1,8 +1,5 @@
package fr.neatmonster.nocheatplus.permissions;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Combine permission information (registry): RegisteredPermission, policy
* information (default fetching policy, extra flags), (the Bukkit permission is
@ -16,9 +13,6 @@ public class PermissionInfo extends PermissionPolicy {
// TODO: Consider IPermissionPolicy with read only access.
private final RegisteredPermission registeredPermission;
/** Copy on write, primary thread write access only. */
// TODO: Implement or remove.
private RegisteredPermission[] preferUpdateOther = null;
/**
* Minimal constructor.
@ -43,37 +37,4 @@ public class PermissionInfo extends PermissionPolicy {
return registeredPermission;
}
/**
* Copy on write for primary thread only access.
*
* @param otherPermissions
*/
public void preferUpdateOther(final RegisteredPermission... otherPermissions) {
final Set<RegisteredPermission> newEntries = new LinkedHashSet<RegisteredPermission>();
if (preferUpdateOther != null) {
for (int i = 0; i < preferUpdateOther.length; i++) {
newEntries.add(preferUpdateOther[i]);
}
}
for (int i = 0; i < otherPermissions.length; i++) {
newEntries.add(otherPermissions[i]);
}
preferUpdateOther = newEntries.toArray(new RegisteredPermission[newEntries.size()]);
}
/**
* Get the internally stored array with other permissions that are preferred
* to be updated when the permission represented by this PermissionInfo is
* to be updated. Note that the permission itself is not automatically
* included, but external calls might add it. Aiming at requesting
* permission updates from another thread than the primary server thread.
* <br>
* Thread-safe read.
*
* @return
*/
public RegisteredPermission[] preferUpdateOther() {
return this.preferUpdateOther;
}
}

View File

@ -2,12 +2,15 @@ package fr.neatmonster.nocheatplus.permissions;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import fr.neatmonster.nocheatplus.components.registry.exception.AlreadyRegisteredException;
import fr.neatmonster.nocheatplus.components.registry.exception.NotRegisteredException;
import fr.neatmonster.nocheatplus.utilities.ds.map.HashMapLOW;
/**
@ -28,6 +31,16 @@ public class PermissionRegistry {
/** No need to map to the strings in an extra step here. */
private final HashMapLOW<String, PermissionInfo> infosString = new HashMapLOW<String, PermissionInfo>(lock, 100);
// TODO: Might do lazy tasks for all player data regularly.
/**
* All registered permissions that are meant to be kept updated for players.
* Guarantees are not to actually keep them updated, but might lazily update
* them with world changing and logging on.
*/
private final LinkedHashSet<RegisteredPermission> preferKeepUpdated = new LinkedHashSet<RegisteredPermission>();
private RegisteredPermission[] preferKeepUpdatedWorld = new RegisteredPermission[0];
private RegisteredPermission[] preferKeepUpdatedOffline = new RegisteredPermission[0];
/**
*
* @param nextId Next id to return with getId.
@ -130,6 +143,7 @@ public class PermissionRegistry {
}
/**
* (Primary thread only.)
*
* @param settings
* @return All registered permissions for which the policy has changed (by
@ -153,7 +167,72 @@ public class PermissionRegistry {
// Still set in either case, in case we missed something (cheap).
info.set(newPolicy);
}
arrangePreferKeepUpdated();
return changed;
}
/**
* (Primary thread only.)
*/
public void arrangePreferKeepUpdated() {
final List<RegisteredPermission> preferKeepUpdatedWorld = new LinkedList<RegisteredPermission>();
final List<RegisteredPermission> preferKeepUpdatedOffline = new LinkedList<RegisteredPermission>();
// (No permanent updating yet.)
for (final RegisteredPermission registeredPermission : this.preferKeepUpdated) {
final PermissionInfo info = infosInt.get(registeredPermission.getId());
switch (info.fetchingPolicy()) {
case FALSE:
case TRUE:
// Skip only these.
continue;
case ALWAYS:
case INTERVAL:
// Update as often as makes sense in this context.
// TODO: Might later run lazy tasks permanently for online players.
preferKeepUpdatedOffline.add(registeredPermission);
preferKeepUpdatedWorld.add(registeredPermission);
break;
default:
if (info.invalidationOffline()) {
preferKeepUpdatedOffline.add(registeredPermission);
}
else if (info.invalidationWorld()) { // TODO: 'else' as long as world includes offline.
preferKeepUpdatedOffline.add(registeredPermission); // TODO: as long as world includes offline.
preferKeepUpdatedWorld.add(registeredPermission);
}
break;
}
}
this.preferKeepUpdatedWorld = preferKeepUpdatedWorld.toArray(new RegisteredPermission[preferKeepUpdatedWorld.size()]);
this.preferKeepUpdatedOffline = preferKeepUpdatedOffline.toArray(new RegisteredPermission[preferKeepUpdatedOffline.size()]);
}
/**
* Permissions will be sorted into areas, depending on the set policies.
* However that will only happen with either calling
* arrangePreferKeepUpdated() or updateSettings(PermissionSettings).
*
* @param registeredPermissions
*/
public void preferKeepUpdated(final RegisteredPermission... registeredPermissions) {
for (final RegisteredPermission registeredPermission : registeredPermissions) {
final PermissionInfo info = infosInt.get(registeredPermission.getId());
if (info == null) {
throw new NotRegisteredException("Id not registered: " + registeredPermission.getId());
}
if (info.getRegisteredPermission() != registeredPermission) {
throw new AlreadyRegisteredException("RegisteredPermission instances should be identical.");
}
preferKeepUpdated.add(info.getRegisteredPermission()); // Add the already registered object.
}
}
public RegisteredPermission[] getPreferKeepUpdatedWorld() {
return preferKeepUpdatedWorld;
}
public RegisteredPermission[] getPreferKeepUpdatedOffline() {
return preferKeepUpdatedOffline;
}
}

View File

@ -558,7 +558,7 @@ public class PlayerData implements IData, ICanHandleTimeRunningBackwards {
* May be null.
*/
public void requestLazyPermissionUpdate(final RegisteredPermission...registeredPermissions) {
if (registeredPermissions == null) {
if (registeredPermissions == null || registeredPermissions.length == 0) {
return;
}
if (Bukkit.isPrimaryThread()) {
@ -586,6 +586,7 @@ public class PlayerData implements IData, ICanHandleTimeRunningBackwards {
void onPlayerJoin(final long timeNow) {
invalidateOffline();
requestLazyPermissionUpdate(permissionRegistry.getPreferKeepUpdatedOffline());
}
private void invalidateOffline() {
@ -594,7 +595,14 @@ public class PlayerData implements IData, ICanHandleTimeRunningBackwards {
while (it.hasNext()) {
final PermissionNode node = it.next().getValue();
final PermissionInfo info = node.getPermissionInfo();
if (info.invalidationOffline() || info.invalidationWorld()) {
if (info.invalidationOffline()
/*
* TODO: world based should only be invalidated with world
* changing. Therefore store the last world info
* (UUID/name?) in PlayerData and use on login for
* comparison.
*/
|| info.invalidationWorld()) {
// TODO: Really count leave as world change?
node.invalidate();
}
@ -618,6 +626,7 @@ public class PlayerData implements IData, ICanHandleTimeRunningBackwards {
node.invalidate();
}
}
requestLazyPermissionUpdate(permissionRegistry.getPreferKeepUpdatedWorld());
}
/**

View File

@ -53,6 +53,7 @@ import org.bukkit.scheduler.BukkitScheduler;
import fr.neatmonster.nocheatplus.checks.blockbreak.BlockBreakListener;
import fr.neatmonster.nocheatplus.checks.blockinteract.BlockInteractListener;
import fr.neatmonster.nocheatplus.checks.blockplace.BlockPlaceListener;
import fr.neatmonster.nocheatplus.checks.chat.ChatConfig;
import fr.neatmonster.nocheatplus.checks.chat.ChatListener;
import fr.neatmonster.nocheatplus.checks.combined.CombinedData;
import fr.neatmonster.nocheatplus.checks.combined.CombinedListener;
@ -61,6 +62,7 @@ import fr.neatmonster.nocheatplus.checks.inventory.InventoryListener;
import fr.neatmonster.nocheatplus.checks.moving.MovingListener;
import fr.neatmonster.nocheatplus.checks.moving.location.tracking.LocationTrace.TraceEntryPool;
import fr.neatmonster.nocheatplus.checks.moving.util.AuxMoving;
import fr.neatmonster.nocheatplus.checks.net.NetConfig;
import fr.neatmonster.nocheatplus.checks.workaround.WRPT;
import fr.neatmonster.nocheatplus.clients.ModUtil;
import fr.neatmonster.nocheatplus.command.NoCheatPlusCommand;
@ -967,6 +969,12 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
command.setExecutor(commandHandler);
// (CommandHandler is TabExecutor.)
// Tell the permission registry, which permissions should get updated.
// TODO: confine by check enabled flags.
permissionRegistry.preferKeepUpdated(NetConfig.getPreferKeepUpdatedPermissions());
permissionRegistry.preferKeepUpdated(ChatConfig.getPreferKeepUpdatedPermissions());
permissionRegistry.arrangePreferKeepUpdated();
////////////////////////////////
// Tasks, post-rumble-logging
////////////////////////////////
@ -1049,6 +1057,10 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
*/
private void postEnable(final Player[] onlinePlayers) {
logManager.info(Streams.INIT, "Post-enable running...");
// Update permission registry internals for permissions preferred to be updated.
// (By now checks should have noted what they want.)
permissionRegistry.arrangePreferKeepUpdated();
final ConfigFile config = ConfigManager.getConfigFile();
try {
// Command protection feature.