Upstream Paper and misc

This commit is contained in:
Sotr
2018-08-13 04:28:45 +08:00
parent 46e8595949
commit 0c3fec2c10
10 changed files with 165 additions and 1904 deletions

View File

@@ -1,18 +1,29 @@
package io.akarin.api.internal.utils; package io.akarin.api.internal.utils;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class ReentrantSpinningLock { public class ReentrantSpinningLock {
private final AtomicBoolean singleLock = new AtomicBoolean(false); /*
* Impl Note:
* A write lock can reentrant as a read lock, while a
* read lock is not allowed to reentrant as a write lock.
*/
private final AtomicBoolean writeLocked = new AtomicBoolean(false);
// --------- Thread local restricted fields ---------
private long heldThreadId = 0; private long heldThreadId = 0;
private int reentrantLocks = 0; private int reentrantLocks = 0;
/**
* Lock as a typical write lock
*/
public void lock() { public void lock() {
long currentThreadId = Thread.currentThread().getId(); long currentThreadId = Thread.currentThread().getId();
if (heldThreadId == currentThreadId) { if (heldThreadId == currentThreadId) {
reentrantLocks++; reentrantLocks++;
} else { } else {
while (!singleLock.compareAndSet(false, true)) ; // In case acquire one lock concurrently while (!writeLocked.compareAndSet(false, true)) ; // In case acquire one lock concurrently
heldThreadId = currentThreadId; heldThreadId = currentThreadId;
} }
} }
@@ -20,9 +31,68 @@ public class ReentrantSpinningLock {
public void unlock() { public void unlock() {
if (reentrantLocks == 0) { if (reentrantLocks == 0) {
heldThreadId = 0; heldThreadId = 0;
singleLock.set(false); if (readerThreads.getAndDecrement() == 1) { // Micro-optimization: this saves one subtract
writeLocked.set(false);
}
} else { } else {
--reentrantLocks; --reentrantLocks;
} }
} }
private final AtomicInteger readerThreads = new AtomicInteger(0);
/**
* Lock as a typical read lock
*/
public void lockWeak() {
long currentThreadId = Thread.currentThread().getId();
if (heldThreadId == currentThreadId) {
reentrantLocks++;
} else {
if (readerThreads.get() == 0) {
while (!writeLocked.compareAndSet(false, true)) ; // Block future write lock
}
heldThreadId = currentThreadId;
readerThreads.getAndIncrement(); // Micro-optimization: this saves one plus
}
}
public void unlockWeak() {
if (reentrantLocks == 0) {
heldThreadId = 0;
writeLocked.set(false);
} else {
--reentrantLocks;
}
}
// --------- Wrappers to allow typical usages ---------
private SpinningWriteLock wrappedWriteLock = new SpinningWriteLock();
private SpinningReadLock wrappedReadLock = new SpinningReadLock();
public class SpinningWriteLock {
public void lock() {
lock();
}
public void unlock() {
unlock();
}
}
public class SpinningReadLock {
public void lock() {
lockWeak();
}
public void unlock() {
unlockWeak();
}
}
public SpinningWriteLock writeLock() {
return wrappedWriteLock;
}
public SpinningReadLock readLocked() {
return wrappedReadLock;
}
} }

View File

@@ -25,7 +25,7 @@ public class AkarinGlobalConfig {
+ "Some options may impact gameplay, so use with caution,\n" + "Some options may impact gameplay, so use with caution,\n"
+ "and make sure you know what each option does before configuring.\n" + "and make sure you know what each option does before configuring.\n"
+ "\n" + "\n"
+ "Akarin forums: https://akarin.io/ \n"; + "Akarin website: https://akarin.io/ \n";
/*========================================================================*/ /*========================================================================*/
public static YamlConfiguration config; public static YamlConfiguration config;
static int version; static int version;

View File

@@ -6,7 +6,6 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.CraftServer;
import org.spigotmc.RestartCommand; import org.spigotmc.RestartCommand;
import org.spigotmc.WatchdogThread; import org.spigotmc.WatchdogThread;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
@@ -23,6 +22,10 @@ import net.minecraft.server.MinecraftServer;
public abstract class Watchcat extends Thread { public abstract class Watchcat extends Thread {
@Shadow private static WatchdogThread instance; @Shadow private static WatchdogThread instance;
@Shadow private @Final long timeoutTime; @Shadow private @Final long timeoutTime;
@Shadow private @Final long earlyWarningEvery; // Paper - Timeout time for just printing a dump but not restarting
@Shadow private @Final long earlyWarningDelay; // Paper
@Shadow public static volatile boolean hasStarted; // Paper
@Shadow private long lastEarlyWarning; // Paper - Keep track of short dump times to avoid spamming console with short dumps
@Shadow private @Final boolean restart; @Shadow private @Final boolean restart;
@Shadow private volatile long lastTick; @Shadow private volatile long lastTick;
@Shadow private volatile boolean stopping; @Shadow private volatile boolean stopping;
@@ -39,47 +42,67 @@ public abstract class Watchcat extends Thread {
public void run() { public void run() {
while (!stopping) { while (!stopping) {
// //
if (lastTick != 0 && System.currentTimeMillis() > lastTick + timeoutTime && !Boolean.getBoolean("disable.watchdog")) { // Paper - Add property to disable long currentTime = System.currentTimeMillis(); // Paper - do we REALLY need to call this method multiple times?
Logger log = Bukkit.getServer().getLogger(); if (lastTick != 0 && currentTime > lastTick + earlyWarningEvery && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable and short timeout
log.log(Level.SEVERE, "Server has stopped responding!"); {
log.log(Level.SEVERE, "Please report this to https://github.com/Akarin-project/Akarin/issues"); // Paper start
log.log(Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports"); boolean isLongTimeout = currentTime > lastTick + timeoutTime;
log.log(Level.SEVERE, "Akarin version: " + Bukkit.getServer().getVersion()); // Don't spam short dumps
// if (!isLongTimeout && (earlyWarningEvery <= 0 || !hasStarted || currentTime < lastEarlyWarning + earlyWarningEvery || currentTime < lastTick + earlyWarningDelay))
if (net.minecraft.server.World.haveWeSilencedAPhysicsCrash) { continue;
log.log(Level.SEVERE, "------------------------------"); lastEarlyWarning = currentTime;
log.log(Level.SEVERE, "During the run of the server, a physics stackoverflow was supressed");
log.log(Level.SEVERE, "near " + net.minecraft.server.World.blockLocation);
}
// Paper start - Warn in watchdog if an excessive velocity was ever set
if (CraftServer.excessiveVelEx != null) {
log.log(Level.SEVERE, "------------------------------");
log.log(Level.SEVERE, "During the run of the server, a plugin set an excessive velocity on an entity");
log.log(Level.SEVERE, "This may be the cause of the issue, or it may be entirely unrelated");
log.log(Level.SEVERE, CraftServer.excessiveVelEx.getMessage());
for (StackTraceElement stack : CraftServer.excessiveVelEx.getStackTrace()) {
log.log(Level.SEVERE, "\t\t" + stack);
}
}
// Paper end // Paper end
Logger log = Bukkit.getServer().getLogger();
// Paper start - Different message when it's a short timeout
if (isLongTimeout) {
log.log(Level.SEVERE, "The server has stopped responding!");
log.log(Level.SEVERE, "Please report this to https://github.com/Akarin-project/Akarin/issues"); // Akarin
log.log(Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports");
log.log(Level.SEVERE, "Akarin version: " + Bukkit.getServer().getVersion()); // Akarin
//
if (net.minecraft.server.World.haveWeSilencedAPhysicsCrash) {
log.log(Level.SEVERE, "------------------------------");
log.log(Level.SEVERE, "During the run of the server, a physics stackoverflow was supressed");
log.log(Level.SEVERE, "near " + net.minecraft.server.World.blockLocation);
}
// Paper start - Warn in watchdog if an excessive velocity was ever set
if (org.bukkit.craftbukkit.CraftServer.excessiveVelEx != null) {
log.log(Level.SEVERE, "------------------------------");
log.log(Level.SEVERE, "During the run of the server, a plugin set an excessive velocity on an entity");
log.log(Level.SEVERE, "This may be the cause of the issue, or it may be entirely unrelated");
log.log(Level.SEVERE, org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getMessage());
for (StackTraceElement stack : org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getStackTrace()) {
log.log(Level.SEVERE, "\t\t" + stack);
}
}
// Paper end
} else {
log.log(Level.SEVERE, "The server has not responded for " + earlyWarningEvery / 1000 + " seconds! Creating thread dump");
}
// Paper end - Different message for short timeout
log.log(Level.SEVERE, "------------------------------"); log.log(Level.SEVERE, "------------------------------");
log.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Akarin!):"); log.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):");
dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(MinecraftServer.getServer().primaryThread.getId(), Integer.MAX_VALUE), log); dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(MinecraftServer.getServer().primaryThread.getId(), Integer.MAX_VALUE), log);
log.log(Level.SEVERE, "------------------------------"); log.log(Level.SEVERE, "------------------------------");
// //
log.log(Level.SEVERE, "Entire Thread Dump:"); // Paper start - Only print full dump on long timeouts
ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true, true); if (isLongTimeout) {
for (ThreadInfo thread : threads) { log.log(Level.SEVERE, "Entire Thread Dump:");
dumpThread(thread, log); ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true, true);
} for (ThreadInfo thread : threads) {
log.log(Level.SEVERE, "------------------------------"); dumpThread(thread, log);
}
log.log(Level.SEVERE, "------------------------------");
if (restart) RestartCommand.restart(); // GC Inlined if (restart) {
break; RestartCommand.restart();
}
break;
} // Paper end
} }
try { try {
sleep(9000); // Akarin sleep(1000); // Paper - Reduce check time to every second instead of every ten seconds, more consistent and allows for short timeout
} catch (InterruptedException ex) { } catch (InterruptedException ex) {
interrupt(); interrupt();
} }

View File

@@ -14,9 +14,7 @@ public abstract class MixinAsyncCatcher {
@Overwrite @Overwrite
public static void catchOp(String reason) { public static void catchOp(String reason) {
if (enabled) { if (enabled && !Akari.isPrimaryThread()) {
if (Akari.isPrimaryThread()) return;
if (AkarinGlobalConfig.throwOnAsyncCaught) { if (AkarinGlobalConfig.throwOnAsyncCaught) {
throw new IllegalStateException("Asynchronous " + reason + "!"); throw new IllegalStateException("Asynchronous " + reason + "!");
} else { } else {

File diff suppressed because it is too large Load Diff

View File

@@ -395,8 +395,8 @@ public class PlayerChunkMap {
// Paper start - Separate into two methods // Paper start - Separate into two methods
public void a(int i) { public void a(int i) {
i = MathHelper.clamp(i, 3, 32); i = MathHelper.clamp(i, 3, 32);
if (i != this.j) { // Akarin if (i != this.j) {
int j = i - this.j; // Akarin int j = i - this.j;
managedPlayersLock.readLock().lock(); // Akarin managedPlayersLock.readLock().lock(); // Akarin
ArrayList arraylist = Lists.newArrayList(this.managedPlayers); ArrayList arraylist = Lists.newArrayList(this.managedPlayers);
managedPlayersLock.readLock().unlock(); // Akarin managedPlayersLock.readLock().unlock(); // Akarin

View File

@@ -92,6 +92,7 @@ import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.Recipe; import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.ShapedRecipe; import org.bukkit.inventory.ShapedRecipe;
import org.bukkit.inventory.ShapelessRecipe; import org.bukkit.inventory.ShapelessRecipe;
import org.bukkit.loot.LootTable;
import org.bukkit.permissions.Permissible; import org.bukkit.permissions.Permissible;
import org.bukkit.permissions.Permission; import org.bukkit.permissions.Permission;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
@@ -119,7 +120,6 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators; import com.google.common.collect.Iterators;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
@@ -1899,6 +1899,13 @@ public final class CraftServer implements Server {
} }
} }
public LootTable getLootTable(NamespacedKey key) {
Validate.notNull(key, "NamespacedKey cannot be null");
LootTableRegistry registry = getServer().aP(); // PAIL getLootTableRegistry
return new CraftLootTable(key, registry.a(CraftNamespacedKey.toMinecraft(key))); // PAIL rename getLootTable
}
@Deprecated @Deprecated
@Override @Override
public UnsafeValues getUnsafe() { public UnsafeValues getUnsafe() {
@@ -1992,17 +1999,20 @@ public final class CraftServer implements Server {
@Override @Override
public void reloadPermissions() { public void reloadPermissions() {
((SimplePluginManager) pluginManager).clearPermissions(); pluginManager.clearPermissions();
loadCustomPermissions(); if (com.destroystokyo.paper.PaperConfig.loadPermsBeforePlugins) loadCustomPermissions();
for (Plugin plugin : pluginManager.getPlugins()) { for (Plugin plugin : pluginManager.getPlugins()) {
plugin.getDescription().getPermissions().forEach((perm) -> { for (Permission perm : plugin.getDescription().getPermissions()) {
try { try {
pluginManager.addPermission(perm); pluginManager.addPermission(perm);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
getLogger().log(Level.WARNING, "Plugin " + plugin.getDescription().getFullName() + " tried to register permission '" + perm.getName() + "' but it's already registered", ex); getLogger().log(Level.WARNING, "Plugin " + plugin.getDescription().getFullName() + " tried to register permission '" + perm.getName() + "' but it's already registered", ex);
} }
}); }
} }
if (!com.destroystokyo.paper.PaperConfig.loadPermsBeforePlugins) loadCustomPermissions();
DefaultPermissions.registerCorePermissions();
CraftDefaultPermissions.registerCorePermissions();
} }
@Override @Override

View File

@@ -5,7 +5,6 @@ import com.destroystokyo.paper.profile.CraftPlayerProfile;
import com.destroystokyo.paper.profile.PlayerProfile; import com.destroystokyo.paper.profile.PlayerProfile;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
@@ -17,7 +16,6 @@ import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -131,7 +129,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
private boolean hasPlayedBefore = false; private boolean hasPlayedBefore = false;
private final ConversationTracker conversationTracker = new ConversationTracker(); private final ConversationTracker conversationTracker = new ConversationTracker();
private final Set<String> channels = new HashSet<String>(); private final Set<String> channels = new HashSet<String>();
private final Map<UUID, Set<WeakReference<Plugin>>> hiddenPlayers = Collections.synchronizedMap(new HashMap<>()); // Akarin private final Map<UUID, Set<WeakReference<Plugin>>> hiddenPlayers = new java.util.concurrent.ConcurrentHashMap<>(); // Akarin
private static final WeakHashMap<Plugin, WeakReference<Plugin>> pluginWeakReferences = new WeakHashMap<>(); private static final WeakHashMap<Plugin, WeakReference<Plugin>> pluginWeakReferences = new WeakHashMap<>();
private int hash = 0; private int hash = 0;
private double health = 20; private double health = 20;
@@ -1199,14 +1197,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
EntityTracker tracker = ((WorldServer) entity.world).tracker; EntityTracker tracker = ((WorldServer) entity.world).tracker;
// Paper end // Paper end
tracker.entriesLock.updateLock().lock(); // Akarin tracker.entriesLock.writeLock().lock(); // Akarin
EntityTrackerEntry entry = tracker.trackedEntities.get(other.getId()); EntityTrackerEntry entry = tracker.trackedEntities.get(other.getId());
if (entry != null) { if (entry != null) {
tracker.entriesLock.writeLock().lock(); // Akarin
entry.clear(getHandle()); entry.clear(getHandle());
tracker.entriesLock.writeLock().unlock(); // Akarin
} }
tracker.entriesLock.updateLock().unlock(); // Akarin tracker.entriesLock.writeLock().unlock(); // Akarin
// Remove the hidden player from this player user list, if they're on it // Remove the hidden player from this player user list, if they're on it
if (other.sentListPacket) { if (other.sentListPacket) {
@@ -1253,14 +1249,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
getHandle().playerConnection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, other)); getHandle().playerConnection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, other));
tracker.entriesLock.updateLock().lock(); // Akarin tracker.entriesLock.writeLock().lock(); // Akarin
EntityTrackerEntry entry = tracker.trackedEntities.get(other.getId()); EntityTrackerEntry entry = tracker.trackedEntities.get(other.getId());
if (entry != null && !entry.trackedPlayers.contains(getHandle())) { if (entry != null && !entry.trackedPlayers.contains(getHandle())) {
tracker.entriesLock.writeLock().lock(); // Akarin
entry.updatePlayer(getHandle()); entry.updatePlayer(getHandle());
tracker.entriesLock.writeLock().unlock(); // Akarin
} }
tracker.entriesLock.updateLock().unlock(); // Akarin tracker.entriesLock.writeLock().unlock(); // Akarin
} }
// Paper start // Paper start
private void reregisterPlayer(EntityPlayer player) { private void reregisterPlayer(EntityPlayer player) {
@@ -1871,7 +1865,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
public boolean getAffectsSpawning() { public boolean getAffectsSpawning() {
return this.getHandle().affectsSpawning; return this.getHandle().affectsSpawning;
} }
// Paper end
@Override @Override
public int getViewDistance() { public int getViewDistance() {
@@ -1882,6 +1875,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
public void setViewDistance(int viewDistance) { public void setViewDistance(int viewDistance) {
((WorldServer) getHandle().world).getPlayerChunkMap().updateViewDistance(getHandle(), viewDistance); ((WorldServer) getHandle().world).getPlayerChunkMap().updateViewDistance(getHandle(), viewDistance);
} }
// Paper end
@Override
public void updateCommands() {
if (getHandle().playerConnection == null) return;
getHandle().server.getCommandDispatcher().a(getHandle());
}
@Override @Override
public void setResourcePack(String url, String hash) { public void setResourcePack(String url, String hash) {

View File

@@ -17,7 +17,6 @@ import com.destroystokyo.paper.event.executor.asm.ClassDefiner;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import io.akarin.api.internal.Akari; import io.akarin.api.internal.Akari;
import io.akarin.api.internal.utils.thread.SuspendableExecutorCompletionService;
// Paper end // Paper end
import io.akarin.server.core.AkarinGlobalConfig; import io.akarin.server.core.AkarinGlobalConfig;