Overhaul user cache to fix op losing issue

This commit is contained in:
Sotr
2019-04-09 14:50:03 +08:00
parent fcf97fa61e
commit 0d05c57c04
17 changed files with 120 additions and 218 deletions

View File

@@ -132,10 +132,10 @@ public final class SimplePluginManager implements PluginManager {
updateDirectory = new File(directory, server.getUpdateFolder());
}
Map<String, File> plugins = new HashMap<String, File>();
Set<String> loadedPlugins = new HashSet<String>();
Map<String, Collection<String>> dependencies = new HashMap<String, Collection<String>>();
Map<String, Collection<String>> softDependencies = new HashMap<String, Collection<String>>();
Map<String, File> plugins = HashObjObjMaps.newMutableMap(); // Akarin
Set<String> loadedPlugins = HashObjSets.newMutableSet(); // Akarin
Map<String, Collection<String>> dependencies = HashObjObjMaps.newMutableMap(); // Akarin
Map<String, Collection<String>> softDependencies = HashObjObjMaps.newMutableMap(); // Akarin
// This is where it figures out all possible plugins
for (File file : directory.listFiles()) {

View File

@@ -1,21 +1,18 @@
package com.destroystokyo.paper.profile;
import com.destroystokyo.paper.PaperConfig;
import com.google.common.base.Charsets;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.ProfileLookupCallback;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import net.minecraft.server.AkarinUserCache;
import net.minecraft.server.EntityHuman;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.UserCache;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.spigotmc.SpigotConfig;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
@@ -172,29 +169,22 @@ public class CraftPlayerProfile implements PlayerProfile {
}
}
*/
ProfileLookupCallback callback = new ProfileLookupCallback() {
@Override
public void onProfileLookupSucceeded(GameProfile gameprofile) {
profile = gameprofile;
}
@Override
public void onProfileLookupFailed(GameProfile gameprofile, Exception ex) {
;
}
};
if (lookupName) {
userCache.acquire(name, callback, true);
} else {
GameProfile peeked = userCache.peek(name);
if (peeked.getName() == null) {
userCache.acquire(name, callback, true);
if (profile.getId() == null) {
if (lookupName) {
profile = userCache.acquire(name);
} else {
this.profile = peeked;
GameProfile peeked = userCache.peek(name);
if (peeked != null)
profile = peeked;
}
}
return true;
if (profile.getName() == null)
profile = userCache.acquire(name);
return profile.isComplete();
// Akarin end
}

View File

@@ -12,7 +12,9 @@ import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.commons.io.IOUtils;
@@ -42,13 +44,14 @@ public class AkarinUserCache {
private final static Logger LOGGER = LogManager.getLogger("Akarin");
public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
// Used to reduce date create
// To reduce date creation
private final static long RECREATE_DATE_INTERVAL = TimeUnit.MILLISECONDS.convert(10, TimeUnit.MINUTES);
private static long lastWarpExpireDate;
private static Date lastExpireDate;
/**
* All user caches, Username -> Entry(profile and expire date included)
* All user caches
* String username -> UserCacheEntry cacheEntry (profile and expire date)
*/
private final Cache<String, UserCacheEntry> profiles = Caffeine.newBuilder().maximumSize(SpigotConfig.userCacheCap).build();
@@ -66,8 +69,7 @@ public class AkarinUserCache {
lastWarpExpireDate = now;
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(now);
calendar.add(Calendar.DAY_OF_YEAR, AkarinGlobalConfig.userCacheExpireDays);
calendar.add(Calendar.SECOND, AkarinGlobalConfig.userCacheExpireDays);
return lastExpireDate = calendar.getTime();
}
@@ -82,35 +84,37 @@ public class AkarinUserCache {
return new UserCacheEntry(entry.getProfile(), createExpireDate(true));
}
private static GameProfile lookup(GameProfileRepository profileRepo, String username, ProfileLookupCallback callback, boolean async) {
private static GameProfile lookup(GameProfileRepository profileRepo, String username, @Nonnull Consumer<GameProfile> cachedCallbackHandler, boolean async) {
GameProfile[] gameProfile = new GameProfile[1];
ProfileLookupCallback callbackHandler = new ProfileLookupCallback() {
ProfileLookupCallback handler = new ProfileLookupCallback() {
@Override
public void onProfileLookupSucceeded(GameProfile gameprofile) {
if (async)
callback.onProfileLookupSucceeded(gameprofile);
else
gameProfile[0] = gameprofile;
cachedCallbackHandler.accept(gameprofile);
gameProfile[0] = gameprofile;
}
@Override
public void onProfileLookupFailed(GameProfile gameprofile, Exception ex) {
LOGGER.warn("Failed to lookup player {}, using local UUID.", gameprofile.getName());
if (async)
callback.onProfileLookupSucceeded(new GameProfile(EntityHuman.getOfflineUUID(username.toLowerCase(Locale.ROOT)), username));
else
gameProfile[0] = new GameProfile(EntityHuman.getOfflineUUID(username), username);
LOGGER.warn("Failed to lookup profile for player {}, applying offline UUID.", gameprofile.getName());
GameProfile offline = createOfflineProfile(username);
cachedCallbackHandler.accept(offline);
gameProfile[0] = offline;
}
};
Runnable find = () -> profileRepo.findProfilesByNames(new String[] { username }, Agent.MINECRAFT, callbackHandler);
Runnable find = () -> profileRepo.findProfilesByNames(new String[]{username}, Agent.MINECRAFT, handler);
if (async) {
AkarinAsyncExecutor.scheduleAsyncTask(find);
return null;
} else {
find.run();
return gameProfile[0];
}
return gameProfile[0];
}
private static GameProfile createOfflineProfile(String username) {
String usernameOffline = username.toLowerCase(Locale.ROOT);
GameProfile offlineProfile = new GameProfile(EntityHuman.getOfflineUUID(usernameOffline), username);
return offlineProfile;
}
public AkarinUserCache(GameProfileRepository repo, File file, Gson gson) {
@@ -123,63 +127,48 @@ public class AkarinUserCache {
this.load();
}
private GameProfile lookupAndCache(String username, ProfileLookupCallback callback, boolean async) {
private GameProfile lookupAndCache(String username, @Nullable Consumer<GameProfile> callback, boolean async) {
return lookupAndCache(username, callback, createExpireDate(false), async);
}
private GameProfile lookupAndCache(String username, ProfileLookupCallback callback, Date date, boolean async) {
ProfileLookupCallback callbackHandler = new ProfileLookupCallback() {
@Override
public void onProfileLookupSucceeded(GameProfile gameprofile) {
profiles.put(username, new UserCacheEntry(gameprofile, date));
if (async)
callback.onProfileLookupSucceeded(gameprofile);
if(!org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly)
save();
}
@Override
public void onProfileLookupFailed(GameProfile gameprofile, Exception ex) {
;
}
private GameProfile lookupAndCache(String username, @Nullable Consumer<GameProfile> callback, Date expireDate, boolean async) {
Consumer<GameProfile> cachedCallbackHandler = profile -> {
profiles.put(username, new UserCacheEntry(profile, expireDate));
if (async)
callback.accept(profile);
if (!org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly)
save();
};
return lookup(profileHandler, username, callbackHandler, async);
return lookup(profileHandler, username, cachedCallbackHandler, async);
}
public GameProfile acquire(String username) {
return acquire(username, null, false);
}
public GameProfile acquire(String username, ProfileLookupCallback callback) {
public GameProfile acquire(String username, @Nonnull Consumer<GameProfile> callback) {
return acquire(username, callback, true);
}
public GameProfile acquire(String username, ProfileLookupCallback callback, boolean async) {
public GameProfile acquire(String username, @Nullable Consumer<GameProfile> callback, boolean async) {
if (StringUtils.isBlank(username))
throw new UnsupportedOperationException("Blank username");
if (!isOnlineMode()) {
String usernameOffline = username.toLowerCase(Locale.ROOT);
GameProfile offlineProfile = new GameProfile(EntityHuman.getOfflineUUID(usernameOffline), username);
if (async) callback.onProfileLookupSucceeded(offlineProfile);
return offlineProfile;
}
if (!isOnlineMode())
return createOfflineProfile(username);
UserCacheEntry entry = profiles.getIfPresent(username);
if (entry != null) {
if (isExpired(entry)) {
profiles.invalidate(username);
return lookupAndCache(username, callback, async);
} else {
if (async) {
callback.onProfileLookupSucceeded(entry.getProfile());
return null;
} else {
return entry.getProfile();
}
if (async)
callback.accept(entry.getProfile());
return entry.getProfile();
}
}
return lookupAndCache(username, callback, async);
@@ -187,55 +176,46 @@ public class AkarinUserCache {
@Nullable
public GameProfile peek(String username) {
if (!isOnlineMode()) {
String usernameOffline = username.toLowerCase(Locale.ROOT);
GameProfile offlineProfile = new GameProfile(EntityHuman.getOfflineUUID(usernameOffline), username);
return offlineProfile;
}
if (!isOnlineMode())
return createOfflineProfile(username);
UserCacheEntry entry = profiles.getIfPresent(username);
return entry == null ? null : entry.getProfile();
}
protected void offer(GameProfile profile) {
if (!isOnlineMode())
return;
offer(profile, createExpireDate(false));
if (isOnlineMode())
offer(profile, createExpireDate(false));
}
protected void offer(GameProfile profile, Date date) {
if (!isOnlineMode())
return;
private void offer(GameProfile profile, Date date) {
String username = profile.getName();
UserCacheEntry entry = profiles.getIfPresent(username);
// Refresh expire date or create new entry
entry = entry != null ? refreshExpireDate(entry) : new UserCacheEntry(profile, date);
profiles.put(username, entry);
if (!SpigotConfig.saveUserCacheOnStopOnly)
this.save();
}
private void offer(UserCacheEntry entry) {
if (isOnlineMode() && !isExpired(entry))
if (!isExpired(entry))
profiles.put(entry.getProfile().getName(), entry);
}
protected String[] usernames() {
return isOnlineMode() ? profiles.asMap().keySet().toArray(new String[profiles.asMap().size()]) : new String[0];
}
protected void load() {
if (!isOnlineMode())
return;
BufferedReader reader = null;
try {
reader = Files.newReader(userCacheFile, Charsets.UTF_8);
List<UserCacheEntry> entries = this.gson.fromJson(reader, UserCache.PARAMETERIZED_TYPE);
profiles.invalidateAll();
reader = Files.newReader(userCacheFile, Charsets.UTF_8);
List<UserCacheEntry> entries;
synchronized (this.userCacheFile) {
entries = this.gson.fromJson(reader, UserCache.PARAMETERIZED_TYPE);
}
if (entries != null && !entries.isEmpty())
Lists.reverse(entries).forEach(this::offer);
} catch (FileNotFoundException e) {
@@ -251,10 +231,6 @@ public class AkarinUserCache {
}
protected void save() {
save(true);
}
protected void save(boolean async) {
if (!isOnlineMode())
return;
@@ -264,10 +240,11 @@ public class AkarinUserCache {
try {
writer = Files.newWriter(this.userCacheFile, Charsets.UTF_8);
writer.write(jsonString);
return;
synchronized (this.userCacheFile) {
writer.write(jsonString);
}
} catch (FileNotFoundException e) {
return;
;
} catch (IOException io) {
;
} finally {
@@ -275,13 +252,10 @@ public class AkarinUserCache {
}
};
if (async)
AkarinAsyncExecutor.scheduleAsyncTask(save);
else
save.run();
AkarinAsyncExecutor.scheduleSingleAsyncTask(save);
}
protected List<UserCacheEntry> entries() {
private List<UserCacheEntry> entries() {
return isOnlineMode() ? Lists.newArrayList(profiles.asMap().values()) : Collections.emptyList();
}
}

View File

@@ -11,6 +11,7 @@ import com.destroystokyo.paper.exception.ServerInternalException;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
import com.koloboke.collect.map.hash.HashObjObjMaps;
import io.akarin.server.core.AkarinAsyncExecutor;
import io.akarin.server.core.AkarinGlobalConfig;
@@ -952,7 +953,7 @@ public class Chunk implements IChunkAccess {
// Paper start
DuplicateUUIDMode mode = world.paperConfig.duplicateUUIDMode;
if (mode == DuplicateUUIDMode.WARN || mode == DuplicateUUIDMode.DELETE || mode == DuplicateUUIDMode.SAFE_REGEN) {
Map<UUID, Entity> thisChunk = new HashMap<>();
Map<UUID, Entity> thisChunk = HashObjObjMaps.newMutableMap(); // Akarin
for (Iterator<Entity> iterator = ((List<Entity>) entityslice).iterator(); iterator.hasNext(); ) {
Entity entity = iterator.next();
if (entity.dead || entity.valid) continue;

View File

@@ -166,6 +166,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer
this.b("127.0.0.1");
} else {
this.setOnlineMode(this.propertyManager.getBoolean("online-mode", true));
UserCache.a(this.propertyManager.getBoolean("online-mode", true)); // Akarin
this.f(this.propertyManager.getBoolean("prevent-proxy-connections", false));
this.b(this.propertyManager.getString("server-ip", ""));
}
@@ -297,7 +298,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer
this.propertyManager.setProperty("max-build-height", this.getMaxBuildHeight());
TileEntitySkull.a(this.getUserCache());
TileEntitySkull.a(this.ap());
UserCache.a(this.getOnlineMode());
//UserCache.a(this.getOnlineMode()); // Akarin
DedicatedServer.LOGGER.info("Preparing level \"{}\"", this.getWorld());
JsonObject jsonobject = new JsonObject();

View File

@@ -328,8 +328,8 @@ public class Explosion {
return this.world.a(vec3d, aabb);
}
CacheKey key = new CacheKey(this, aabb);
Float blockDensity = this.world.explosionDensityCache.get(key);
if (blockDensity == null) {
float blockDensity = 0f; // Akarin
if (!this.world.explosionDensityCache.containsKey(key)) { // Akarin
blockDensity = this.world.a(vec3d, aabb);
this.world.explosionDensityCache.put(key, blockDensity);
}

View File

@@ -3,13 +3,17 @@ package net.minecraft.server;
// CraftBukkit start
import java.net.InetAddress;
import java.util.HashMap;
import com.koloboke.collect.map.hash.HashObjLongMap;
import com.koloboke.collect.map.hash.HashObjLongMaps;
import com.koloboke.function.ObjLongPredicate;
// CraftBukkit end
public class HandshakeListener implements PacketHandshakingInListener {
private static final com.google.gson.Gson gson = new com.google.gson.Gson(); // Spigot
// CraftBukkit start - add fields
private static final HashMap<InetAddress, Long> throttleTracker = new HashMap<InetAddress, Long>();
private static final HashObjLongMap<InetAddress> throttleTracker = HashObjLongMaps.newMutableMap();
private static int throttleCounter = 0;
// CraftBukkit end
@@ -49,13 +53,7 @@ public class HandshakeListener implements PacketHandshakingInListener {
throttleCounter = 0;
// Cleanup stale entries
java.util.Iterator iter = throttleTracker.entrySet().iterator();
while (iter.hasNext()) {
java.util.Map.Entry<InetAddress, Long> entry = (java.util.Map.Entry) iter.next();
if (entry.getValue() > connectionThrottle) {
iter.remove();
}
}
throttleTracker.removeIf((InetAddress, value) -> value > connectionThrottle); // Akarin
}
}
} catch (Throwable t) {

View File

@@ -33,6 +33,7 @@ import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bukkit.Bukkit;
public class JsonList<K, V extends JsonListEntry<K>> {
@@ -40,7 +41,7 @@ public class JsonList<K, V extends JsonListEntry<K>> {
protected final Gson b;
private final File c;
// Paper - replace HashMap is ConcurrentHashMap
private final Map<String, V> d = Maps.newConcurrentMap(); private final Map<String, V> getBackingMap() { return this.d; } // Paper - OBFHELPER
protected final Map<String, V> d = Maps.newConcurrentMap(); private final Map<String, V> getBackingMap() { return this.d; } // Paper - OBFHELPER
private boolean e = true;
private static final ParameterizedType f = new ParameterizedType() {
public Type[] getActualTypeArguments() {

View File

@@ -695,7 +695,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
// Spigot start
if (org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) {
LOGGER.info("Saving usercache.json");
this.getModernUserCache().save(false); // Paper // Akarin
this.getModernUserCache().save(); // Paper // Akarin - force to single thread to ensure safety
}
// Spigot end
}

View File

@@ -269,7 +269,7 @@ public class NameReferencingFileConverter {
public static String a(final MinecraftServer minecraftserver, String s) {
if (!UtilColor.b(s) && s.length() <= 16) {
GameProfile gameprofile = minecraftserver.getModernUserCache().acquire(s); // Akarin
GameProfile gameprofile = minecraftserver.getModernUserCache().peek(s); // Akarin
if (gameprofile != null && gameprofile.getId() != null) {
return gameprofile.getId().toString();

View File

@@ -1,10 +1,13 @@
package net.minecraft.server;
import com.google.common.collect.Lists;
import com.google.gson.JsonObject;
import com.mojang.authlib.GameProfile;
import java.io.File;
import java.util.Iterator;
import org.bukkit.Bukkit;
public class OpList extends JsonList<GameProfile, OpListEntry> {
public OpList(File file) {

View File

@@ -71,7 +71,7 @@ public class UserCache {
gsonbuilder.registerTypeHierarchyAdapter(UserCache.UserCacheEntry.class, new UserCache.BanEntrySerializer());
this.b = gsonbuilder.create();
this.b();
//this.b(); // Akarin
}
private static GameProfile a(GameProfileRepository gameprofilerepository, String s) {

View File

@@ -450,28 +450,13 @@ public class Village {
AkarinUserCache usercache = this.a.getMinecraftServer().getModernUserCache(); // Akarin
try {
// Akarin start
ProfileLookupCallback callback = new ProfileLookupCallback() {
@Override
public void onProfileLookupSucceeded(GameProfile gameprofile) {
nbttagcompound2.setString("UUID", gameprofile.getId().toString());
nbttagcompound2.setInt("S", (Integer) j.get(s));
nbttaglist1.add((NBTBase) nbttagcompound2);
}
GameProfile gameprofile = usercache.peek(s);
@Override
public void onProfileLookupFailed(GameProfile gameprofile, Exception ex) {
;
}
};
usercache.acquire(s, callback);
//if (gameprofile != null) {
// nbttagcompound2.setString("UUID", gameprofile.getId().toString());
// nbttagcompound2.setInt("S", (Integer) this.j.get(s));
// nbttaglist1.add((NBTBase) nbttagcompound2);
//}
// Akarin end
if (gameprofile != null) {
nbttagcompound2.setString("UUID", gameprofile.getId().toString());
nbttagcompound2.setInt("S", (Integer) this.j.get(s));
nbttaglist1.add((NBTBase) nbttagcompound2);
}
} catch (RuntimeException runtimeexception) {
;
}

View File

@@ -28,6 +28,8 @@ import org.apache.logging.log4j.Logger;
// CraftBukkit start
import com.google.common.collect.Maps;
import com.koloboke.collect.map.hash.HashObjFloatMap;
import com.koloboke.collect.map.hash.HashObjFloatMaps;
import com.koloboke.collect.map.hash.HashObjObjMaps;
import com.koloboke.collect.set.hash.HashObjSets;
@@ -176,7 +178,7 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
private org.spigotmc.TickLimiter entityLimiter;
private org.spigotmc.TickLimiter tileLimiter;
private int tileTickPosition;
public final Map<Explosion.CacheKey, Float> explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions
public final HashObjFloatMap<Explosion.CacheKey> explosionDensityCache = HashObjFloatMaps.newMutableMap();
public CraftWorld getWorld() {
return this.world;

View File

@@ -48,45 +48,19 @@ public class CraftProfileBanList implements org.bukkit.BanList {
Validate.notNull(target, "Ban target cannot be null");
// Akarin start
ProfileLookupCallback callback = new ProfileLookupCallback() {
@Override
public void onProfileLookupSucceeded(GameProfile profile) {
GameProfileBanEntry entry = new GameProfileBanEntry(profile, new Date(),
StringUtils.isBlank(source) ? null : source, expires,
StringUtils.isBlank(reason) ? null : reason);
MinecraftServer.getServer().getModernUserCache().acquire(target, profile -> {
GameProfileBanEntry entry = new GameProfileBanEntry(profile, new Date(),
StringUtils.isBlank(source) ? null : source, expires,
StringUtils.isBlank(reason) ? null : reason);
list.add(entry);
list.add(entry);
try {
list.save();
} catch (IOException ex) {
Bukkit.getLogger().log(Level.SEVERE, "Failed to save banned-players.json, {0}", ex.getMessage());
}
try {
list.save();
} catch (IOException ex) {
Bukkit.getLogger().log(Level.SEVERE, "Failed to save banned-players.json, {0}", ex.getMessage());
}
@Override
public void onProfileLookupFailed(GameProfile gameprofile, Exception ex) {
;
}
};
MinecraftServer.getServer().getModernUserCache().acquire(target, callback);
/*
if (profile == null) {
return null;
}
GameProfileBanEntry entry = new GameProfileBanEntry(profile, new Date(),
StringUtils.isBlank(source) ? null : source, expires,
StringUtils.isBlank(reason) ? null : reason);
list.add(entry);
try {
list.save();
} catch (IOException ex) {
Bukkit.getLogger().log(Level.SEVERE, "Failed to save banned-players.json, {0}", ex.getMessage());
}
*/
});
return null;
// Akarin end
@@ -121,18 +95,7 @@ public class CraftProfileBanList implements org.bukkit.BanList {
Validate.notNull(target, "Target cannot be null");
// Akarin start
ProfileLookupCallback callback = new ProfileLookupCallback() {
@Override
public void onProfileLookupSucceeded(GameProfile profile) {
list.remove(profile);
}
@Override
public void onProfileLookupFailed(GameProfile gameprofile, Exception ex) {
;
}
};
GameProfile profile = MinecraftServer.getServer().getModernUserCache().acquire(target, callback);
MinecraftServer.getServer().getModernUserCache().acquire(target, profile -> list.remove(profile));
//list.remove(profile);
// Akarin end
}

View File

@@ -78,23 +78,7 @@ public class CraftSkull extends CraftBlockEntityState<TileEntitySkull> implement
}
// Akarin start
ProfileLookupCallback callback = new ProfileLookupCallback() {
@Override
public void onProfileLookupSucceeded(GameProfile gameprofile) {
profile = gameprofile;
}
@Override
public void onProfileLookupFailed(GameProfile gameprofile, Exception ex) {
;
}
};
GameProfile profile = MinecraftServer.getServer().getModernUserCache().acquire(name, callback);
if (profile == null) {
return false;
}
//this.profile = profile;
MinecraftServer.getServer().getModernUserCache().acquire(name, gameProfile -> profile = gameProfile);
// Akarin end
return true;
}