Ensure caches are invalidated when Bukkit/Nukkit's Permission#getChildren map is modified (#1378)

This commit is contained in:
Luck 2019-01-13 16:58:31 +00:00
parent 78a74510cf
commit 3c0d1ba7a1
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
12 changed files with 285 additions and 23 deletions

View File

@ -61,4 +61,9 @@ public class ChildProcessor extends AbstractPermissionProcessor implements Permi
} }
this.childPermissions = builder; this.childPermissions = builder;
} }
@Override
public void invalidate() {
refresh();
}
} }

View File

@ -95,6 +95,8 @@ public final class LPDefaultsMap implements Map<Boolean, Set<Permission>> {
private void invalidate(boolean op) { private void invalidate(boolean op) {
getCache(op).invalidate(); getCache(op).invalidate();
this.plugin.getUserManager().invalidateAllPermissionCalculators();
this.plugin.getGroupManager().invalidateAllPermissionCalculators();
} }
/** /**
@ -176,6 +178,13 @@ public final class LPDefaultsMap implements Map<Boolean, Set<Permission>> {
invalidate(this.op); invalidate(this.op);
return ret; return ret;
} }
@Override
public boolean remove(@NonNull Object object) {
boolean ret = super.remove(object);
invalidate(this.op);
return ret;
}
} }
} }

View File

@ -37,6 +37,7 @@ import org.bukkit.plugin.PluginManager;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Field;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -49,7 +50,8 @@ import java.util.function.Function;
* *
* This instance allows LuckPerms to intercept calls to * This instance allows LuckPerms to intercept calls to
* {@link PluginManager#addPermission(Permission)} and record permissions in the * {@link PluginManager#addPermission(Permission)} and record permissions in the
* {@link PermissionRegistry}. * {@link PermissionRegistry}. It also lets us monitor changes to child permission
* relationships.
* *
* It also allows us to pre-determine child permission relationships. * It also allows us to pre-determine child permission relationships.
* *
@ -57,6 +59,17 @@ import java.util.function.Function;
*/ */
public final class LPPermissionMap extends ForwardingMap<String, Permission> { public final class LPPermissionMap extends ForwardingMap<String, Permission> {
private static final Field PERMISSION_CHILDREN_FIELD;
static {
try {
PERMISSION_CHILDREN_FIELD = Permission.class.getDeclaredField("children");
PERMISSION_CHILDREN_FIELD.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new ExceptionInInitializerError(e);
}
}
// Uses perm.getName().toLowerCase(java.util.Locale.ENGLISH); to determine the key // Uses perm.getName().toLowerCase(java.util.Locale.ENGLISH); to determine the key
private final Map<String, Permission> delegate = new ConcurrentHashMap<>(); private final Map<String, Permission> delegate = new ConcurrentHashMap<>();
@ -81,6 +94,8 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
private void update() { private void update() {
this.trueChildPermissions.clear(); this.trueChildPermissions.clear();
this.falseChildPermissions.clear(); this.falseChildPermissions.clear();
this.plugin.getUserManager().invalidateAllPermissionCalculators();
this.plugin.getGroupManager().invalidateAllPermissionCalculators();
} }
@Override @Override
@ -94,7 +109,7 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
Objects.requireNonNull(value, "value"); Objects.requireNonNull(value, "value");
this.plugin.getPermissionRegistry().insert(key); this.plugin.getPermissionRegistry().insert(key);
Permission ret = super.put(key, value); Permission ret = super.put(key, inject(value));
update(); update();
return ret; return ret;
} }
@ -106,32 +121,21 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
} }
} }
@Override
public Permission putIfAbsent(String key, Permission value) {
Objects.requireNonNull(key, "key");
Objects.requireNonNull(value, "value");
this.plugin.getPermissionRegistry().insert(key);
Permission ret = super.putIfAbsent(key, value);
update();
return ret;
}
// null-safe - the plugin manager uses hashmap
@Override @Override
public Permission remove(@Nullable Object object) { public Permission remove(@Nullable Object object) {
if (object == null) { if (object == null) {
return null; return null;
} }
return super.remove(object); return uninject(super.remove(object));
} }
@Override @Override
public boolean remove(Object key, Object value) { public boolean remove(Object key, Object value) {
return key != null && value != null && super.remove(key, value); return key != null && value != null && super.remove(key, uninject(((Permission) value)));
} }
// check for null
@Override @Override
public boolean containsKey(@Nullable Object key) { public boolean containsKey(@Nullable Object key) {
return key != null && super.containsKey(key); return key != null && super.containsKey(key);
@ -186,4 +190,81 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
} }
} }
private Permission inject(Permission permission) {
if (permission == null) {
return null;
}
try {
//noinspection unchecked
Map<String, Boolean> children = (Map<String, Boolean>) PERMISSION_CHILDREN_FIELD.get(permission);
while (children instanceof PermissionNotifyingChildrenMap) {
children = ((PermissionNotifyingChildrenMap) children).delegate;
}
PermissionNotifyingChildrenMap notifyingChildren = new PermissionNotifyingChildrenMap(children);
PERMISSION_CHILDREN_FIELD.set(permission, notifyingChildren);
} catch (Exception e) {
e.printStackTrace();
}
return permission;
}
private Permission uninject(Permission permission) {
if (permission == null) {
return null;
}
try {
//noinspection unchecked
Map<String, Boolean> children = (Map<String, Boolean>) PERMISSION_CHILDREN_FIELD.get(permission);
while (children instanceof PermissionNotifyingChildrenMap) {
children = ((PermissionNotifyingChildrenMap) children).delegate;
}
PERMISSION_CHILDREN_FIELD.set(permission, children);
} catch (Exception e) {
e.printStackTrace();
}
return permission;
}
private final class PermissionNotifyingChildrenMap extends ForwardingMap<String, Boolean> {
private final Map<String, Boolean> delegate;
PermissionNotifyingChildrenMap(Map<String, Boolean> delegate) {
this.delegate = delegate;
}
@Override
protected Map<String, Boolean> delegate() {
return this.delegate;
}
@Override
public Boolean put(@NonNull String key, @NonNull Boolean value) {
Boolean ret = super.put(key, value);
LPPermissionMap.this.update();
return ret;
}
@Override
public void putAll(@NonNull Map<? extends String, ? extends Boolean> map) {
super.putAll(map);
LPPermissionMap.this.update();
}
@Override
public Boolean remove(@NonNull Object object) {
Boolean ret = super.remove(object);
LPPermissionMap.this.update();
return ret;
}
@Override
public void clear() {
super.clear();
LPPermissionMap.this.update();
}
}
} }

