Skip to content

Commit 3ed8098

Browse files
committed
Adjust tick loop logic
There were a few problems in the tick loop code: 1. Not all tasks may be executed before server stop 2. Not all tasks may have been executed before server tick 3. Tasks may not have been executed while waiting for next server tick Note that tasks exclusively refer to tasks scheduled directly to the MinecraftServer. For the first issue, we apply a fix where we execute all tasks on server stop. However, this is not a true fix. Tasks could be scheduled while the server is stopping. This simply reduces us to being no worse off than Vanilla. This particularly fixes the client freezing when disconnecting from a singleplayer world - see the logic in Minecraft#disconnect(Screen, boolean, boolean) where it invokes IntegratedServer#halt(boolean). For the second issue, we rework the tick tracking on TickTask so that it uses our own counter which we increment immediately before beginning to process tasks. Additionally, we fix the shouldRun logic so that it allows tasks as long as the current tick is strictly greater-than the task tick (before, it checked if the task tick + 1 (3 Vanilla) was strictly less-than). See comments in the server mixin around #shouldRun, details. We fix the final issue by allowing task execution in #shouldRun if we are currently awaiting the next tick. The reason this is a fix is due to the fact that our logic to await the next tick does not run in the managedBlock function, which bypasses the shouldRun check. Tuinity/Moonrise@e394e33
1 parent c700325 commit 3ed8098

File tree

2 files changed

+69
-12
lines changed

2 files changed

+69
-12
lines changed

paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
if (Runtime.getRuntime().availableProcessors() > 4) {
7676
thread.setPriority(8);
7777
}
78-
@@ -309,7 +_,109 @@
78+
@@ -309,7 +_,124 @@
7979
return server;
8080
}
8181

@@ -116,6 +116,21 @@
116116
+ private long currentTickStart;
117117
+ private long scheduledTickStart;
118118
+ private long taskExecutionTime;
119+
+ /**
120+
+ * The tickCount field is not incremented exactly where and when we want for our
121+
+ * usage here.
122+
+ * <p></p>
123+
+ * There are two problems we need to fix:
124+
+ * <ol>
125+
+ * <li>The tickCount field is not incremented when paused through integrated server.</li>
126+
+ * <li>The tickCount field is incremented after draining tasks (during server tick).</li>
127+
+ * </ol>
128+
+ * Our goal with the tick count here is to prevent executing tasks scheduled after the start
129+
+ * of the current tick, which is marked by the task draining.
130+
+ *
131+
+ * @see #runAllTasksAtTickStart
132+
+ */
133+
+ private final java.util.concurrent.atomic.AtomicInteger tickTaskTickCount = new java.util.concurrent.atomic.AtomicInteger();
119134
+ private final Object statsLock = new Object();
120135
+ private @Nullable double[] tps;
121136
+ private ca.spottedleaf.moonrise.common.time.TickData.@Nullable MSPTData msptData5s;
@@ -562,7 +577,7 @@
562577
if (flush) {
563578
this.savedDataStorage.saveAndJoin();
564579
} else {
565-
@@ -649,19 +_,48 @@
580+
@@ -649,19 +_,49 @@
566581
this.stopServer();
567582
}
568583

