From eb0f07af81516def23f388a5a6fd846aff9d82e5 Mon Sep 17 00:00:00 2001 From: Jordan Date: Wed, 6 Nov 2024 17:16:03 +0000 Subject: [PATCH] feat: alter clipboard thread to move all clipboard loading from main thread (#2867) --- .../worldedit/bukkit/WorldEditPlugin.java | 20 ++--- .../com/fastasyncworldedit/core/Fawe.java | 63 +++++++++++++-- .../util/task/UUIDKeyQueuedThreadFactory.java | 41 ++++++++++ .../com/sk89q/worldedit/LocalSession.java | 4 +- .../worldedit/command/ClipboardCommands.java | 2 +- .../com/sk89q/worldedit/entity/Player.java | 30 +------ .../platform/AbstractPlayerActor.java | 80 +++++++++++++++---- .../src/main/resources/lang/strings.json | 2 +- 8 files changed, 181 insertions(+), 61 deletions(-) create mode 100644 worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/UUIDKeyQueuedThreadFactory.java diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java index faac1c955..f676ae07b 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java @@ -564,17 +564,17 @@ public class WorldEditPlugin extends JavaPlugin { public BukkitPlayer wrapPlayer(Player player) { //FAWE start - Use cache over returning a direct BukkitPlayer BukkitPlayer wePlayer = getCachedPlayer(player); - if (wePlayer == null) { - synchronized (player) { - wePlayer = getCachedPlayer(player); - if (wePlayer == null) { - wePlayer = new BukkitPlayer(this, player); - player.setMetadata("WE", new FixedMetadataValue(this, wePlayer)); - return wePlayer; - } - } + if (wePlayer != null) { + return wePlayer; + } + synchronized (player) { + BukkitPlayer bukkitPlayer = getCachedPlayer(player); + if (bukkitPlayer == null) { + bukkitPlayer = new BukkitPlayer(this, player); + player.setMetadata("WE", new FixedMetadataValue(this, bukkitPlayer)); + } + return bukkitPlayer; } - return wePlayer; //FAWE end } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java index dac6fe9d6..6238ea1ea 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java @@ -14,8 +14,8 @@ import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.TextureUtil; import com.fastasyncworldedit.core.util.WEManager; import com.fastasyncworldedit.core.util.task.KeyQueuedExecutorService; +import com.fastasyncworldedit.core.util.task.UUIDKeyQueuedThreadFactory; import com.github.luben.zstd.Zstd; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.internal.util.LogManagerCompat; import net.jpountz.lz4.LZ4Factory; @@ -37,6 +37,9 @@ import java.lang.management.MemoryUsage; import java.util.Date; import java.util.List; import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -92,7 +95,7 @@ public class Fawe { * The platform specific implementation. */ private final IFawe implementation; - private final KeyQueuedExecutorService clipboardExecutor; + private final KeyQueuedExecutorService uuidKeyQueuedExecutorService; private FaweVersion version; private TextureUtil textures; private QueueHandler queueHandler; @@ -140,14 +143,13 @@ public class Fawe { }, 0); TaskManager.taskManager().repeat(timer, 1); - - clipboardExecutor = new KeyQueuedExecutorService<>(new ThreadPoolExecutor( + uuidKeyQueuedExecutorService = new KeyQueuedExecutorService<>(new ThreadPoolExecutor( 1, Settings.settings().QUEUE.PARALLEL_THREADS, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), - new ThreadFactoryBuilder().setNameFormat("FAWE Clipboard - %d").build() + new UUIDKeyQueuedThreadFactory() )); } @@ -463,9 +465,58 @@ public class Fawe { * * @return Executor used for clipboard IO if clipboard on disk is enabled or null * @since 2.6.2 + * @deprecated Use any of {@link Fawe#submitUUIDKeyQueuedTask(UUID, Runnable)}, + * {@link Fawe#submitUUIDKeyQueuedTask(UUID, Runnable, Object), {@link Fawe#submitUUIDKeyQueuedTask(UUID, Callable)} + * to ensure if a thread is already a UUID-queued thread, the task is immediately run */ + @Deprecated(forRemoval = true, since = "TODO") public KeyQueuedExecutorService getClipboardExecutor() { - return this.clipboardExecutor; + return this.uuidKeyQueuedExecutorService; + } + + /** + * Submit a task to the UUID key-queued executor + * + * @return Future representing the tank + * @since TODO + */ + public Future submitUUIDKeyQueuedTask(UUID uuid, Runnable runnable) { + if (Thread.currentThread() instanceof UUIDKeyQueuedThreadFactory.UUIDKeyQueuedThread) { + runnable.run(); + return CompletableFuture.completedFuture(null); + } + return this.uuidKeyQueuedExecutorService.submit(uuid, runnable); + } + + /** + * Submit a task to the UUID key-queued executor + * + * @return Future representing the tank + * @since TODO + */ + public Future submitUUIDKeyQueuedTask(UUID uuid, Runnable runnable, T result) { + if (Thread.currentThread() instanceof UUIDKeyQueuedThreadFactory.UUIDKeyQueuedThread) { + runnable.run(); + return CompletableFuture.completedFuture(result); + } + return this.uuidKeyQueuedExecutorService.submit(uuid, runnable, result); + } + + /** + * Submit a task to the UUID key-queued executor + * + * @return Future representing the tank + * @since TODO + */ + public Future submitUUIDKeyQueuedTask(UUID uuid, Callable callable) { + if (Thread.currentThread() instanceof UUIDKeyQueuedThreadFactory.UUIDKeyQueuedThread) { + try { + return CompletableFuture.completedFuture(callable.call()); + } catch (Throwable t) { + return CompletableFuture.failedFuture(t); + } + } + return this.uuidKeyQueuedExecutorService.submit(uuid, callable); } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/UUIDKeyQueuedThreadFactory.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/UUIDKeyQueuedThreadFactory.java new file mode 100644 index 000000000..2e87b8c25 --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/UUIDKeyQueuedThreadFactory.java @@ -0,0 +1,41 @@ +package com.fastasyncworldedit.core.util.task; + +import org.jetbrains.annotations.ApiStatus; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +@ApiStatus.Internal +public class UUIDKeyQueuedThreadFactory implements ThreadFactory { + + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + + public UUIDKeyQueuedThreadFactory() { + group = new ThreadGroup("UUIDKeyQueuedThreadGroup"); + namePrefix = "FAWE UUID-key-queued - "; + } + + public Thread newThread(@Nonnull Runnable r) { + Thread t = new UUIDKeyQueuedThread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); + if (t.isDaemon()) { + t.setDaemon(false); + } + if (t.getPriority() != Thread.NORM_PRIORITY) { + t.setPriority(Thread.NORM_PRIORITY); + } + return t; + } + + public static final class UUIDKeyQueuedThread extends Thread { + + public UUIDKeyQueuedThread(@Nullable ThreadGroup group, Runnable task, @Nonnull String name, long stackSize) { + super(group, task, name, stackSize); + } + + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java index 51a03119f..006de1b0b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java @@ -930,7 +930,7 @@ public class LocalSession implements TextureHolder { } } catch (EmptyClipboardException ignored) { } - DiskOptimizedClipboard doc = Fawe.instance().getClipboardExecutor().submit( + DiskOptimizedClipboard doc = Fawe.instance().submitUUIDKeyQueuedTask( uuid, () -> DiskOptimizedClipboard.loadFromFile(file) ).get(); @@ -954,7 +954,7 @@ public class LocalSession implements TextureHolder { } else { continue; } - Fawe.instance().getClipboardExecutor().submit(uuid, () -> { + Fawe.instance().submitUUIDKeyQueuedTask(uuid, () -> { doc.close(); // Ensure closed before deletion doc.getFile().delete(); }); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java index bb42e7095..a424682fe 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java @@ -325,7 +325,7 @@ public class ClipboardCommands { } else { throw e; } - Fawe.instance().getClipboardExecutor().submit(actor.getUniqueId(), () -> { + Fawe.instance().submitUUIDKeyQueuedTask(actor.getUniqueId(), () -> { clipboard.close(); doc.getFile().delete(); }); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/entity/Player.java b/worldedit-core/src/main/java/com/sk89q/worldedit/entity/Player.java index cc203190b..11561f313 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/entity/Player.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/entity/Player.java @@ -423,7 +423,7 @@ public interface Player extends Entity, Actor { if (Settings.settings().CLIPBOARD.USE_DISK && Settings.settings().CLIPBOARD.DELETE_ON_LOGOUT) { session.deleteClipboardOnDisk(); } else if (Settings.settings().CLIPBOARD.USE_DISK) { - Fawe.instance().getClipboardExecutor().submit(getUniqueId(), () -> session.setClipboard(null)); + Fawe.instance().submitUUIDKeyQueuedTask(getUniqueId(), () -> session.setClipboard(null)); } else if (Settings.settings().CLIPBOARD.DELETE_ON_LOGOUT) { session.setClipboard(null); } @@ -436,32 +436,8 @@ public interface Player extends Entity, Actor { void sendTitle(Component title, Component sub); /** - * Loads any history items from disk: - Should already be called if history on disk is enabled. + * Loads clipboard file from disk if it exists */ - default void loadClipboardFromDisk() { - File file = MainUtil.getFile( - Fawe.platform().getDirectory(), - Settings.settings().PATHS.CLIPBOARD + File.separator + getUniqueId() + ".bd" - ); - try { - getSession().loadClipboardFromDisk(file); - } catch (FaweClipboardVersionMismatchException e) { - print(e.getComponent()); - } catch (RuntimeException e) { - print(Caption.of("fawe.error.clipboard.invalid")); - e.printStackTrace(); - print(Caption.of("fawe.error.stacktrace")); - print(Caption.of("fawe.error.clipboard.load.failure")); - print(Caption.of("fawe.error.clipboard.invalid.info", file.getName(), file.length())); - print(Caption.of("fawe.error.stacktrace")); - } catch (Exception e) { - print(Caption.of("fawe.error.clipboard.invalid")); - e.printStackTrace(); - print(Caption.of("fawe.error.stacktrace")); - print(Caption.of("fawe.error.no-failure")); - print(Caption.of("fawe.error.clipboard.invalid.info", file.getName(), file.length())); - print(Caption.of("fawe.error.stacktrace")); - } - } + void loadClipboardFromDisk(); //FAWE end } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java index 13379f853..23b3799ef 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java @@ -19,10 +19,14 @@ package com.sk89q.worldedit.extension.platform; +import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.configuration.Caption; +import com.fastasyncworldedit.core.configuration.Settings; +import com.fastasyncworldedit.core.internal.exception.FaweClipboardVersionMismatchException; import com.fastasyncworldedit.core.internal.exception.FaweException; import com.fastasyncworldedit.core.math.MutableBlockVector3; import com.fastasyncworldedit.core.regions.FaweMaskManager; +import com.fastasyncworldedit.core.util.MainUtil; import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.WEManager; import com.fastasyncworldedit.core.util.task.AsyncNotifyKeyedQueue; @@ -69,6 +73,8 @@ import javax.annotation.Nullable; import java.io.File; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicInteger; /** @@ -82,6 +88,7 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable { //FAWE start private final Map meta; + private final Semaphore clipboardLoading = new Semaphore(1); // Queue for async tasks private final AtomicInteger runningCount = new AtomicInteger(); @@ -524,22 +531,68 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable { @Override public void setSelection(Region region) { - RegionSelector selector; - if (region instanceof ConvexPolyhedralRegion) { - selector = new ConvexPolyhedralRegionSelector((ConvexPolyhedralRegion) region); - } else if (region instanceof CylinderRegion) { - selector = new CylinderRegionSelector((CylinderRegion) region); - } else if (region instanceof Polygonal2DRegion) { - selector = new Polygonal2DRegionSelector((Polygonal2DRegion) region); - } else { - selector = new CuboidRegionSelector(null, region.getMinimumPoint(), - region.getMaximumPoint() - ); - } + RegionSelector selector = switch (region) { + case ConvexPolyhedralRegion blockVector3s -> new ConvexPolyhedralRegionSelector(blockVector3s); + case CylinderRegion blockVector3s -> new CylinderRegionSelector(blockVector3s); + case Polygonal2DRegion blockVector3s -> new Polygonal2DRegionSelector(blockVector3s); + default -> new CuboidRegionSelector(null, region.getMinimumPoint(), region.getMaximumPoint()); + }; selector.setWorld(region.getWorld()); getSession().setRegionSelector(getWorld(), selector); } + + @Override + public void loadClipboardFromDisk() { + if (!clipboardLoading.tryAcquire()) { + if (!Fawe.isMainThread()) { + try { + clipboardLoading.acquire(); + clipboardLoading.release(); + } catch (InterruptedException e) { + LOGGER.error("Error waiting for clipboard-on-disk loading for player {}", getName(), e); + } + } + return; + } + + File file = MainUtil.getFile( + Fawe.platform().getDirectory(), + Settings.settings().PATHS.CLIPBOARD + File.separator + getUniqueId() + ".bd" + ); + try { + Future fut = Fawe.instance().submitUUIDKeyQueuedTask(getUniqueId(), () -> { + try { + getSession().loadClipboardFromDisk(file); + } catch (FaweClipboardVersionMismatchException e) { + print(e.getComponent()); + } catch (RuntimeException e) { + print(Caption.of("fawe.error.clipboard.invalid")); + LOGGER.error("Error loading clipboard from disk", e); + print(Caption.of("fawe.error.stacktrace")); + print(Caption.of("fawe.error.clipboard.load.failure")); + print(Caption.of("fawe.error.clipboard.invalid.info", file.getName(), file.length())); + print(Caption.of("fawe.error.stacktrace")); + } catch (Exception e) { + print(Caption.of("fawe.error.clipboard.invalid")); + LOGGER.error("Error loading clipboard from disk", e); + print(Caption.of("fawe.error.stacktrace")); + print(Caption.of("fawe.error.no-failure")); + print(Caption.of("fawe.error.clipboard.invalid.info", file.getName(), file.length())); + print(Caption.of("fawe.error.stacktrace")); + } finally { + clipboardLoading.release(); + } + }); + if (Fawe.isMainThread()) { + return; + } + fut.get(); + } catch (Exception e) { + LOGGER.error("Error loading clipboard from disk", e); + print(Caption.of("fawe.error.clipboard.load.failure")); + } + } //FAWE end @Override @@ -698,10 +751,9 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable { @Override public boolean equals(Object other) { - if (!(other instanceof Player)) { + if (!(other instanceof Player other2)) { return false; } - Player other2 = (Player) other; return other2.getName().equals(getName()); } diff --git a/worldedit-core/src/main/resources/lang/strings.json b/worldedit-core/src/main/resources/lang/strings.json index 0b7a63b8d..c3468f9d8 100644 --- a/worldedit-core/src/main/resources/lang/strings.json +++ b/worldedit-core/src/main/resources/lang/strings.json @@ -132,7 +132,7 @@ "fawe.error.parse.no-clipboard-source": "No clipboards found at given source: {0}", "fawe.error.clipboard.invalid": "====== INVALID CLIPBOARD ======", "fawe.error.clipboard.invalid.info": "File: {0} (len: {1})", - "fawe.error.clipboard.load.failure": "Could not load clipboard. Possible that the clipboard is still being written to from another server?!", + "fawe.error.clipboard.load.failure": "Unexpected failure loading clipboard from disk!", "fawe.error.clipboard.on.disk.version.mismatch": "Clipboard version mismatch: expected {0} but got {1}. It is recommended you delete the clipboard folder and restart the server.\nYour clipboard folder is located at {2}.", "fawe.error.limit.disallowed-block": "Your limit disallows use of block '{0}'", "fawe.error.limit.disallowed-property": "Your limit disallows use of property '{0}'",