View File

@ -130,6 +130,9 @@ public class PermissionCalculator implements Function<String, Tristate> {
} }
public void invalidateCache() { public void invalidateCache() {
for (PermissionProcessor processor : this.processors) {
processor.invalidate();
}
this.lookupCache.clear(); this.lookupCache.clear();
} }
} }

View File

@ -62,4 +62,11 @@ public interface PermissionProcessor {
} }
/**
* Called after the parent calculator has been invalidated
*/
default void invalidate() {
}
} }

View File

@ -69,4 +69,9 @@ public abstract class AbstractGroupManager<T extends Group> extends AbstractMana
public void invalidateAllGroupCaches() { public void invalidateAllGroupCaches() {
getAll().values().forEach(PermissionHolder::invalidateCachedData); getAll().values().forEach(PermissionHolder::invalidateCachedData);
} }
@Override
public void invalidateAllPermissionCalculators() {
getAll().values().forEach(p -> p.getCachedData().invalidatePermissionCalculators());
}
} }

View File

@ -25,6 +25,7 @@
package me.lucko.luckperms.common.model.manager.group; package me.lucko.luckperms.common.model.manager.group;
import me.lucko.luckperms.common.calculator.PermissionCalculator;
import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.manager.Manager; import me.lucko.luckperms.common.model.manager.Manager;
@ -43,4 +44,9 @@ public interface GroupManager<T extends Group> extends Manager<String, Group, T>
*/ */
void invalidateAllGroupCaches(); void invalidateAllGroupCaches();
/**
* Invalidates the {@link PermissionCalculator}s for *loaded* groups.
*/
void invalidateAllPermissionCalculators();
} }

