Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package com.sk89q.worldguard.bukkit.session;

import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldguard.LocalPlayer;
import com.sk89q.worldguard.WorldGuard;
Expand All @@ -32,6 +33,7 @@
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldUnloadEvent;

import java.util.function.Consumer;

Expand Down Expand Up @@ -96,6 +98,16 @@ public void accept(Object ignored) {
task.run();
}
}

// Force eviction of expired bypass/session entries. Without this the
// caches only evict lazily, so an idle entry can keep a player (and
// their world) reachable long after its logical lifetime.
cleanUpCaches();
}

@EventHandler
public void onWorldUnload(WorldUnloadEvent event) {
forgetWorld(BukkitAdapter.adapt(event.getWorld()));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,26 @@ public Session getIfPresent(LocalPlayer player) {
return getIfPresentInternal(new CacheKey(player));
}

@Override
public void forgetWorld(World world) {
checkNotNull(world, "world");
bypassCache.asMap().keySet().removeIf(tuple -> tuple.getWorld().equals(world));
}

/**
* Force the caches to evict any entries that have already expired.
*
* <p>The bypass cache uses {@code expireAfterWrite} and the session cache
* uses {@code expireAfterAccess}, but Guava only evicts expired entries
* lazily during cache operations. When the caches go idle, an expired
* entry can keep a strong reference to a player or world alive long after
* its logical lifetime. Calling this periodically bounds that retention.</p>
*/
protected void cleanUpCaches() {
bypassCache.cleanUp();
sessions.cleanUp();
}

private Session getIfPresentInternal(CacheKey cacheKey) {
@Nullable Session session = sessions.getIfPresent(cacheKey);
if (session != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ public void uninitialize(LocalPlayer player) {
for (Handler handler : handlers.values()) {
handler.uninitialize(player, location, set);
}

lastValid = null;
lastRegionSet = null;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,18 @@ public interface SessionManager {
*/
@Nullable Session getIfPresent(LocalPlayer player);

/**
* Forget any cached bypass information that references the given world.
*
* <p>This should be called when a world is unloaded so that the bypass
* cache does not retain a strong reference to the world (and the players
* that were last checked in it) until the cache entries happen to
* expire.</p>
*
* @param world The world to forget
*/
void forgetWorld(World world);

/**
* Get a player's session. A session will be created if there is no
* existing session for the player.
Expand Down