mirror of
https://github.com/StarWishsama/Slimefun4.git
synced 2024-09-19 19:25:48 +00:00
Fixes exhaustion when loading large profiles (#4127)
This commit is contained in:
parent
98bc59efc9
commit
5be4718684
@ -0,0 +1,23 @@
|
|||||||
|
package io.github.thebusybiscuit.slimefun4;
|
||||||
|
|
||||||
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
public class Threads {
|
||||||
|
|
||||||
|
@ParametersAreNonnullByDefault
|
||||||
|
public static void newThread(JavaPlugin plugin, String name, Runnable runnable) {
|
||||||
|
// TODO: Change to thread pool
|
||||||
|
new Thread(runnable, plugin.getName() + " - " + name).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getCaller() {
|
||||||
|
// First item will be getting the call stack
|
||||||
|
// Second item will be this call
|
||||||
|
// Third item will be the func we care about being called
|
||||||
|
// And finally will be the caller
|
||||||
|
StackTraceElement element = Thread.currentThread().getStackTrace()[3];
|
||||||
|
return element.getClassName() + "." + element.getMethodName() + ":" + element.getLineNumber();
|
||||||
|
}
|
||||||
|
}
|
@ -3,10 +3,12 @@ package io.github.thebusybiscuit.slimefun4.api.player;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
@ -28,6 +30,7 @@ import com.google.common.collect.ImmutableSet;
|
|||||||
import io.github.bakedlibs.dough.common.ChatColors;
|
import io.github.bakedlibs.dough.common.ChatColors;
|
||||||
import io.github.bakedlibs.dough.common.CommonPatterns;
|
import io.github.bakedlibs.dough.common.CommonPatterns;
|
||||||
import io.github.bakedlibs.dough.config.Config;
|
import io.github.bakedlibs.dough.config.Config;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.Threads;
|
||||||
import io.github.thebusybiscuit.slimefun4.api.events.AsyncProfileLoadEvent;
|
import io.github.thebusybiscuit.slimefun4.api.events.AsyncProfileLoadEvent;
|
||||||
import io.github.thebusybiscuit.slimefun4.api.gps.Waypoint;
|
import io.github.thebusybiscuit.slimefun4.api.gps.Waypoint;
|
||||||
import io.github.thebusybiscuit.slimefun4.api.items.HashedArmorpiece;
|
import io.github.thebusybiscuit.slimefun4.api.items.HashedArmorpiece;
|
||||||
@ -55,6 +58,8 @@ import io.github.thebusybiscuit.slimefun4.utils.NumberUtils;
|
|||||||
*/
|
*/
|
||||||
public class PlayerProfile {
|
public class PlayerProfile {
|
||||||
|
|
||||||
|
private static final Map<UUID, Boolean> loading = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private final UUID ownerId;
|
private final UUID ownerId;
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
@ -361,17 +366,37 @@ public class PlayerProfile {
|
|||||||
*/
|
*/
|
||||||
public static boolean get(@Nonnull OfflinePlayer p, @Nonnull Consumer<PlayerProfile> callback) {
|
public static boolean get(@Nonnull OfflinePlayer p, @Nonnull Consumer<PlayerProfile> callback) {
|
||||||
Validate.notNull(p, "Cannot get a PlayerProfile for: null!");
|
Validate.notNull(p, "Cannot get a PlayerProfile for: null!");
|
||||||
|
|
||||||
UUID uuid = p.getUniqueId();
|
UUID uuid = p.getUniqueId();
|
||||||
|
|
||||||
|
Debug.log(TestCase.PLAYER_PROFILE_DATA, "Getting PlayerProfile for {}", uuid);
|
||||||
|
|
||||||
PlayerProfile profile = Slimefun.getRegistry().getPlayerProfiles().get(uuid);
|
PlayerProfile profile = Slimefun.getRegistry().getPlayerProfiles().get(uuid);
|
||||||
|
|
||||||
if (profile != null) {
|
if (profile != null) {
|
||||||
|
Debug.log(TestCase.PLAYER_PROFILE_DATA, "PlayerProfile for {} was already loaded", uuid);
|
||||||
callback.accept(profile);
|
callback.accept(profile);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(Slimefun.instance(), () -> {
|
// If we're already loading, we don't want to spin up a whole new thread and load the profile again/more
|
||||||
|
// This can very easily cause CPU, memory and thread exhaustion if the profile is large
|
||||||
|
// See #4011, #4116
|
||||||
|
if (loading.containsKey(uuid)) {
|
||||||
|
Debug.log(TestCase.PLAYER_PROFILE_DATA, "Attempted to get PlayerProfile ({}) while loading", uuid);
|
||||||
|
Debug.log(TestCase.PLAYER_PROFILE_DATA, "Caller: {}", Threads.getCaller());
|
||||||
|
|
||||||
|
// We can't easily consume the callback so we will throw it away in this case
|
||||||
|
// This will mean that if a user has attempted to do an action like open a block while
|
||||||
|
// their profile is still loading. Instead of it opening after a second or whatever when the
|
||||||
|
// profile is loaded, they will have to explicitly re-click the block/item/etc.
|
||||||
|
// This isn't the best but I think it's totally reasonable.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.put(uuid, true);
|
||||||
|
Threads.newThread(Slimefun.instance(), "PlayerProfile#get(" + uuid + ")", () -> {
|
||||||
PlayerData data = Slimefun.getPlayerStorage().loadPlayerData(p.getUniqueId());
|
PlayerData data = Slimefun.getPlayerStorage().loadPlayerData(p.getUniqueId());
|
||||||
|
loading.remove(uuid);
|
||||||
|
|
||||||
AsyncProfileLoadEvent event = new AsyncProfileLoadEvent(new PlayerProfile(p, data));
|
AsyncProfileLoadEvent event = new AsyncProfileLoadEvent(new PlayerProfile(p, data));
|
||||||
Bukkit.getPluginManager().callEvent(event);
|
Bukkit.getPluginManager().callEvent(event);
|
||||||
@ -394,14 +419,28 @@ public class PlayerProfile {
|
|||||||
*/
|
*/
|
||||||
public static boolean request(@Nonnull OfflinePlayer p) {
|
public static boolean request(@Nonnull OfflinePlayer p) {
|
||||||
Validate.notNull(p, "Cannot request a Profile for null");
|
Validate.notNull(p, "Cannot request a Profile for null");
|
||||||
|
Debug.log(TestCase.PLAYER_PROFILE_DATA, "Requesting PlayerProfile for {}", p.getName());
|
||||||
|
|
||||||
if (!Slimefun.getRegistry().getPlayerProfiles().containsKey(p.getUniqueId())) {
|
UUID uuid = p.getUniqueId();
|
||||||
|
|
||||||
|
// If we're already loading, we don't want to spin up a whole new thread and load the profile again/more
|
||||||
|
// This can very easily cause CPU, memory and thread exhaustion if the profile is large
|
||||||
|
// See #4011, #4116
|
||||||
|
if (loading.containsKey(uuid)) {
|
||||||
|
Debug.log(TestCase.PLAYER_PROFILE_DATA, "Attempted to request PlayerProfile ({}) while loading", uuid);
|
||||||
|
Debug.log(TestCase.PLAYER_PROFILE_DATA, "Caller: {}", Threads.getCaller());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Slimefun.getRegistry().getPlayerProfiles().containsKey(uuid)) {
|
||||||
|
loading.put(uuid, true);
|
||||||
// Should probably prevent multiple requests for the same profile in the future
|
// Should probably prevent multiple requests for the same profile in the future
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(Slimefun.instance(), () -> {
|
Threads.newThread(Slimefun.instance(), "PlayerProfile#request(" + uuid + ")", () -> {
|
||||||
PlayerData data = Slimefun.getPlayerStorage().loadPlayerData(p.getUniqueId());
|
PlayerData data = Slimefun.getPlayerStorage().loadPlayerData(uuid);
|
||||||
|
loading.remove(uuid);
|
||||||
|
|
||||||
PlayerProfile pp = new PlayerProfile(p, data);
|
PlayerProfile pp = new PlayerProfile(p, data);
|
||||||
Slimefun.getRegistry().getPlayerProfiles().put(p.getUniqueId(), pp);
|
Slimefun.getRegistry().getPlayerProfiles().put(uuid, pp);
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
Loading…
Reference in New Issue
Block a user