--- a/net/minecraft/server/players/GameProfileCache.java +++ b/net/minecraft/server/players/GameProfileCache.java @@ -60,6 +60,11 @@ @Nullable private Executor executor; + // Paper start - Fix GameProfileCache concurrency + protected final java.util.concurrent.locks.ReentrantLock stateLock = new java.util.concurrent.locks.ReentrantLock(); + protected final java.util.concurrent.locks.ReentrantLock lookupLock = new java.util.concurrent.locks.ReentrantLock(); + // Paper end - Fix GameProfileCache concurrency + public GameProfileCache(GameProfileRepository profileRepository, File cacheFile) { this.profileRepository = profileRepository; this.file = cacheFile; @@ -67,11 +72,13 @@ } private void safeAdd(GameProfileCache.GameProfileInfo entry) { + try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency GameProfile gameprofile = entry.getProfile(); entry.setLastAccess(this.getNextOperation()); this.profilesByName.put(gameprofile.getName().toLowerCase(Locale.ROOT), entry); this.profilesByUUID.put(gameprofile.getId(), entry); + } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency } private static Optional lookupGameProfile(GameProfileRepository repository, String name) { @@ -85,10 +92,12 @@ } public void onProfileLookupFailed(String s1, Exception exception) { - atomicreference.set((Object) null); + atomicreference.set(null); // CraftBukkit - decompile error } }; + if (!org.apache.commons.lang3.StringUtils.isBlank(name) // Paper - Don't lookup a profile with a blank name + && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()) // Paper - Add setting for proxy online mode status repository.findProfilesByNames(new String[]{name}, profilelookupcallback); GameProfile gameprofile = (GameProfile) atomicreference.get(); @@ -105,7 +114,7 @@ } private static boolean usesAuthentication() { - return GameProfileCache.usesAuthentication; + return io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode(); // Paper - Add setting for proxy online mode status } public void add(GameProfile profile) { @@ -117,15 +126,29 @@ GameProfileCache.GameProfileInfo usercache_usercacheentry = new GameProfileCache.GameProfileInfo(profile, date); this.safeAdd(usercache_usercacheentry); - this.save(); + if( !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly ) this.save(true); // Spigot - skip saving if disabled // Paper - Perf: Async GameProfileCache saving } private long getNextOperation() { return this.operationCount.incrementAndGet(); } + // Paper start + public @Nullable GameProfile getProfileIfCached(String name) { + try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency + GameProfileCache.GameProfileInfo entry = this.profilesByName.get(name.toLowerCase(Locale.ROOT)); + if (entry == null) { + return null; + } + entry.setLastAccess(this.getNextOperation()); + return entry.getProfile(); + } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency + } + // Paper end + public Optional get(String name) { String s1 = name.toLowerCase(Locale.ROOT); + boolean stateLocked = true; try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByName.get(s1); boolean flag = false; @@ -141,19 +164,24 @@ if (usercache_usercacheentry != null) { usercache_usercacheentry.setLastAccess(this.getNextOperation()); optional = Optional.of(usercache_usercacheentry.getProfile()); + stateLocked = false; this.stateLock.unlock(); // Paper - Fix GameProfileCache concurrency } else { - optional = GameProfileCache.lookupGameProfile(this.profileRepository, s1); + stateLocked = false; this.stateLock.unlock(); // Paper - Fix GameProfileCache concurrency + try { this.lookupLock.lock(); // Paper - Fix GameProfileCache concurrency + optional = GameProfileCache.lookupGameProfile(this.profileRepository, name); // CraftBukkit - use correct case for offline players + } finally { this.lookupLock.unlock(); } // Paper - Fix GameProfileCache concurrency if (optional.isPresent()) { this.add((GameProfile) optional.get()); flag = false; } } - if (flag) { - this.save(); + if (flag && !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) { // Spigot - skip saving if disabled + this.save(true); // Paper - Perf: Async GameProfileCache saving } return optional; + } finally { if (stateLocked) { this.stateLock.unlock(); } } // Paper - Fix GameProfileCache concurrency } public CompletableFuture> getAsync(String username) { @@ -167,7 +195,7 @@ } else { CompletableFuture> completablefuture1 = CompletableFuture.supplyAsync(() -> { return this.get(username); - }, Util.backgroundExecutor().forName("getProfile")).whenCompleteAsync((optional, throwable) -> { + }, Util.PROFILE_EXECUTOR).whenCompleteAsync((optional, throwable) -> { // Paper - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread this.requests.remove(username); }, this.executor); @@ -178,6 +206,7 @@ } public Optional get(UUID uuid) { + try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByUUID.get(uuid); if (usercache_usercacheentry == null) { @@ -186,6 +215,7 @@ usercache_usercacheentry.setLastAccess(this.getNextOperation()); return Optional.of(usercache_usercacheentry.getProfile()); } + } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency } public void setExecutor(Executor executor) { @@ -208,7 +238,7 @@ label54: { - ArrayList arraylist; + List arraylist; // CraftBukkit - decompile error try { JsonArray jsonarray = (JsonArray) this.gson.fromJson(bufferedreader, JsonArray.class); @@ -217,7 +247,7 @@ DateFormat dateformat = GameProfileCache.createDateFormat(); jsonarray.forEach((jsonelement) -> { - Optional optional = GameProfileCache.readGameProfile(jsonelement, dateformat); + Optional optional = GameProfileCache.readGameProfile(jsonelement, dateformat); // CraftBukkit - decompile error Objects.requireNonNull(list); optional.ifPresent(list::add); @@ -250,6 +280,11 @@ } } catch (FileNotFoundException filenotfoundexception) { ; + // Spigot Start + } catch (com.google.gson.JsonSyntaxException | NullPointerException ex) { + GameProfileCache.LOGGER.warn( "Usercache.json is corrupted or has bad formatting. Deleting it to prevent further issues." ); + this.file.delete(); + // Spigot End } catch (JsonParseException | IOException ioexception) { GameProfileCache.LOGGER.warn("Failed to load profile cache {}", this.file, ioexception); } @@ -257,14 +292,15 @@ return list; } - public void save() { + public void save(boolean asyncSave) { // Paper - Perf: Async GameProfileCache saving JsonArray jsonarray = new JsonArray(); DateFormat dateformat = GameProfileCache.createDateFormat(); - this.getTopMRUProfiles(1000).forEach((usercache_usercacheentry) -> { + this.listTopMRUProfiles(org.spigotmc.SpigotConfig.userCacheCap).forEach((usercache_usercacheentry) -> { // Spigot // Paper - Fix GameProfileCache concurrency jsonarray.add(GameProfileCache.writeGameProfile(usercache_usercacheentry, dateformat)); }); String s = this.gson.toJson(jsonarray); + Runnable save = () -> { // Paper - Perf: Async GameProfileCache saving try { BufferedWriter bufferedwriter = Files.newWriter(this.file, StandardCharsets.UTF_8); @@ -289,13 +325,32 @@ } catch (IOException ioexception) { ; } + // Paper start - Perf: Async GameProfileCache saving + }; + if (asyncSave) { + io.papermc.paper.util.MCUtil.scheduleAsyncTask(save); + } else { + save.run(); + } + // Paper end - Perf: Async GameProfileCache saving } private Stream getTopMRUProfiles(int limit) { - return ImmutableList.copyOf(this.profilesByUUID.values()).stream().sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()).limit((long) limit); + // Paper start - Fix GameProfileCache concurrency + return this.listTopMRUProfiles(limit).stream(); } + private List listTopMRUProfiles(int limit) { + try { + this.stateLock.lock(); + return this.profilesByUUID.values().stream().sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()).limit(limit).toList(); + } finally { + this.stateLock.unlock(); + } + } + // Paper end - Fix GameProfileCache concurrency + private static JsonElement writeGameProfile(GameProfileCache.GameProfileInfo entry, DateFormat dateFormat) { JsonObject jsonobject = new JsonObject();