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;
}
@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) {
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);
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.Nullable;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@ -49,7 +50,8 @@ import java.util.function.Function;
*
* This instance allows LuckPerms to intercept calls to
* {@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.
*
@ -57,6 +59,17 @@ import java.util.function.Function;
*/
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
private final Map<String, Permission> delegate = new ConcurrentHashMap<>();
@ -81,6 +94,8 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
private void update() {
this.trueChildPermissions.clear();
this.falseChildPermissions.clear();
this.plugin.getUserManager().invalidateAllPermissionCalculators();
this.plugin.getGroupManager().invalidateAllPermissionCalculators();
}
@Override
@ -94,7 +109,7 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
Objects.requireNonNull(value, "value");
this.plugin.getPermissionRegistry().insert(key);
Permission ret = super.put(key, value);
Permission ret = super.put(key, inject(value));
update();
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
public Permission remove(@Nullable Object object) {
if (object == null) {
return null;
}
return super.remove(object);
return uninject(super.remove(object));
}
@Override
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
public boolean containsKey(@Nullable Object 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() {
for (PermissionProcessor processor : this.processors) {
processor.invalidate();
}
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() {
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;
import me.lucko.luckperms.common.calculator.PermissionCalculator;
import me.lucko.luckperms.common.model.Group;
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();
/**
* 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);
}
@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.
*

View File

@ -25,6 +25,7 @@
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.UserIdentifier;
import me.lucko.luckperms.common.model.manager.Manager;
@ -89,4 +90,9 @@ public interface UserManager<T extends User> extends Manager<UserIdentifier, Use
*/
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;
}
@Override
public void invalidate() {
refresh();
}
}

View File

@ -88,6 +88,8 @@ public final class LPDefaultsMap {
private void invalidate(boolean op) {
getCache(op).invalidate();
this.plugin.getUserManager().invalidateAllPermissionCalculators();
this.plugin.getGroupManager().invalidateAllPermissionCalculators();
}
/**
@ -136,6 +138,13 @@ public final class LPDefaultsMap {
super.putAll(map);
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>> {

View File

@ -33,13 +33,16 @@ import me.lucko.luckperms.common.treeview.PermissionRegistry;
import me.lucko.luckperms.common.util.LoadingMap;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import cn.nukkit.permission.Permission;
import cn.nukkit.plugin.PluginManager;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
@ -56,6 +59,17 @@ import java.util.function.Function;
*/
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
private final Map<String, Permission> delegate = new ConcurrentHashMap<>();
@ -80,6 +94,8 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
private void update() {
this.trueChildPermissions.clear();
this.falseChildPermissions.clear();
this.plugin.getUserManager().invalidateAllPermissionCalculators();
this.plugin.getGroupManager().invalidateAllPermissionCalculators();
}
@Override
@ -89,8 +105,11 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
@Override
public Permission put(@NonNull String key, @NonNull Permission value) {
Objects.requireNonNull(key, "key");
Objects.requireNonNull(value, "value");
this.plugin.getPermissionRegistry().insert(key);
Permission ret = super.put(key, value);
Permission ret = super.put(key, inject(value));
update();
return ret;
}
@ -103,11 +122,36 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
}
@Override
public Permission putIfAbsent(String key, Permission value) {
this.plugin.getPermissionRegistry().insert(key);
Permission ret = super.putIfAbsent(key, value);
update();
return ret;
public Permission remove(@Nullable Object object) {
if (object == null) {
return null;
}
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>> {
@ -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();
}
}
}