Safety timings w/ Configurable ticking mode

This commit is contained in:
Sotr
2018-08-13 00:37:29 +08:00
parent 1c5da01dec
commit f8e01fa0f9
9 changed files with 740 additions and 68 deletions

View File

@@ -0,0 +1,127 @@
/*
* This file is licensed under the MIT License (MIT).
*
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package co.aikar.timings;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
import static co.aikar.util.JSONUtil.toArray;
/**
* Akarin Changes Note
* 1) Thread safe timing (safety)
*/
/**
* <p>Lightweight object for tracking timing data</p>
*
* This is broken out to reduce memory usage
*/
class TimingData {
private final int id;
private int count = 0;
private int lagCount = 0;
private long totalTime = 0;
private long lagTotalTime = 0;
private AtomicInteger curTickCount = new AtomicInteger(); // Akarin
private LongAdder curTickTotal = new LongAdder(); // Akarin
TimingData(int id) {
this.id = id;
}
private TimingData(TimingData data) {
this.id = data.id;
this.totalTime = data.totalTime;
this.lagTotalTime = data.lagTotalTime;
this.count = data.count;
this.lagCount = data.lagCount;
}
void add(long diff) {
curTickCount.incrementAndGet();
curTickTotal.add(diff);
}
void processTick(boolean violated) {
totalTime += curTickTotal.sum();
count += curTickCount.get();
if (violated) {
lagTotalTime += curTickTotal.sum();
lagCount += curTickCount.get();
}
curTickTotal.reset();
curTickCount.set(0);
}
void reset() {
count = 0;
lagCount = 0;
curTickTotal.reset();
curTickCount.set(0);
totalTime = 0;
lagTotalTime = 0;
}
protected TimingData clone() {
return new TimingData(this);
}
List<Object> export() {
List<Object> list = toArray(
id,
count,
totalTime);
if (lagCount > 0) {
list.add(lagCount);
list.add(lagTotalTime);
}
return list;
}
boolean hasData() {
return count > 0;
}
long getTotalTime() {
return totalTime;
}
int getCurTickCount() {
return curTickCount.get();
}
void setCurTickCount(int curTickCount) {
this.curTickCount.getAndSet(curTickCount);
}
long getCurTickTotal() {
return curTickTotal.sum();
}
void setCurTickTotal(long curTickTotal) {
this.curTickTotal.reset();
this.curTickTotal.add(curTickTotal);
}
}

View File

