Skip to content

Commit eb0f07a

Browse files
authored
feat: alter clipboard thread to move all clipboard loading from main thread (#2867)
1 parent e55e8fa commit eb0f07a

File tree

8 files changed

+181
-61
lines changed

8 files changed

+181
-61
lines changed

worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -564,17 +564,17 @@ public BukkitPermissionAttachmentManager getPermissionAttachmentManager() {
564564
public BukkitPlayer wrapPlayer(Player player) {
565565
//FAWE start - Use cache over returning a direct BukkitPlayer
566566
BukkitPlayer wePlayer = getCachedPlayer(player);
567-
if (wePlayer == null) {
568-
synchronized (player) {
569-
wePlayer = getCachedPlayer(player);
570-
if (wePlayer == null) {
571-
wePlayer = new BukkitPlayer(this, player);
572-
player.setMetadata("WE", new FixedMetadataValue(this, wePlayer));
573-
return wePlayer;
574-
}
567+
if (wePlayer != null) {
568+
return wePlayer;
569+
}
570+
synchronized (player) {
571+
BukkitPlayer bukkitPlayer = getCachedPlayer(player);
572+
if (bukkitPlayer == null) {
573+
bukkitPlayer = new BukkitPlayer(this, player);
574+
player.setMetadata("WE", new FixedMetadataValue(this, bukkitPlayer));
575575
}
576+
return bukkitPlayer;
576577
}
577-
return wePlayer;
578578
//FAWE end
579579
}
580580

worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
import com.fastasyncworldedit.core.util.TextureUtil;
1515
import com.fastasyncworldedit.core.util.WEManager;
1616
import com.fastasyncworldedit.core.util.task.KeyQueuedExecutorService;
17+
import com.fastasyncworldedit.core.util.task.UUIDKeyQueuedThreadFactory;
1718
import com.github.luben.zstd.Zstd;
18-
import com.google.common.util.concurrent.ThreadFactoryBuilder;
1919
import com.sk89q.worldedit.WorldEdit;
2020
import com.sk89q.worldedit.internal.util.LogManagerCompat;
2121
import net.jpountz.lz4.LZ4Factory;
@@ -37,6 +37,9 @@
3737
import java.util.Date;
3838
import java.util.List;
3939
import java.util.UUID;
40+
import java.util.concurrent.Callable;
41+
import java.util.concurrent.CompletableFuture;
42+
import java.util.concurrent.Future;
4043
import java.util.concurrent.LinkedBlockingQueue;
4144
import java.util.concurrent.ThreadPoolExecutor;
4245
import java.util.concurrent.TimeUnit;
@@ -92,7 +95,7 @@ public class Fawe {
9295
* The platform specific implementation.
9396
*/
9497
private final IFawe implementation;
95-
private final KeyQueuedExecutorService<UUID> clipboardExecutor;
98+
private final KeyQueuedExecutorService<UUID> uuidKeyQueuedExecutorService;
9699
private FaweVersion version;
97100
private TextureUtil textures;
98101
private QueueHandler queueHandler;
@@ -140,14 +143,13 @@ private Fawe(final IFawe implementation) {
140143
}, 0);
141144

142145
TaskManager.taskManager().repeat(timer, 1);
143-
144-
clipboardExecutor = new KeyQueuedExecutorService<>(new ThreadPoolExecutor(
146+
uuidKeyQueuedExecutorService = new KeyQueuedExecutorService<>(new ThreadPoolExecutor(
145147
1,
146148
Settings.settings().QUEUE.PARALLEL_THREADS,
147149
0L,
148150
TimeUnit.MILLISECONDS,
149151
new LinkedBlockingQueue<>(),
150-
new ThreadFactoryBuilder().setNameFormat("FAWE Clipboard - %d").build()
152+
new UUIDKeyQueuedThreadFactory()
151153
));
152154
}
153155

@@ -463,9 +465,58 @@ public Thread setMainThread() {
463465
*
464466
* @return Executor used for clipboard IO if clipboard on disk is enabled or null
465467
* @since 2.6.2
468+
* @deprecated Use any of {@link Fawe#submitUUIDKeyQueuedTask(UUID, Runnable)},
469+
* {@link Fawe#submitUUIDKeyQueuedTask(UUID, Runnable, Object), {@link Fawe#submitUUIDKeyQueuedTask(UUID, Callable)}
470+
* to ensure if a thread is already a UUID-queued thread, the task is immediately run
466471
*/
472+
@Deprecated(forRemoval = true, since = "TODO")
467473
public KeyQueuedExecutorService<UUID> getClipboardExecutor() {
468-
return this.clipboardExecutor;
474+
return this.uuidKeyQueuedExecutorService;
475+
}
476+
477+
/**
478+
* Submit a task to the UUID key-queued executor
479+
*
480+
* @return Future representing the tank
481+
* @since TODO
482+
*/
483+
public Future<?> submitUUIDKeyQueuedTask(UUID uuid, Runnable runnable) {
484+
if (Thread.currentThread() instanceof UUIDKeyQueuedThreadFactory.UUIDKeyQueuedThread) {
485+
runnable.run();
486+
return CompletableFuture.completedFuture(null);
487+
}
488+
return this.uuidKeyQueuedExecutorService.submit(uuid, runnable);
489+
}
490+
491+
/**
492+
* Submit a task to the UUID key-queued executor
493+
*
494+
* @return Future representing the tank
495+
* @since TODO
496+
*/
497+
public <T> Future<T> submitUUIDKeyQueuedTask(UUID uuid, Runnable runnable, T result) {
498+
if (Thread.currentThread() instanceof UUIDKeyQueuedThreadFactory.UUIDKeyQueuedThread) {
499+
runnable.run();
500+
return CompletableFuture.completedFuture(result);
501+
}
502+
return this.uuidKeyQueuedExecutorService.submit(uuid, runnable, result);
503+
}
504+
505+
/**
506+
* Submit a task to the UUID key-queued executor
507+
*
508+
* @return Future representing the tank
509+
* @since TODO
510+
*/
511+
public <T> Future<T> submitUUIDKeyQueuedTask(UUID uuid, Callable<T> callable) {
512+
if (Thread.currentThread() instanceof UUIDKeyQueuedThreadFactory.UUIDKeyQueuedThread) {
513+
try {
514+
return CompletableFuture.completedFuture(callable.call());
515+
} catch (Throwable t) {
516+
return CompletableFuture.failedFuture(t);
517+
}
518+
}
519+
return this.uuidKeyQueuedExecutorService.submit(uuid, callable);
469520
}
470521

471522
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.fastasyncworldedit.core.util.task;
2+
3+
import org.jetbrains.annotations.ApiStatus;
4+
5+
import javax.annotation.Nonnull;
6+
import javax.annotation.Nullable;
7+
import java.util.concurrent.ThreadFactory;
8+
import java.util.concurrent.atomic.AtomicInteger;
9+
10+
@ApiStatus.Internal
11+
public class UUIDKeyQueuedThreadFactory implements ThreadFactory {
12+
13+
private final ThreadGroup group;
14+
private final AtomicInteger threadNumber = new AtomicInteger(1);
15+
private final String namePrefix;
16+
17+
public UUIDKeyQueuedThreadFactory() {
18+
group = new ThreadGroup("UUIDKeyQueuedThreadGroup");
19+
namePrefix = "FAWE UUID-key-queued - ";
20+
}
21+
22+
public Thread newThread(@Nonnull Runnable r) {
23+
Thread t = new UUIDKeyQueuedThread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
24+
if (t.isDaemon()) {
25+
t.setDaemon(false);
26+
}
27+
if (t.getPriority() != Thread.NORM_PRIORITY) {
28+
t.setPriority(Thread.NORM_PRIORITY);
29+
}
30+
return t;
31+
}
32+
33+
public static final class UUIDKeyQueuedThread extends Thread {
34+
35+
public UUIDKeyQueuedThread(@Nullable ThreadGroup group, Runnable task, @Nonnull String name, long stackSize) {
36+
super(group, task, name, stackSize);
37+
}
38+
39+
}
40+
41+
}

worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -930,7 +930,7 @@ public void loadClipboardFromDisk(File file) throws FaweClipboardVersionMismatch
930930
}
931931
} catch (EmptyClipboardException ignored) {
932932
}
933-
DiskOptimizedClipboard doc = Fawe.instance().getClipboardExecutor().submit(
933+
DiskOptimizedClipboard doc = Fawe.instance().submitUUIDKeyQueuedTask(
934934
uuid,
935935
() -> DiskOptimizedClipboard.loadFromFile(file)
936936
).get();
@@ -954,7 +954,7 @@ public void deleteClipboardOnDisk() {
954954
} else {
955955
continue;
956956
}
957-
Fawe.instance().getClipboardExecutor().submit(uuid, () -> {
957+
Fawe.instance().submitUUIDKeyQueuedTask(uuid, () -> {
958958
doc.close(); // Ensure closed before deletion
959959
doc.getFile().delete();
960960
});

worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ private void createCopy(
325325
} else {
326326
throw e;
327327
}
328-
Fawe.instance().getClipboardExecutor().submit(actor.getUniqueId(), () -> {
328+
Fawe.instance().submitUUIDKeyQueuedTask(actor.getUniqueId(), () -> {
329329
clipboard.close();
330330
doc.getFile().delete();
331331
});

worldedit-core/src/main/java/com/sk89q/worldedit/entity/Player.java

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ default void unregister() {
423423
if (Settings.settings().CLIPBOARD.USE_DISK && Settings.settings().CLIPBOARD.DELETE_ON_LOGOUT) {
424424
session.deleteClipboardOnDisk();
425425
} else if (Settings.settings().CLIPBOARD.USE_DISK) {
426-
Fawe.instance().getClipboardExecutor().submit(getUniqueId(), () -> session.setClipboard(null));
426+
Fawe.instance().submitUUIDKeyQueuedTask(getUniqueId(), () -> session.setClipboard(null));
427427
} else if (Settings.settings().CLIPBOARD.DELETE_ON_LOGOUT) {
428428
session.setClipboard(null);
429429
}
@@ -436,32 +436,8 @@ default void unregister() {
436436
void sendTitle(Component title, Component sub);
437437

438438
/**
439-
* Loads any history items from disk: - Should already be called if history on disk is enabled.
440-
*/
441-
default void loadClipboardFromDisk() {
442-
File file = MainUtil.getFile(
443-
Fawe.platform().getDirectory(),
444-
Settings.settings().PATHS.CLIPBOARD + File.separator + getUniqueId() + ".bd"
445-
);
446-
try {
447-
getSession().loadClipboardFromDisk(file);
448-
} catch (FaweClipboardVersionMismatchException e) {
449-
print(e.getComponent());
450-
} catch (RuntimeException e) {
451-
print(Caption.of("fawe.error.clipboard.invalid"));
452-
e.printStackTrace();
453-
print(Caption.of("fawe.error.stacktrace"));
454-
print(Caption.of("fawe.error.clipboard.load.failure"));
455-
print(Caption.of("fawe.error.clipboard.invalid.info", file.getName(), file.length()));
456-
print(Caption.of("fawe.error.stacktrace"));
457-
} catch (Exception e) {
458-
print(Caption.of("fawe.error.clipboard.invalid"));
459-
e.printStackTrace();
460-
print(Caption.of("fawe.error.stacktrace"));
461-
print(Caption.of("fawe.error.no-failure"));
462-
print(Caption.of("fawe.error.clipboard.invalid.info", file.getName(), file.length()));
463-
print(Caption.of("fawe.error.stacktrace"));
464-
}
465-
}
439+
* Loads clipboard file from disk if it exists
440+
*/
441+
void loadClipboardFromDisk();
466442
//FAWE end
467443
}

worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@
1919

2020
package com.sk89q.worldedit.extension.platform;
2121

22+
import com.fastasyncworldedit.core.Fawe;
2223
import com.fastasyncworldedit.core.configuration.Caption;
24+
import com.fastasyncworldedit.core.configuration.Settings;
25+
import com.fastasyncworldedit.core.internal.exception.FaweClipboardVersionMismatchException;
2326
import com.fastasyncworldedit.core.internal.exception.FaweException;
2427
import com.fastasyncworldedit.core.math.MutableBlockVector3;
2528
import com.fastasyncworldedit.core.regions.FaweMaskManager;
29+
import com.fastasyncworldedit.core.util.MainUtil;
2630
import com.fastasyncworldedit.core.util.TaskManager;
2731
import com.fastasyncworldedit.core.util.WEManager;
2832
import com.fastasyncworldedit.core.util.task.AsyncNotifyKeyedQueue;
@@ -69,6 +73,8 @@
6973
import java.io.File;
7074
import java.util.Map;
7175
import java.util.concurrent.ConcurrentHashMap;
76+
import java.util.concurrent.Future;
77+
import java.util.concurrent.Semaphore;
7278
import java.util.concurrent.atomic.AtomicInteger;
7379

7480
/**
@@ -82,6 +88,7 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable {
8288

8389
//FAWE start
8490
private final Map<String, Object> meta;
91+
private final Semaphore clipboardLoading = new Semaphore(1);
8592

8693
// Queue for async tasks
8794
private final AtomicInteger runningCount = new AtomicInteger();
@@ -524,22 +531,68 @@ public Region getLargestRegion() {
524531

525532
@Override
526533
public void setSelection(Region region) {
527-
RegionSelector selector;
528-
if (region instanceof ConvexPolyhedralRegion) {
529-
selector = new ConvexPolyhedralRegionSelector((ConvexPolyhedralRegion) region);
530-
} else if (region instanceof CylinderRegion) {
531-
selector = new CylinderRegionSelector((CylinderRegion) region);
532-
} else if (region instanceof Polygonal2DRegion) {
533-
selector = new Polygonal2DRegionSelector((Polygonal2DRegion) region);
534-
} else {
535-
selector = new CuboidRegionSelector(null, region.getMinimumPoint(),
536-
region.getMaximumPoint()
537-
);
538-
}
534+
RegionSelector selector = switch (region) {
535+
case ConvexPolyhedralRegion blockVector3s -> new ConvexPolyhedralRegionSelector(blockVector3s);
536+
case CylinderRegion blockVector3s -> new CylinderRegionSelector(blockVector3s);
537+
case Polygonal2DRegion blockVector3s -> new Polygonal2DRegionSelector(blockVector3s);
538+
default -> new CuboidRegionSelector(null, region.getMinimumPoint(), region.getMaximumPoint());
539+
};
539540
selector.setWorld(region.getWorld());
540541

541542
getSession().setRegionSelector(getWorld(), selector);
542543
}
544+
545+
@Override
546+
public void loadClipboardFromDisk() {
547+
if (!clipboardLoading.tryAcquire()) {
548+
if (!Fawe.isMainThread()) {
549+
try {
550+
clipboardLoading.acquire();
551+
clipboardLoading.release();
552+
} catch (InterruptedException e) {
553+
LOGGER.error("Error waiting for clipboard-on-disk loading for player {}", getName(), e);
554+
}
555+
}
556+
return;
557+
}
558+
559+
File file = MainUtil.getFile(
560+
Fawe.platform().getDirectory(),
561+
Settings.settings().PATHS.CLIPBOARD + File.separator + getUniqueId() + ".bd"
562+
);
563+
try {
564+
Future<?> fut = Fawe.instance().submitUUIDKeyQueuedTask(getUniqueId(), () -> {
565+
try {
566+
getSession().loadClipboardFromDisk(file);
567+
} catch (FaweClipboardVersionMismatchException e) {
568+
print(e.getComponent());
569+
} catch (RuntimeException e) {
570+
print(Caption.of("fawe.error.clipboard.invalid"));
571+
LOGGER.error("Error loading clipboard from disk", e);
572+
print(Caption.of("fawe.error.stacktrace"));
573+
print(Caption.of("fawe.error.clipboard.load.failure"));
574+
print(Caption.of("fawe.error.clipboard.invalid.info", file.getName(), file.length()));
575+
print(Caption.of("fawe.error.stacktrace"));
576+
} catch (Exception e) {
577+
print(Caption.of("fawe.error.clipboard.invalid"));
578+
LOGGER.error("Error loading clipboard from disk", e);
579+
print(Caption.of("fawe.error.stacktrace"));
580+
print(Caption.of("fawe.error.no-failure"));
581+
print(Caption.of("fawe.error.clipboard.invalid.info", file.getName(), file.length()));
582+
print(Caption.of("fawe.error.stacktrace"));
583+
} finally {
584+
clipboardLoading.release();
585+
}
586+
});
587+
if (Fawe.isMainThread()) {
588+
return;
589+
}
590+
fut.get();
591+
} catch (Exception e) {
592+
LOGGER.error("Error loading clipboard from disk", e);
593+
print(Caption.of("fawe.error.clipboard.load.failure"));
594+
}
595+
}
543596
//FAWE end
544597

545598
@Override
@@ -698,10 +751,9 @@ public void dispatchCUIEvent(CUIEvent event) {
698751

699752
@Override
700753
public boolean equals(Object other) {
701-
if (!(other instanceof Player)) {
754+
if (!(other instanceof Player other2)) {
702755
return false;
703756
}
704-
Player other2 = (Player) other;
705757
return other2.getName().equals(getName());
706758
}
707759

worldedit-core/src/main/resources/lang/strings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@
132132
"fawe.error.parse.no-clipboard-source": "No clipboards found at given source: {0}",
133133
"fawe.error.clipboard.invalid": "====== INVALID CLIPBOARD ======",
134134
"fawe.error.clipboard.invalid.info": "File: {0} (len: {1})",
135-
"fawe.error.clipboard.load.failure": "Could not load clipboard. Possible that the clipboard is still being written to from another server?!",
135+
"fawe.error.clipboard.load.failure": "Unexpected failure loading clipboard from disk!",
136136
"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}.",
137137
"fawe.error.limit.disallowed-block": "Your limit disallows use of block '{0}'",
138138
"fawe.error.limit.disallowed-property": "Your limit disallows use of property '{0}'",

0 commit comments

Comments
 (0)