@@ -578,6 +593,7 @@
578593
+ // CraftBukkit end
579594
+
580595
protected void stopServer() {
596+
+ if (Thread.currentThread() == this.serverThread) this.executeAllRecentInternalTasks(); // Paper - execute tasks on stop
581597
+ // CraftBukkit start - prevent double stopping on multiple threads
582598
+ synchronized (this.stopLock) {
583599
+ if (this.hasStopped) return;
@@ -648,7 +664,7 @@
648664
this.running = false;
649665
if (wait) {
650666
try {
651-
@@ -729,6 +_,118 @@
667+
@@ -729,6 +_,122 @@
652668
}
653669
}
654670

@@ -699,8 +715,13 @@
699715
+ profiler.push("moonrise:run_all_tasks");
700716
+
701717
+ profiler.push("moonrise:run_all_server");
702-
+ // avoid calling MinecraftServer#pollTask - we just want to execute queued tasks
703-
+ while (super.pollTask()) {
718+
+ // avoid calling pollTask - we just want to execute queued tasks
719+
+ final int currentTick = this.tickTaskTickCount.incrementAndGet();
720+
+ final java.util.function.Predicate<net.minecraft.server.TickTask> taskPredicate = (final TickTask task) -> {
721+
+ // only run tasks scheduled before the current tick - which we incremented above
722+
+ return currentTick - task.getTick() > 0; // currentTick > tick accounting for overflow
723+
+ };
724+
+ while (this.runTaskIf(taskPredicate)) {
704725
+ // execute small amounts of other tasks just in case the number of tasks we are
705726
+ // draining is large - chunk system and packet processing may be latency sensitive
706727
+
@@ -737,7 +758,6 @@
737758
+ }
738759
+
739760
+ // execute tasks while there are tasks and there is time left
740-
+ // note: we do not need to bypass the task execution check here (like managedBlock) since it checks time
741761
+ while (this.pollTask() && (Util.getNanos() - deadline < 0L));
742762
+
743763
+ final long now = Util.getNanos();
@@ -836,7 +856,7 @@
836856
+ );
837857
+
838858
+ // adjust ticksBehind so that it is not greater-than catchup
839-
+ if (ticksBehind > catchup) {
859+
+ if (ticksBehind - catchup > 0L) {
840860
+ final long difference = ticksBehind - catchup;
841861
+ this.tickSchedule.advanceBy(difference, thisTickNanos);
842862
}
@@ -912,25 +932,40 @@
912932
} finally {
913933
this.waitingForNextTick = false;
914934
}
915-
@@ -892,18 +_,24 @@
935+
@@ -892,18 +_,38 @@
916936
}
917937

918938
@Override
919939
- public TickTask wrapRunnable(final Runnable runnable) {
940+
- return new TickTask(this.tickCount, runnable);
920941
+ // Paper start - anything that does try to post to main during watchdog crash, run on watchdog
921942
+ public TickTask wrapRunnable(Runnable runnable) {
922943
+ if (this.hasStopped && Thread.currentThread().equals(this.shutdownThread)) {
923944
+ runnable.run();
924945
+ runnable = () -> {};
925946
+ }
926947
+ // Paper end - anything that does try to post to main during watchdog crash, run on watchdog
927-
return new TickTask(this.tickCount, runnable);
948+
+ return new TickTask(this.tickTaskTickCount.get(), runnable); // Paper - use different tick field for tick tasks (see #shouldRun)
928949
}
929950

930951
@Override
931952
protected boolean shouldRun(final TickTask task) {
932953
- return task.getTick() + 3 < this.tickCount || this.haveTime();
933-
+ return task.getTick() + 1 < this.tickCount || this.haveTime(); // Paper - improve tick loop - do not stall queued tasks
954+
+ // Paper start - improve tick loop - do not stall queued tasks
955+
+ // note: make this overflow safe as well
956+
+ return this.tickTaskTickCount.getPlain() - task.getTick() > 0 ||
957+
+ /*
958+
+ * Ensure that we execute any task as long as we are waiting for the next tick.
959+
+ * The Vanilla server will use managedBlock when awaiting the next tick, but
960+
+ * we do not. The Vanilla managedBlock function will bypass task execution
961+
+ * checks, and in order to ensure we execute tasks like Vanilla we need to also
962+
+ * bypass task execution checks.
963+
+ * This fixes {@link #recordTaskExecutionTimeWhileWaiting} not executing tasks
964+
+ * that it should be executing.
965+
+ */
966+
+ this.waitingForNextTick ||
967+
+ this.haveTime();
968+
+ // Paper end - improve tick loop - do not stall queued tasks
934969
}
935970

936971
@Override

paper-server/patches/sources/net/minecraft/util/thread/BlockableEventLoop.java.patch

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,45 @@
11
--- a/net/minecraft/util/thread/BlockableEventLoop.java
22
+++ b/net/minecraft/util/thread/BlockableEventLoop.java
3-
@@ -38,6 +_,23 @@
3+
@@ -38,6 +_,45 @@
44
MetricsRegistry.INSTANCE.add(this);
55
}
66

77
+ // Paper start
8-
+ public final void executeAllRecentInternalTasks() {
8+
+ public final boolean executeAllRecentInternalTasks() {
99
+ final int pending = this.pendingRunnables.size();
1010
+
1111
+ // note: due to possible recursive execution, we may execute more tasks than we want to
1212
+
13+
+ int ran = 0;
14+
+
1315
+ for (int i = 0; i < pending; ++i) {
1416
+ final R run = this.pendingRunnables.poll();
1517
+ if (run == null) {
1618
+ // recursion
1719
+ break;
1820
+ }
1921
+ this.doRunTask(run);
22+
+ ++ran;
23+
+ }
24+
+
25+
+ return ran > 0;
26+
+ }
27+
+
28+
+ public boolean runTaskIf(final java.util.function.Predicate<? super R> predicate) {
29+
+ if (!this.isSameThread()) {
30+
+ throw new IllegalStateException();
2031
+ }
32+
+
33+
+ final R run = this.pendingRunnables.peek();
34+
+ if (run == null || (predicate != null && !predicate.test(run))) {
35+
+ return false;
36+
+ }
37+
+
38+
+ this.pendingRunnables.remove();
39+
+
40+
+ this.doRunTask(run);
41+
+
42+
+ return true;
2143
+ }
2244
+ // Paper end
2345
+

0 commit comments

Comments
 (0)