View File

@ -162,6 +162,11 @@ public abstract class AbstractUserManager<T extends User> extends AbstractManage
getAll().values().forEach(PermissionHolder::invalidateCachedData); getAll().values().forEach(PermissionHolder::invalidateCachedData);
} }
@Override
public void invalidateAllPermissionCalculators() {
getAll().values().forEach(p -> p.getCachedData().invalidatePermissionCalculators());
}
/** /**
* Check whether the user's state indicates that they should be persisted to storage. * Check whether the user's state indicates that they should be persisted to storage.
* *

View File

@ -25,6 +25,7 @@
package me.lucko.luckperms.common.model.manager.user; package me.lucko.luckperms.common.model.manager.user;
import me.lucko.luckperms.common.calculator.PermissionCalculator;
import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.model.UserIdentifier; import me.lucko.luckperms.common.model.UserIdentifier;
import me.lucko.luckperms.common.model.manager.Manager; import me.lucko.luckperms.common.model.manager.Manager;
@ -89,4 +90,9 @@ public interface UserManager<T extends User> extends Manager<UserIdentifier, Use
*/ */
void invalidateAllUserCaches(); void invalidateAllUserCaches();
/**
* Invalidates the {@link PermissionCalculator}s for *loaded* users.
*/
void invalidateAllPermissionCalculators();
} }

View File

@ -61,4 +61,9 @@ public class ChildProcessor extends AbstractPermissionProcessor implements Permi
} }
this.childPermissions = builder; this.childPermissions = builder;
} }
@Override
public void invalidate() {
refresh();
}
} }

View File

