记一次fabric服务器异常无法正常关闭的处理

在之前一次关服,服务器又卡在了Stopped IO Workder!
气死我了,直接看看是什么在搞鬼

环境

fabric 1.20.1 - fabric loader 0.18.4
Zulu JDK 25

Mods

有Create 0.5.1-j-build.1631
有各种辅助mod
有Xaero小地图和世界地图
有Carpet和各种拓展

分析

获取PID

  • 首先服务器带着MCDR运行,首先 !!MCDR status 看看服务端的PID

PID 197

如果没有MCDR,可以lsof server.jar 换成实际JAR文件 Windows可以去死了(bushi)
Windows可以用任务管理器看

1
lsof fabric-server-mc.1.20.1-loader.0.18.4-launcher.1.1.1.jar

查看非daemon线程

拿到PID之后,掏出 jstack 来看一下是不是非daemon线程导致的

1
2
jstack 197 > jstack.out
cat jstack.out | grep ' #' | grep -v daemon

看到了2个线程,一个 DestroyJavaVM ,这是Java自带的,只能是另一个 Auto-Sync 线程有问题了

转储

  • 推测是有一个byd创建了一个非daemon线程没有关

存一份heap dump再说

1
jmap -dump:format=b,file=heapdump.hprof 197

分析转储

  • 用jprofiler打开转储

去线程转储过滤 Auto-Sync 看看是哪个byd搞鬼

看到了关键的类: dev.toma.configuration.config.io.FileWatchManager

Bing 搜索一下,找到了这个byd的文件

可以看到第 37 行创建了这个byd线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public FileWatchManager() {
WatchService watchService = null;
try {
watchService = FileSystems.getDefault().newWatchService();
} catch (IOException e) {
Configuration.LOGGER.error(MARKER, "Failed to initialize file watch service due to error, configs won't be automatically refreshed", e);
} finally {
this.service = watchService;
this.executorService = Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = new Thread(r);
t.setName("Auto-Sync thread");
return t; // 非daemon线程
});
}
}

而它确实有关闭的逻辑,但 stop 方法是:

1
2
3
4
5
6
7
8
public void stop() {
try {
executorService.shutdown();
service.close();
} catch (IOException e) {
throw new IllegalStateException("Error while stopping FileWatch service", e);
}
}

可能有任务一直在运行导致它不会关闭

所以我做了一个mod,通过mixin把这个线程设置为daemon线程,完美解决,至于其它问题有了再说