Print the full stack trace from the dedicated server watchdog (#4052)

* Port FullSTackWatchdog

* spotless

* checkstyle

* fixed package

* added test mod

* checkstyle
This commit is contained in:
TelepathicGrunt 2024-09-10 08:16:48 -04:00 committed by GitHub
parent 64afec5a95
commit 4854df7166
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 211 additions and 0 deletions

View file

@ -1 +1,5 @@
version = getSubprojectVersion(project)
testDependencies(project, [
':fabric-command-api-v2'
])

View file

@ -0,0 +1,99 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.crash.report.info;
import java.lang.management.LockInfo;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
public class ThreadPrinting {
/**
* A modified copy of {@link ThreadInfo#toString} without the MAX_FRAMES check.
*/
public static String fullThreadInfoToString(ThreadInfo threadInfo) {
StringBuilder sb = new StringBuilder("\"" + threadInfo.getThreadName() + "\""
+ (threadInfo.isDaemon() ? " daemon" : "")
+ " prio=" + threadInfo.getPriority()
+ " Id=" + threadInfo.getThreadId() + " "
+ threadInfo.getThreadState());
if (threadInfo.getLockName() != null) {
sb.append(" on ").append(threadInfo.getLockName());
}
if (threadInfo.getLockOwnerName() != null) {
sb.append(" owned by \"").append(threadInfo.getLockOwnerName())
.append("\" Id=").append(threadInfo.getLockOwnerId());
}
if (threadInfo.isSuspended()) {
sb.append(" (suspended)");
}
if (threadInfo.isInNative()) {
sb.append(" (in native)");
}
sb.append('\n');
StackTraceElement[] stackTraceElements = threadInfo.getStackTrace();
for (int i = 0; i < stackTraceElements.length; i++) {
StackTraceElement ste = stackTraceElements[i];
sb.append("\tat ").append(ste.toString());
sb.append('\n');
if (i == 0 && threadInfo.getLockInfo() != null) {
Thread.State ts = threadInfo.getThreadState();
switch (ts) {
case BLOCKED -> {
sb.append("\t- blocked on ").append(threadInfo.getLockInfo());
sb.append('\n');
}
case WAITING, TIMED_WAITING -> {
sb.append("\t- waiting on ").append(threadInfo.getLockInfo());
sb.append('\n');
}
default -> {
}
}
}
for (MonitorInfo mi : threadInfo.getLockedMonitors()) {
if (mi.getLockedStackDepth() == i) {
sb.append("\t- locked ").append(mi);
sb.append('\n');
}
}
}
LockInfo[] locks = threadInfo.getLockedSynchronizers();
if (locks.length > 0) {
sb.append("\n\tNumber of locked synchronizers = ").append(locks.length);
sb.append('\n');
for (LockInfo li : locks) {
sb.append("\t- ").append(li);
sb.append('\n');
}
}
sb.append('\n');
return sb.toString();
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.crash.report.info;
import java.lang.management.ThreadInfo;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import net.minecraft.server.dedicated.DedicatedServerWatchdog;
import net.fabricmc.fabric.impl.crash.report.info.ThreadPrinting;
@Mixin(DedicatedServerWatchdog.class)
public class DedicatedServerWatchdogMixin {
@ModifyArg(method = "createCrashReport(Ljava/lang/String;J)Lnet/minecraft/util/crash/CrashReport;",
at = @At(value = "INVOKE",
target = "Ljava/lang/StringBuilder;append(Ljava/lang/Object;)Ljava/lang/StringBuilder;",
ordinal = 0,
remap = false)
)
private static Object printEntireThreadDump(Object object) {
if (object instanceof ThreadInfo threadInfo) {
return ThreadPrinting.fullThreadInfoToString(threadInfo);
}
return object;
}
}

View file

@ -3,6 +3,7 @@
"package": "net.fabricmc.fabric.mixin.crash.report.info",
"compatibilityLevel": "JAVA_21",
"mixins": [
"DedicatedServerWatchdogMixin",
"SystemDetailsMixin"
],
"injectors": {

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.crash.report.info;
import static net.minecraft.server.command.CommandManager.literal;
import com.mojang.brigadier.context.CommandContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.dedicated.DedicatedServerWatchdog;
import net.minecraft.text.Text;
import net.minecraft.util.crash.CrashReport;
import net.minecraft.util.crash.ReportType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
public class ThreadDumpTests implements ModInitializer {
private static final Logger LOGGER = LoggerFactory.getLogger(ThreadDumpTests.class);
@Override
public void onInitialize() {
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) ->
dispatcher.register(literal("print_thread_dump_test_command").executes(this::executeDumpCommand)));
}
private int executeDumpCommand(CommandContext<ServerCommandSource> context) {
final ServerCommandSource source = context.getSource();
CrashReport crashReport = DedicatedServerWatchdog.createCrashReport("Watching Server", context.getSource().getServer().getThread().threadId());
LOGGER.info(crashReport.asString(ReportType.MINECRAFT_CRASH_REPORT));
source.sendFeedback(() -> Text.literal("Thread Dump printed to console."), false);
return 1;
}
}

View file

@ -0,0 +1,13 @@
{
"schemaVersion": 1,
"id": "fabric-crash-report-info-v1-testmod",
"name": "Fabric Crash Report Info (v1) Test Mod",
"version": "1.0.0",
"environment": "*",
"license": "Apache-2.0",
"entrypoints": {
"main": [
"net.fabricmc.fabric.test.crash.report.info.ThreadDumpTests"
]
}
}