@@ -0,0 +1,215 @@
/*
* This file is licensed under the MIT License (MIT).
*
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package co.aikar.timings;
import co.aikar.util.LoadingIntMap;
import org.bukkit.Bukkit;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
/**
* Akarin Changes Note
* 1) Thread safe timing (safety)
*/
class TimingHandler implements Timing {
private static int idPool = 1;
final int id = idPool++;
final String name;
private final boolean verbose;
private final Map<Integer, TimingData> children = Collections.synchronizedMap(new LoadingIntMap<>(TimingData::new)); // Akarin - HashMap
final TimingData record;
private final TimingHandler groupHandler;
private AtomicLong start = new AtomicLong(); // Akarin
private AtomicInteger timingDepth = new AtomicInteger(); // Akarin
private volatile boolean added; // Akarin
private boolean timed;
private boolean enabled;
private volatile TimingHandler parent; // Akarin
TimingHandler(TimingIdentifier id) {
if (id.name.startsWith("##")) {
verbose = true;
this.name = id.name.substring(3);
} else {
this.name = id.name;
verbose = false;
}
this.record = new TimingData(this.id);
this.groupHandler = id.groupHandler;
TimingIdentifier.getGroup(id.group).handlers.add(this);
checkEnabled();
}
final void checkEnabled() {
enabled = Timings.timingsEnabled && (!verbose || Timings.verboseEnabled);
}
void processTick(boolean violated) {
if (timingDepth.get() != 0 || record.getCurTickCount() == 0) {
timingDepth.set(0);
start.set(0);
return;
}
record.processTick(violated);
for (TimingData handler : children.values()) {
handler.processTick(violated);
}
}
@Override
public Timing startTimingIfSync() {
if (Bukkit.isPrimaryThread()) {
startTiming();
}
return this;
}
@Override
public void stopTimingIfSync() {
if (Bukkit.isPrimaryThread()) {
stopTiming();
}
}
public Timing startTiming() {
if (enabled && timingDepth.incrementAndGet() == 1) {
start.getAndSet(System.nanoTime());
parent = TimingsManager.CURRENT;
TimingsManager.CURRENT = this;
}
return this;
}
public void stopTiming() {
if (enabled && timingDepth.decrementAndGet() == 0 && start.get() != 0) {
if (!Bukkit.isPrimaryThread()) {
Bukkit.getLogger().log(Level.SEVERE, "stopTiming called async for " + name);
new Throwable().printStackTrace();
start.getAndSet(0);
return;
}
addDiff(System.nanoTime() - start.getAndSet(0));
}
}
@Override
public void abort() {
if (enabled && timingDepth.get() > 0) {
start.getAndSet(0);
}
}
void addDiff(long diff) {
if (TimingsManager.CURRENT == this) {
TimingsManager.CURRENT = parent;
if (parent != null) {
parent.children.get(id).add(diff);
}
}
record.add(diff);
if (!added) {
added = true;
timed = true;
TimingsManager.HANDLERS.add(this);
}
if (groupHandler != null) {
groupHandler.addDiff(diff);
groupHandler.children.get(id).add(diff);
}
}
/**
* Reset this timer, setting all values to zero.
*
* @param full
*/
void reset(boolean full) {
record.reset();
if (full) {
timed = false;
}
start.set(0);
timingDepth.set(0);
added = false;
children.clear();
checkEnabled();
}
@Override
public TimingHandler getTimingHandler() {
return this;
}
@Override
public boolean equals(Object o) {
return (this == o);
}
@Override
public int hashCode() {
return id;
}
/**
* This is simply for the Closeable interface so it can be used with
* try-with-resources ()
*/
@Override
public void close() {
stopTimingIfSync();
}
public boolean isSpecial() {
return this == TimingsManager.FULL_SERVER_TICK || this == TimingsManager.TIMINGS_TICK;
}
boolean isTimed() {
return timed;
}
public boolean isEnabled() {
return enabled;
}
TimingData[] cloneChildren() {
int i = 0;
final TimingData[] clonedChildren = new TimingData[children.size()];
for (TimingData child : children.values()) {
clonedChildren[i++] = child.clone();
}
return clonedChildren;
}
}

View File