@ -88,6 +88,8 @@ public final class LPDefaultsMap {
private void invalidate(boolean op) { private void invalidate(boolean op) {
getCache(op).invalidate(); getCache(op).invalidate();
this.plugin.getUserManager().invalidateAllPermissionCalculators();
this.plugin.getGroupManager().invalidateAllPermissionCalculators();
} }
/** /**
@ -136,6 +138,13 @@ public final class LPDefaultsMap {
super.putAll(map); super.putAll(map);
invalidate(this.op); invalidate(this.op);
} }
@Override
public Permission remove(@NonNull Object object) {
Permission ret = super.remove(object);
invalidate(this.op);
return ret;
}
} }
private final class DefaultsCache extends Cache<Map<String, Boolean>> { private final class DefaultsCache extends Cache<Map<String, Boolean>> {

View File

@ -33,13 +33,16 @@ import me.lucko.luckperms.common.treeview.PermissionRegistry;
import me.lucko.luckperms.common.util.LoadingMap; import me.lucko.luckperms.common.util.LoadingMap;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import cn.nukkit.permission.Permission; import cn.nukkit.permission.Permission;
import cn.nukkit.plugin.PluginManager; import cn.nukkit.plugin.PluginManager;
import java.lang.reflect.Field;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function; import java.util.function.Function;
@ -56,6 +59,17 @@ import java.util.function.Function;
*/ */
public final class LPPermissionMap extends ForwardingMap<String, Permission> { public final class LPPermissionMap extends ForwardingMap<String, Permission> {
private static final Field PERMISSION_CHILDREN_FIELD;
static {
try {
PERMISSION_CHILDREN_FIELD = Permission.class.getDeclaredField("children");
PERMISSION_CHILDREN_FIELD.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new ExceptionInInitializerError(e);
}
}
// Uses perm.getName().toLowerCase(java.util.Locale.ENGLISH); to determine the key // Uses perm.getName().toLowerCase(java.util.Locale.ENGLISH); to determine the key
private final Map<String, Permission> delegate = new ConcurrentHashMap<>(); private final Map<String, Permission> delegate = new ConcurrentHashMap<>();
@ -80,6 +94,8 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
private void update() { private void update() {
this.trueChildPermissions.clear(); this.trueChildPermissions.clear();
this.falseChildPermissions.clear(); this.falseChildPermissions.clear();
this.plugin.getUserManager().invalidateAllPermissionCalculators();
this.plugin.getGroupManager().invalidateAllPermissionCalculators();
} }
@Override @Override
@ -89,8 +105,11 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
@Override @Override
public Permission put(@NonNull String key, @NonNull Permission value) { public Permission put(@NonNull String key, @NonNull Permission value) {
Objects.requireNonNull(key, "key");
Objects.requireNonNull(value, "value");
this.plugin.getPermissionRegistry().insert(key); this.plugin.getPermissionRegistry().insert(key);
Permission ret = super.put(key, value); Permission ret = super.put(key, inject(value));
update(); update();
return ret; return ret;
} }
@ -103,11 +122,36 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
} }
@Override @Override
public Permission putIfAbsent(String key, Permission value) { public Permission remove(@Nullable Object object) {
this.plugin.getPermissionRegistry().insert(key); if (object == null) {
Permission ret = super.putIfAbsent(key, value); return null;
update(); }
return ret; return uninject(super.remove(object));
}
@Override
public boolean remove(Object key, Object value) {
return key != null && value != null && super.remove(key, uninject(((Permission) value)));
}
// check for null
@Override
public boolean containsKey(@Nullable Object key) {
return key != null && super.containsKey(key);
}
@Override
public boolean containsValue(@Nullable Object value) {
return value != null && super.containsValue(value);
}
@Override
public Permission get(@Nullable Object key) {
if (key == null) {
return null;
}
return super.get(key);
} }
private final class ChildPermissionResolver implements Function<String, Map<String, Boolean>> { private final class ChildPermissionResolver implements Function<String, Map<String, Boolean>> {
@ -146,4 +190,81 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
} }
} }
private Permission inject(Permission permission) {
if (permission == null) {
return null;
}
try {
//noinspection unchecked
Map<String, Boolean> children = (Map<String, Boolean>) PERMISSION_CHILDREN_FIELD.get(permission);
while (children instanceof PermissionNotifyingChildrenMap) {
children = ((PermissionNotifyingChildrenMap) children).delegate;
}
PermissionNotifyingChildrenMap notifyingChildren = new PermissionNotifyingChildrenMap(children);
PERMISSION_CHILDREN_FIELD.set(permission, notifyingChildren);
} catch (Exception e) {
e.printStackTrace();
}
return permission;
}
private Permission uninject(Permission permission) {
if (permission == null) {
return null;
}
try {
//noinspection unchecked
Map<String, Boolean> children = (Map<String, Boolean>) PERMISSION_CHILDREN_FIELD.get(permission);
while (children instanceof PermissionNotifyingChildrenMap) {
children = ((PermissionNotifyingChildrenMap) children).delegate;
}
PERMISSION_CHILDREN_FIELD.set(permission, children);
} catch (Exception e) {
e.printStackTrace();
}
return permission;
}
private final class PermissionNotifyingChildrenMap extends ForwardingMap<String, Boolean> {
private final Map<String, Boolean> delegate;
PermissionNotifyingChildrenMap(Map<String, Boolean> delegate) {
this.delegate = delegate;
}
@Override
protected Map<String, Boolean> delegate() {
return this.delegate;
}
@Override
public Boolean put(@NonNull String key, @NonNull Boolean value) {
Boolean ret = super.put(key, value);
LPPermissionMap.this.update();
return ret;
}
@Override
public void putAll(@NonNull Map<? extends String, ? extends Boolean> map) {
super.putAll(map);
LPPermissionMap.this.update();
}
@Override
public Boolean remove(@NonNull Object object) {
Boolean ret = super.remove(object);
LPPermissionMap.this.update();
return ret;
}
@Override
public void clear() {
super.clear();
LPPermissionMap.this.update();
}
}
} }