diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java index 9c2361cfb..686f5f257 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java @@ -58,6 +58,7 @@ import me.lucko.luckperms.common.managers.impl.GenericUserManager; import me.lucko.luckperms.common.messaging.RedisMessaging; import me.lucko.luckperms.common.storage.Storage; import me.lucko.luckperms.common.storage.StorageFactory; +import me.lucko.luckperms.common.tasks.CacheHousekeepingTask; import me.lucko.luckperms.common.tasks.ExpireTemporaryTask; import me.lucko.luckperms.common.tasks.UpdateTask; import me.lucko.luckperms.common.utils.BufferedRequest; @@ -252,6 +253,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { // register tasks getServer().getScheduler().runTaskTimerAsynchronously(this, new ExpireTemporaryTask(this), 60L, 60L); + getServer().getScheduler().runTaskTimerAsynchronously(this, new CacheHousekeepingTask(this), 2400L, 2400L); // register permissions registerPermissions(getConfiguration().isCommandsAllowOp() ? PermissionDefault.OP : PermissionDefault.FALSE); diff --git a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java index e1cfc1608..54256e2c9 100644 --- a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java +++ b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java @@ -51,6 +51,7 @@ import me.lucko.luckperms.common.managers.impl.GenericUserManager; import me.lucko.luckperms.common.messaging.RedisMessaging; import me.lucko.luckperms.common.storage.Storage; import me.lucko.luckperms.common.storage.StorageFactory; +import me.lucko.luckperms.common.tasks.CacheHousekeepingTask; import me.lucko.luckperms.common.tasks.ExpireTemporaryTask; import me.lucko.luckperms.common.tasks.UpdateTask; import me.lucko.luckperms.common.utils.BufferedRequest; @@ -187,6 +188,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin { // register tasks getProxy().getScheduler().schedule(this, new ExpireTemporaryTask(this), 3L, 3L, TimeUnit.SECONDS); + getProxy().getScheduler().schedule(this, new CacheHousekeepingTask(this), 2L, 2L, TimeUnit.MINUTES); getLog().info("Successfully loaded."); } diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/UserCache.java b/common/src/main/java/me/lucko/luckperms/common/caching/UserCache.java index a4447f040..944435650 100644 --- a/common/src/main/java/me/lucko/luckperms/common/caching/UserCache.java +++ b/common/src/main/java/me/lucko/luckperms/common/caching/UserCache.java @@ -41,6 +41,7 @@ import me.lucko.luckperms.common.core.model.User; import me.lucko.luckperms.common.utils.ExtractedContexts; import java.util.Set; +import java.util.concurrent.TimeUnit; /** * Holds an easily accessible cache of a user's data in a number of contexts @@ -59,6 +60,7 @@ public class UserCache implements UserData { private final CalculatorFactory calculatorFactory; private final LoadingCache permission = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) .build(new CacheLoader() { @Override public PermissionCache load(Contexts contexts) { @@ -73,6 +75,7 @@ public class UserCache implements UserData { }); private final LoadingCache meta = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) .build(new CacheLoader() { @Override public MetaCache load(Contexts contexts) { @@ -153,4 +156,9 @@ public class UserCache implements UserData { permission.asMap().values().forEach(PermissionData::invalidateCache); } + public void cleanup() { + permission.cleanUp(); + meta.cleanUp(); + } + } diff --git a/common/src/main/java/me/lucko/luckperms/common/core/model/PermissionHolder.java b/common/src/main/java/me/lucko/luckperms/common/core/model/PermissionHolder.java index 4ad63a75a..64153b1fb 100644 --- a/common/src/main/java/me/lucko/luckperms/common/core/model/PermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/core/model/PermissionHolder.java @@ -76,6 +76,7 @@ import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; @@ -142,6 +143,7 @@ public abstract class PermissionHolder { /* External Caches - may depend on the state of other instances. */ private LoadingCache> getAllNodesCache = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) .build(new CacheLoader>() { @Override public SortedSet load(GetAllNodesHolder getAllNodesHolder) { @@ -149,6 +151,7 @@ public abstract class PermissionHolder { } }); private LoadingCache> getAllNodesFilteredCache = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) .build(new CacheLoader>() { @Override public Set load(ExtractedContexts extractedContexts) throws Exception { @@ -156,6 +159,7 @@ public abstract class PermissionHolder { } }); private LoadingCache> exportNodesCache = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) .build(new CacheLoader>() { @Override public Map load(ExportNodesHolder exportNodesHolder) throws Exception { @@ -167,6 +171,12 @@ public abstract class PermissionHolder { /* Caching apply methods. Are just called by the caching instances to gather data about the instance. */ + protected void forceCleanup() { + getAllNodesCache.cleanUp(); + getAllNodesFilteredCache.cleanUp(); + exportNodesCache.cleanUp(); + } + private void invalidateCache(boolean enduring) { if (enduring) { enduringCache.invalidate(); diff --git a/common/src/main/java/me/lucko/luckperms/common/core/model/User.java b/common/src/main/java/me/lucko/luckperms/common/core/model/User.java index e9f0cf2b9..159d52bad 100644 --- a/common/src/main/java/me/lucko/luckperms/common/core/model/User.java +++ b/common/src/main/java/me/lucko/luckperms/common/core/model/User.java @@ -149,4 +149,12 @@ public class User extends PermissionHolder implements Identifiable + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.tasks; + +import lombok.RequiredArgsConstructor; + +import me.lucko.luckperms.common.LuckPermsPlugin; +import me.lucko.luckperms.common.core.model.User; + +@RequiredArgsConstructor +public class CacheHousekeepingTask implements Runnable { + private final LuckPermsPlugin plugin; + + @Override + public void run() { + for (User user : plugin.getUserManager().getAll().values()) { + user.cleanup(); + } + } +} diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java index a922dbb2b..f3be15e82 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java @@ -48,6 +48,7 @@ import me.lucko.luckperms.common.managers.impl.GenericTrackManager; import me.lucko.luckperms.common.messaging.RedisMessaging; import me.lucko.luckperms.common.storage.Storage; import me.lucko.luckperms.common.storage.StorageFactory; +import me.lucko.luckperms.common.tasks.CacheHousekeepingTask; import me.lucko.luckperms.common.tasks.ExpireTemporaryTask; import me.lucko.luckperms.common.tasks.UpdateTask; import me.lucko.luckperms.common.utils.BufferedRequest; @@ -60,6 +61,7 @@ import me.lucko.luckperms.sponge.contexts.WorldCalculator; import me.lucko.luckperms.sponge.managers.SpongeGroupManager; import me.lucko.luckperms.sponge.managers.SpongeUserManager; import me.lucko.luckperms.sponge.service.LuckPermsService; +import me.lucko.luckperms.sponge.service.ServiceCacheHousekeepingTask; import me.lucko.luckperms.sponge.timings.LPTimings; import me.lucko.luckperms.sponge.utils.VersionData; @@ -250,6 +252,9 @@ public class LPSpongePlugin implements LuckPermsPlugin { // register tasks scheduler.createTaskBuilder().async().intervalTicks(60L).execute(new ExpireTemporaryTask(this)).submit(this); + scheduler.createTaskBuilder().async().intervalTicks(2400L).execute(new CacheHousekeepingTask(this)).submit(this); + scheduler.createTaskBuilder().async().intervalTicks(2400L).execute(new ServiceCacheHousekeepingTask(service)).submit(this); + scheduler.createTaskBuilder().async().intervalTicks(2400L).execute(() -> userManager.performCleanup()).submit(this); getLog().info("Successfully loaded."); } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/managers/SpongeUserManager.java b/sponge/src/main/java/me/lucko/luckperms/sponge/managers/SpongeUserManager.java index 7f70b06d7..7930f7a44 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/managers/SpongeUserManager.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/managers/SpongeUserManager.java @@ -52,6 +52,7 @@ import org.spongepowered.api.service.permission.PermissionService; import co.aikar.timings.Timing; import java.util.Collection; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -83,6 +84,17 @@ public class SpongeUserManager implements UserManager, LPSubjectCollection { new SpongeUser(id.getUuid(), id.getUsername(), plugin); } + public void performCleanup() { + Set set = new HashSet<>(); + for (Map.Entry user : objects.asMap().entrySet()) { + if (user.getValue().getSpongeData().shouldCleanup()) { + set.add(user.getKey()); + } + } + + objects.invalidateAll(set); + } + /* ------------------------------------------ * Manager methods * ------------------------------------------ */ @@ -109,7 +121,6 @@ public class SpongeUserManager implements UserManager, LPSubjectCollection { @Override public void unload(User t) { - // TODO override if (t != null) { objects.invalidate(t.getId()); } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/model/SpongeGroup.java b/sponge/src/main/java/me/lucko/luckperms/sponge/model/SpongeGroup.java index 28d847956..887f8b076 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/model/SpongeGroup.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/model/SpongeGroup.java @@ -54,6 +54,7 @@ import co.aikar.timings.Timing; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; public class SpongeGroup extends Group { @@ -77,6 +78,7 @@ public class SpongeGroup extends Group { private final LuckPermsSubjectData transientSubjectData; private final LoadingCache permissionCache = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) .build(new CacheLoader() { @Override public NodeTree load(ContextSet contexts) { @@ -90,6 +92,7 @@ public class SpongeGroup extends Group { }); private final LoadingCache> parentCache = CacheBuilder.newBuilder() + .expireAfterWrite(10, TimeUnit.MINUTES) .build(new CacheLoader>() { @Override public Set load(ContextSet contexts) { @@ -120,6 +123,12 @@ public class SpongeGroup extends Group { }); } + @Override + public void performCleanup() { + permissionCache.cleanUp(); + parentCache.cleanUp(); + } + @Override public String getIdentifier() { return parent.getObjectName(); diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/model/SpongeUser.java b/sponge/src/main/java/me/lucko/luckperms/sponge/model/SpongeUser.java index 95c3518ce..4e3c901c8 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/model/SpongeUser.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/model/SpongeUser.java @@ -74,6 +74,8 @@ public class SpongeUser extends User { @Getter private final LuckPermsSubjectData transientSubjectData; + private long lastUse = System.currentTimeMillis(); + private UserSubject(LPSpongePlugin plugin, SpongeUser parent) { this.parent = parent; this.plugin = plugin; @@ -81,6 +83,16 @@ public class SpongeUser extends User { this.transientSubjectData = new LuckPermsSubjectData(false, plugin.getService(), parent, this); } + private void logUsage() { + lastUse = System.currentTimeMillis(); + } + + public boolean shouldCleanup() { + long now = System.currentTimeMillis(); + // Expire after 10 minutes of idle + return (now - lastUse) > 600000; + } + private boolean hasData() { return parent.getUserData() != null; } @@ -114,6 +126,7 @@ public class SpongeUser extends User { @Override public Tristate getPermissionValue(ContextSet contexts, String permission) { + logUsage(); try (Timing ignored = plugin.getTimings().time(LPTiming.USER_GET_PERMISSION_VALUE)) { if (!hasData()) { return Tristate.UNDEFINED; @@ -125,6 +138,7 @@ public class SpongeUser extends User { @Override public boolean isChildOf(ContextSet contexts, SubjectReference parent) { + logUsage(); try (Timing ignored = plugin.getTimings().time(LPTiming.USER_IS_CHILD_OF)) { return parent.getCollection().equals(PermissionService.SUBJECTS_GROUP) && getPermissionValue(contexts, "group." + parent.getIdentifier()).asBoolean(); } @@ -132,6 +146,7 @@ public class SpongeUser extends User { @Override public Set getParents(ContextSet contexts) { + logUsage(); try (Timing ignored = plugin.getTimings().time(LPTiming.USER_GET_PARENTS)) { ImmutableSet.Builder subjects = ImmutableSet.builder(); @@ -157,6 +172,7 @@ public class SpongeUser extends User { @Override public Optional getOption(ContextSet contexts, String s) { + logUsage(); try (Timing ignored = plugin.getTimings().time(LPTiming.USER_GET_OPTION)) { if (hasData()) { MetaData data = parent.getUserData().getMetaData(plugin.getService().calculateContexts(contexts)); @@ -188,6 +204,7 @@ public class SpongeUser extends User { @Override public ContextSet getActiveContextSet() { + logUsage(); try (Timing ignored = plugin.getTimings().time(LPTiming.USER_GET_ACTIVE_CONTEXTS)) { return plugin.getContextManager().getApplicableContext(this); } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/ServiceCacheHousekeepingTask.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/ServiceCacheHousekeepingTask.java new file mode 100644 index 000000000..9e9109926 --- /dev/null +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/ServiceCacheHousekeepingTask.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.sponge.service; + +import lombok.RequiredArgsConstructor; + +import me.lucko.luckperms.sponge.service.base.LPSubject; +import me.lucko.luckperms.sponge.service.base.LPSubjectCollection; + +@RequiredArgsConstructor +public class ServiceCacheHousekeepingTask implements Runnable { + private final LuckPermsService service; + + @Override + public void run() { + for (LPSubjectCollection collection : service.getCollections().values()) { + for (LPSubject subject : collection.getSubjects()) { + subject.performCleanup(); + } + } + } +} diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/base/LPSubject.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/base/LPSubject.java index 02f4ef980..afc9b34c4 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/base/LPSubject.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/base/LPSubject.java @@ -60,6 +60,10 @@ public interface LPSubject extends Subject { LuckPermsService getService(); + default void performCleanup() { + + } + default SubjectReference toReference() { return SubjectReference.of(getParentCollection().getCollection(), getIdentifier()); } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/CalculatedSubjectData.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/CalculatedSubjectData.java index ff995c8e9..e935aa131 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/CalculatedSubjectData.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/CalculatedSubjectData.java @@ -52,6 +52,7 @@ import java.util.Objects; import java.util.Set; import java.util.SortedMap; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; @RequiredArgsConstructor public class CalculatedSubjectData implements LPSubjectData { @@ -102,6 +103,7 @@ public class CalculatedSubjectData implements LPSubjectData { private final Map> permissions = new ConcurrentHashMap<>(); private final LoadingCache permissionCache = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) .build(new CacheLoader() { @Override public CalculatorHolder load(ContextSet contexts) { @@ -119,6 +121,10 @@ public class CalculatedSubjectData implements LPSubjectData { private final Map> parents = new ConcurrentHashMap<>(); private final Map> options = new ConcurrentHashMap<>(); + public void cleanup() { + permissionCache.cleanUp(); + } + public Tristate getPermissionValue(ContextSet contexts, String permission) { return permissionCache.getUnchecked(contexts).getCalculator().getPermissionValue(permission); } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedSubject.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedSubject.java index 6a2882f2c..5aa13b787 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedSubject.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedSubject.java @@ -51,6 +51,7 @@ import java.io.IOException; import java.util.HashSet; import java.util.Optional; import java.util.Set; +import java.util.concurrent.TimeUnit; /** * A simple persistable Subject implementation @@ -66,6 +67,7 @@ public class PersistedSubject implements LPSubject { private final CalculatedSubjectData transientSubjectData; private final LoadingCache permissionLookupCache = CacheBuilder.newBuilder() + .expireAfterAccess(20, TimeUnit.MINUTES) .build(new CacheLoader() { @Override public Tristate load(PermissionLookup lookup) { @@ -73,6 +75,7 @@ public class PersistedSubject implements LPSubject { } }); private final LoadingCache> parentLookupCache = CacheBuilder.newBuilder() + .expireAfterAccess(20, TimeUnit.MINUTES) .build(new CacheLoader>() { @Override public Set load(ImmutableContextSet contexts) { @@ -80,6 +83,7 @@ public class PersistedSubject implements LPSubject { } }); private final LoadingCache> optionLookupCache = CacheBuilder.newBuilder() + .expireAfterAccess(20, TimeUnit.MINUTES) .build(new CacheLoader>() { @Override public Optional load(OptionLookup lookup) { @@ -115,6 +119,15 @@ public class PersistedSubject implements LPSubject { service.getLocalOptionCaches().add(optionLookupCache); } + @Override + public void performCleanup() { + this.subjectData.cleanup(); + this.transientSubjectData.cleanup(); + this.permissionLookupCache.cleanUp(); + this.parentLookupCache.cleanUp(); + this.optionLookupCache.cleanUp(); + } + public void loadData(SubjectDataHolder dataHolder) { subjectData.setSave(false); dataHolder.copyTo(subjectData);