@@ -0,0 +1,200 @@
/*
* This file is licensed under the MIT License (MIT).
*
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package co.aikar.timings;
import com.google.common.base.Function;
import com.google.common.collect.EvictingQueue;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.PluginClassLoader;
import co.aikar.util.LoadingMap;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
/**
* Akarin Changes Note
* 1) Thread safe timing (safety)
*/
public final class TimingsManager {
static final Map<TimingIdentifier, TimingHandler> TIMING_MAP =
Collections.synchronizedMap(LoadingMap.newHashMap(
new Function<TimingIdentifier, TimingHandler>() {
@Override
public TimingHandler apply(TimingIdentifier id) {
return (id.protect ?
new UnsafeTimingHandler(id) :
new TimingHandler(id)
);
}
},
256, .5F
));
public static final FullServerTickHandler FULL_SERVER_TICK = new FullServerTickHandler();
public static final TimingHandler TIMINGS_TICK = Timings.ofSafe("Timings Tick", FULL_SERVER_TICK);
public static final Timing PLUGIN_GROUP_HANDLER = Timings.ofSafe("Plugins");
public static List<String> hiddenConfigs = new ArrayList<String>();
public static boolean privacy = false;
static final Collection<TimingHandler> HANDLERS = Collections.synchronizedCollection(new ArrayDeque<TimingHandler>()); // Akarin
static final ArrayDeque<TimingHistory.MinuteReport> MINUTE_REPORTS = new ArrayDeque<TimingHistory.MinuteReport>();
static EvictingQueue<TimingHistory> HISTORY = EvictingQueue.create(12);
static TimingHandler CURRENT;
static long timingStart = 0;
static long historyStart = 0;
static boolean needsFullReset = false;
static boolean needsRecheckEnabled = false;
private TimingsManager() {}
/**
* Resets all timing data on the next tick
*/
static void reset() {
needsFullReset = true;
}
/**
* Ticked every tick by CraftBukkit to count the number of times a timer
* caused TPS loss.
*/
static void tick() {
if (Timings.timingsEnabled) {
boolean violated = FULL_SERVER_TICK.isViolated();
for (TimingHandler handler : HANDLERS) {
if (handler.isSpecial()) {
// We manually call this
continue;
}
handler.processTick(violated);
}
TimingHistory.playerTicks += Bukkit.getOnlinePlayers().size();
TimingHistory.timedTicks++;
// Generate TPS/Ping/Tick reports every minute
}
}
static void stopServer() {
Timings.timingsEnabled = false;
recheckEnabled();
}
static void recheckEnabled() {
synchronized (TIMING_MAP) {
for (TimingHandler timings : TIMING_MAP.values()) {
timings.checkEnabled();
}
}
needsRecheckEnabled = false;
}
static void resetTimings() {
if (needsFullReset) {
// Full resets need to re-check every handlers enabled state
// Timing map can be modified from async so we must sync on it.
synchronized (TIMING_MAP) {
for (TimingHandler timings : TIMING_MAP.values()) {
timings.reset(true);
}
}
Bukkit.getLogger().log(Level.INFO, "Timings Reset");
HISTORY.clear();
needsFullReset = false;
needsRecheckEnabled = false;
timingStart = System.currentTimeMillis();
} else {
// Soft resets only need to act on timings that have done something
// Handlers can only be modified on main thread.
for (TimingHandler timings : HANDLERS) {
timings.reset(false);
}
}
HANDLERS.clear();
MINUTE_REPORTS.clear();
TimingHistory.resetTicks(true);
historyStart = System.currentTimeMillis();
}
static TimingHandler getHandler(String group, String name, Timing parent, boolean protect) {
return TIMING_MAP.get(new TimingIdentifier(group, name, parent, protect));
}
/**
* <p>Due to access restrictions, we need a helper method to get a Command TimingHandler with String group</p>
*
* Plugins should never call this
*
* @param pluginName Plugin this command is associated with
* @param command Command to get timings for
* @return TimingHandler
*/
public static Timing getCommandTiming(String pluginName, Command command) {
Plugin plugin = null;
final Server server = Bukkit.getServer();
if (!( server == null || pluginName == null ||
"minecraft".equals(pluginName) || "bukkit".equals(pluginName) ||
"spigot".equalsIgnoreCase(pluginName) || "paper".equals(pluginName)
)) {
plugin = server.getPluginManager().getPlugin(pluginName);
}
if (plugin == null) {
// Plugin is passing custom fallback prefix, try to look up by class loader
plugin = getPluginByClassloader(command.getClass());
}
if (plugin == null) {
return Timings.ofSafe("Command: " + pluginName + ":" + command.getTimingName());
}
return Timings.ofSafe(plugin, "Command: " + pluginName + ":" + command.getTimingName());
}
/**
* Looks up the class loader for the specified class, and if it is a PluginClassLoader, return the
* Plugin that created this class.
*
* @param clazz Class to check
* @return Plugin if created by a plugin
*/
public static Plugin getPluginByClassloader(Class<?> clazz) {
if (clazz == null) {
return null;
}
final ClassLoader classLoader = clazz.getClassLoader();
if (classLoader instanceof PluginClassLoader) {
PluginClassLoader pluginClassLoader = (PluginClassLoader) classLoader;
return pluginClassLoader.getPlugin();
}
return null;
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2015. Starlis LLC / dba Empire Minecraft
*
* This source code is proprietary software and must not be redistributed without Starlis LLC's approval
*
*/
package co.aikar.util;
import java.util.HashMap;
import com.google.common.base.Function;
/**
* Allows you to pass a Loader function that when a key is accessed that doesn't exist,
* automatically loads the entry into the map by calling the loader Function.
*
* .get() Will only return null if the Loader can return null.
*
* You may pass any backing Map to use.
*
* This class is not thread safe and should be wrapped with Collections.synchronizedMap on the OUTSIDE of the LoadingMap if needed.
*
* Do not wrap the backing map with Collections.synchronizedMap.
*
* @param <V> Value
*/
public class LoadingIntMap<V> extends HashMap<Integer, V> { // Akarin - HashMap
private static final long serialVersionUID = 1L;
private final Function<Integer, V> loader;
public LoadingIntMap(Function<Integer, V> loader) {
super();
this.loader = loader;
}
public LoadingIntMap(int expectedSize, Function<Integer, V> loader) {
super(expectedSize);
this.loader = loader;
}
public LoadingIntMap(int expectedSize, float loadFactor, Function<Integer, V> loader) {
super(expectedSize, loadFactor);
this.loader = loader;
}
@Override
public V get(Object key) {
V res = super.get(key);
if (res == null) {
res = loader.apply((Integer) key);
if (res != null) {
put((Integer) key, res);
}
}
return res;
}
/**
* Due to java stuff, you will need to cast it to (Function) for some cases
*
* @param <T> Type
*/
public abstract static class Feeder <T> implements Function<T, T> {
@Override
public T apply(Object input) {
return apply();
}
public abstract T apply();
}
}

View File

@@ -3,16 +3,9 @@ package io.akarin.api.internal;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Queue;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -21,15 +14,12 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
import co.aikar.timings.Timing;
import co.aikar.timings.Timings;
import io.akarin.api.internal.Akari.AssignableFactory;
import io.akarin.api.internal.Akari.AssignableThread;
import io.akarin.api.internal.utils.ReentrantSpinningLock;
import io.akarin.api.internal.utils.thread.SuspendableExecutorCompletionService;
import io.akarin.api.internal.utils.thread.SuspendableThreadPoolExecutor;
import io.akarin.server.core.AkarinGlobalConfig;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.World;
import net.minecraft.server.WorldServer;
@SuppressWarnings("restriction")
public abstract class Akari {
@@ -91,7 +81,22 @@ public abstract class Akari {
}
public static void resizeTickExecutors(int worlds) {
STAGE_TICK = new SuspendableExecutorCompletionService<>(new SuspendableThreadPoolExecutor(worlds, worlds,
int parallelism;
switch (AkarinGlobalConfig.parallelMode) {
case -1:
return;
case 0:
parallelism = 1;
break;
case 1:
parallelism = worlds;
break;
case 2:
default:
parallelism = worlds * 2;
break;
}
STAGE_TICK = new SuspendableExecutorCompletionService<>(new SuspendableThreadPoolExecutor(parallelism, parallelism,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
new AssignableFactory("Akarin Parallel Ticking Thread - $")));

View File

@@ -160,11 +160,6 @@ public class AkarinGlobalConfig {
playersPerIOThread = getInt("core.players-per-chunk-io-thread", 50);
}
public static boolean silentAsyncTimings;
private static void silentAsyncTimings() {
silentAsyncTimings = getBoolean("core.always-silent-async-timing", false);
}
public static long timeUpdateInterval;
private static void timeUpdateInterval() {
timeUpdateInterval = getSeconds(getString("core.tick-rate.world-time-update-interval", "1s")) * 10;
@@ -234,4 +229,9 @@ public class AkarinGlobalConfig {
private static void fileIOThreads() {
fileIOThreads = getInt("core.chunk-save-threads", 2);
}
public static int parallelMode;
private static void parallelMode() {
parallelMode = getInt("core.parallel-mode", 1);
}
}

View File

@@ -89,6 +89,8 @@ public abstract class MixinMinecraftServer {
private boolean tickEntities(WorldServer world) {
try {
world.tickEntities();
world.getTracker().updatePlayers();
world.explosionDensityCache.clear(); // Paper - Optimize explosions
} catch (Throwable throwable) {
CrashReport crashreport;
try {
@@ -147,35 +149,78 @@ public abstract class MixinMinecraftServer {
Akari.worldTiming.startTiming();
if (cachedWorldSize != worlds.size()) Akari.resizeTickExecutors((cachedWorldSize = worlds.size()));
// Never tick one world concurrently!
for (int i = 0; i < cachedWorldSize; i++) {
// Impl Note:
// Entities ticking: index 1 -> ... -> 0 (parallel)
// World ticking: index 0 -> ... (parallel)
int interlace = i + 1;
WorldServer entityWorld = worlds.get(interlace < cachedWorldSize ? interlace : 0);
Akari.STAGE_TICK.submit(() -> {
synchronized (((IMixinLockProvider) entityWorld).lock()) {
tickEntities(entityWorld);
entityWorld.getTracker().updatePlayers();
entityWorld.explosionDensityCache.clear(); // Paper - Optimize explosions
switch (AkarinGlobalConfig.parallelMode) {
case 1:
case 2:
default:
// Never tick one world concurrently!
for (int i = 0; i < cachedWorldSize; i++) {
// Impl Note:
// Entities ticking: index 1 -> ... -> 0 (parallel)
// World ticking: index 0 -> ... (parallel)
int interlace = i + 1;
WorldServer entityWorld = worlds.get(interlace < cachedWorldSize ? interlace : 0);
Akari.STAGE_TICK.submit(() -> {
synchronized (((IMixinLockProvider) entityWorld).lock()) {
tickEntities(entityWorld);
}
}, new TimingSignal(entityWorld, true));
WorldServer world = worlds.get(i);
switch (AkarinGlobalConfig.parallelMode) {
case 1:
world.timings.doTick.startTiming();
synchronized (((IMixinLockProvider) world).lock()) {
tickWorld(world);
}
world.timings.doTick.stopTiming();
break;
default:
Akari.STAGE_TICK.submit(() -> {
synchronized (((IMixinLockProvider) world).lock()) {
tickWorld(world);
}
}, new TimingSignal(world, false));
break;
}
}
}, new TimingSignal(entityWorld, true));
WorldServer world = worlds.get(i);
world.timings.doTick.startTiming();
synchronized (((IMixinLockProvider) world).lock()) {
tickWorld(world);
}
world.timings.doTick.stopTiming();
}
for (int i = cachedWorldSize; i --> 0 ;) {
long startTiming = System.nanoTime();
TimingSignal signal = Akari.STAGE_TICK.take().get();
IMixinTimingHandler timing = (IMixinTimingHandler) (signal.isEntities ? signal.tickedWorld.timings.tickEntities : signal.tickedWorld.timings.doTick);
timing.stopTiming(startTiming);
for (int i = AkarinGlobalConfig.parallelMode == 1 ? cachedWorldSize : cachedWorldSize * 2; i --> 0 ;) {
long startTiming = System.nanoTime();
TimingSignal signal = Akari.STAGE_TICK.take().get();
IMixinTimingHandler timing = (IMixinTimingHandler) (signal.isEntities ? signal.tickedWorld.timings.tickEntities : signal.tickedWorld.timings.doTick);
timing.stopTiming(startTiming);
}
break;
case 0:
Akari.STAGE_TICK.submit(() -> {
for (int i = 1; i <= cachedWorldSize; ++i) {
WorldServer world = worlds.get(i < cachedWorldSize ? i : 0);
synchronized (((IMixinLockProvider) world).lock()) {
tickEntities(world);
}
}
}, null);
for (int i = 0; i < cachedWorldSize; ++i) {
WorldServer world = worlds.get(i);
synchronized (((IMixinLockProvider) world).lock()) {
tickWorld(world);
}
}
Akari.STAGE_TICK.take();
break;
case -1:
for (int i = 0; i < cachedWorldSize; ++i) {
WorldServer world = worlds.get(i);
tickWorld(world);
tickEntities(world);
}
break;
}
Akari.worldTiming.stopTiming();
Akari.callbackTiming.startTiming();
while ((runnable = Akari.callbackQueue.poll()) != null) runnable.run();

View File

@@ -1,34 +1,36 @@
package io.akarin.server.mixin.core;
import java.util.logging.Level;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.bukkit.Bukkit;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import co.aikar.timings.Timing;
import io.akarin.api.internal.Akari;
import io.akarin.api.internal.Akari.AssignableThread;
import io.akarin.api.internal.mixin.IMixinTimingHandler;
import io.akarin.server.core.AkarinGlobalConfig;
import net.minecraft.server.MinecraftServer;
@Mixin(targets = "co.aikar.timings.TimingHandler", remap = false)
public abstract class MixinTimingHandler implements IMixinTimingHandler {
@Shadow @Final String name;
@Shadow private boolean enabled;
@Shadow private long start;
@Shadow private int timingDepth;
@Shadow private AtomicLong start;
@Shadow private AtomicInteger timingDepth;
@Shadow abstract void addDiff(long diff);
@Shadow public abstract Timing startTiming();
@Overwrite
public Timing startTimingIfSync() {
startTiming();
return (Timing) this;
}
@Overwrite
public void stopTimingIfSync() {
if (Akari.isPrimaryThread(false)) {
//if (Akari.isPrimaryThread(false)) {
stopTiming(true); // Avoid twice thread check
}
//}
}
@Overwrite
@@ -37,27 +39,24 @@ public abstract class MixinTimingHandler implements IMixinTimingHandler {
}
public void stopTiming(long start) {
if (enabled && --timingDepth == 0 && start != 0) {
addDiff(System.nanoTime() - start);
}
if (enabled) addDiff(System.nanoTime() - start);
}
public void stopTiming(boolean alreadySync) {
if (!enabled || --timingDepth != 0 || start == 0) return;
if (!alreadySync) {
if (!enabled || timingDepth.decrementAndGet() != 0 || start.get() == 0) return;
/*if (!alreadySync) {
Thread curThread = Thread.currentThread();
if (curThread != MinecraftServer.getServer().primaryThread) {
if (!AkarinGlobalConfig.silentAsyncTimings) {
if (false && !AkarinGlobalConfig.silentAsyncTimings) {
Bukkit.getLogger().log(Level.SEVERE, "stopTiming called async for " + name);
Thread.dumpStack();
}
start = 0;
return;
}
}
}*/
// Safety ensured
addDiff(System.nanoTime() - start);
start = 0;
addDiff(System.nanoTime() - start.getAndSet(0));
}
}

View File

@@ -19,6 +19,7 @@ import com.google.common.base.Preconditions;
import io.akarin.api.internal.Akari;
import io.akarin.api.internal.utils.thread.SuspendableExecutorCompletionService;
// Paper end
import io.akarin.server.core.AkarinGlobalConfig;
/**
* Akarin Changes Note
@@ -72,13 +73,21 @@ public interface EventExecutor {
public void execute(Listener listener, Event event) throws EventException {
if (!eventClass.isInstance(event)) return;
try {
Akari.eventSuspendTiming.startTimingIfSync(); // Akarin
Akari.STAGE_TICK.suspend(); // Akarin
Akari.eventSuspendTiming.stopTimingIfSync(); // Akarin
// Akarin start
if (AkarinGlobalConfig.parallelMode != -1) {
Akari.eventSuspendTiming.startTiming();
Akari.STAGE_TICK.suspend();
Akari.eventSuspendTiming.stopTiming();
}
// Akarin end
asmExecutor.execute(listener, event);
Akari.eventResumeTiming.startTimingIfSync(); // Akarin
Akari.STAGE_TICK.resume(); // Akarin
Akari.eventResumeTiming.stopTimingIfSync(); // Akarin
// Akarin start
if (AkarinGlobalConfig.parallelMode != -1) {
Akari.eventResumeTiming.startTiming();
Akari.STAGE_TICK.resume();
Akari.eventResumeTiming.stopTiming();
}
// Akarin end
} catch (Exception e) {
throw new EventException(e);
}