Skip to content

Commit 8ac8910

Browse files
committed
Merge branch 'dev' of github.com:SlimefunGuguProject/Slimefun4 into dev
# 请输入一个提交信息以解释此合并的必要性,尤其是将一个更新后的上游分支 # 合并到主题分支。 # # 以 '#' 开始的行将被忽略,而空的提交说明将终止提交。
2 parents 665d9d3 + 063eb27 commit 8ac8910

13 files changed

Lines changed: 317 additions & 152 deletions

File tree

.github/workflows/spotless.yml

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
name: spotless.yml
1+
name: Spotless
2+
23
on:
34
# push:
45
# branches: [ master, dev ]
@@ -10,13 +11,16 @@ on:
1011
paths:
1112
- 'src/**'
1213
- 'pom.xml'
14+
# workflow_dispatch:
1315

1416
env:
1517
TZ: 'Asia/Shanghai'
1618

1719
jobs:
1820
spotless:
1921
runs-on: ubuntu-latest
22+
outputs:
23+
spotless-status: ${{ steps.spotless-check.outputs.status }}
2024
steps:
2125
- name: Checkout repository
2226
uses: actions/checkout@v4
@@ -32,34 +36,40 @@ jobs:
3236

3337
- name: Spotless check
3438
id: spotless-check
35-
run: mvn -B spotless:check --errors
36-
continue-on-error: ${{ github.event_name == 'push' }}
39+
run: |
40+
set +e
41+
mvn -B spotless:check --errors
42+
STATUS=$?
43+
echo "status=$STATUS" >> $GITHUB_OUTPUT
44+
set -e
45+
exit $STATUS
46+
continue-on-error: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
3747

3848
- name: Spotless apply (if needed)
3949
id: spotless-apply
40-
if: ${{ github.github.event_name == 'push' && failure() && steps.spotless-check.outcome == 'failure' }}
50+
if: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && steps.spotless-check.outputs.status != '0' }}
4151
run: mvn -B spotless:apply --errors
4252

4353
- name: Push changes
4454
uses: EndBug/add-and-commit@v9.1.4
45-
if: ${{ github.github.event_name == 'push'}}
55+
if: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && steps.spotless-check.outputs.status != '0' && success()}}
4656
with:
47-
github_token: ${{ secrets.ACCESS_TOKEN }}
57+
github_token: ${{ secrets.BOT_ACCESS_TOKEN }}
4858
add: '.'
4959
push: true
50-
message: 'chore(ci): spotless'
60+
message: 'chore(style): spotless apply'
5161
author_name: 'noraincity-bot'
5262
author_email: ${{ secrets.BOT_EMAIL }}
5363
committer_name: 'noraincity-bot'
5464
committer_email: ${{ secrets.BOT_EMAIL }}
5565
call-dev-ci:
5666
needs: [ spotless ]
57-
if: ${{ github.event_name == 'PushEvent' && github.ref_name == 'dev' }}
67+
if: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref_name == 'dev' }}
5868
uses: ./.github/workflows/dev-ci.yml
5969
secrets: inherit
6070
call-beta-ci:
6171
needs: [ spotless ]
62-
if: ${{ github.event_name == 'push' && github.ref_name == 'master' }}
72+
if: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref_name == 'master' }}
6373
uses: ./.github/workflows/build-ci.yml
6474
secrets: inherit
6575
call-pr-checker:

src/main/java/city/norain/slimefun4/timings/SQLProfiler.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@
1616
import java.util.Set;
1717
import java.util.concurrent.ExecutorService;
1818
import java.util.concurrent.Executors;
19-
import java.util.concurrent.ThreadFactory;
2019
import java.util.logging.Level;
2120
import java.util.stream.Collectors;
2221
import javax.annotation.Nonnull;
2322
import lombok.Getter;
2423
import org.bukkit.command.CommandSender;
2524

2625
public class SQLProfiler {
27-
private final ThreadFactory threadFactory = r -> new Thread(r, "Slimefun SQL Profiler");
28-
29-
private final ExecutorService executor = Executors.newFixedThreadPool(2, threadFactory);
26+
private final ExecutorService reportExecutor = Executors.newFixedThreadPool(1, r -> {
27+
Thread t = new Thread(r, "Slimefun SQL Reporter");
28+
t.setUncaughtExceptionHandler((et, e) -> Slimefun.logger()
29+
.log(Level.SEVERE, "A error occurred in sql report generator thread " + et.getName(), e));
30+
return t;
31+
});
3032

3133
@Getter
3234
private volatile boolean isProfiling = false;
@@ -39,6 +41,10 @@ public class SQLProfiler {
3941

4042
private long startTime = -1L;
4143

44+
public void initSlowSqlCheck(@Nonnull Slimefun plugin) {
45+
Slimefun.getFoliaLib().getScheduler().runTimerAsync(new SlowSqlCheckTask(() -> samplingEntries), 20L, 20L);
46+
}
47+
4248
public void start() {
4349
if (isProfiling) return;
4450

@@ -82,7 +88,13 @@ public void stop() {
8288
samplingEntries.clear();
8389
isProfiling = false;
8490

85-
executor.execute(this::generateReport);
91+
reportExecutor.execute(this::generateReport);
92+
}
93+
94+
public void shutdown() {
95+
stop();
96+
97+
reportExecutor.shutdownNow();
8698
}
8799

88100
public void generateReport() {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package city.norain.slimefun4.timings;
2+
3+
import city.norain.slimefun4.timings.entry.TimingEntry;
4+
import io.github.thebusybiscuit.slimefun4.implementation.Slimefun;
5+
import java.util.Map;
6+
import java.util.function.Supplier;
7+
import java.util.logging.Level;
8+
9+
public class SlowSqlCheckTask implements Runnable {
10+
private final Supplier<Map<TimingEntry, Long>> statusChecker;
11+
12+
public SlowSqlCheckTask(Supplier<Map<TimingEntry, Long>> statusChecker) {
13+
this.statusChecker = statusChecker;
14+
}
15+
16+
@Override
17+
public void run() {
18+
for (Map.Entry<TimingEntry, Long> mapEntry : statusChecker.get().entrySet()) {
19+
var entry = mapEntry.getKey();
20+
var startTime = mapEntry.getValue();
21+
22+
long elapsedTime = System.currentTimeMillis() - startTime;
23+
24+
if (elapsedTime > 5000) {
25+
Slimefun.logger().log(Level.WARNING, "检测到慢 SQL: {0}", entry.normalize());
26+
break;
27+
}
28+
}
29+
}
30+
}

src/main/java/city/norain/slimefun4/timings/entry/SQLEntry.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,9 @@ public String getIdentifier() {
1616
public String normalize() {
1717
return sql;
1818
}
19+
20+
@Override
21+
public int hashCode() {
22+
return sql.hashCode();
23+
}
1924
}

src/main/java/city/norain/slimefun4/utils/ControllerPoolExecutor.java

Lines changed: 0 additions & 68 deletions
This file was deleted.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package city.norain.slimefun4.utils;
2+
3+
import io.github.thebusybiscuit.slimefun4.implementation.Slimefun;
4+
import java.util.List;
5+
import java.util.concurrent.BlockingQueue;
6+
import java.util.concurrent.CopyOnWriteArrayList;
7+
import java.util.concurrent.FutureTask;
8+
import java.util.concurrent.ThreadFactory;
9+
import java.util.concurrent.ThreadPoolExecutor;
10+
import java.util.concurrent.TimeUnit;
11+
import java.util.logging.Level;
12+
import javax.annotation.Nonnull;
13+
import lombok.Getter;
14+
15+
public class SlimefunPoolExecutor extends ThreadPoolExecutor {
16+
@Getter
17+
private final String name;
18+
19+
@Getter
20+
private final List<Thread> runningThreads = new CopyOnWriteArrayList<>();
21+
22+
public SlimefunPoolExecutor(
23+
String name,
24+
int corePoolSize,
25+
int maximumPoolSize,
26+
long keepAliveTime,
27+
@Nonnull TimeUnit unit,
28+
@Nonnull BlockingQueue<Runnable> workQueue,
29+
@Nonnull ThreadFactory threadFactory) {
30+
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
31+
32+
this.name = name;
33+
34+
Slimefun.getProfiler().registerPool(this);
35+
}
36+
37+
@Override
38+
protected void beforeExecute(Thread t, Runnable r) {
39+
super.beforeExecute(t, r);
40+
41+
runningThreads.add(t);
42+
}
43+
44+
@Override
45+
protected void afterExecute(Runnable r, Throwable t) {
46+
try {
47+
super.afterExecute(r, t);
48+
49+
if (t != null) {
50+
Slimefun.logger()
51+
.log(
52+
Level.WARNING,
53+
"An error occurred in " + name + "("
54+
+ Thread.currentThread().getName() + ")",
55+
t);
56+
}
57+
58+
if (r instanceof FutureTask<?> future) {
59+
try {
60+
future.get();
61+
} catch (Exception e) {
62+
Slimefun.logger()
63+
.log(
64+
Level.WARNING,
65+
"An error occurred in " + name + "("
66+
+ Thread.currentThread().getName() + ")",
67+
t);
68+
}
69+
}
70+
} finally {
71+
runningThreads.remove(Thread.currentThread());
72+
}
73+
}
74+
}

src/main/java/city/norain/slimefun4/utils/StringUtil.java

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import com.xzavier0722.mc.plugin.slimefun4.storage.controller.attributes.UniversalDataTrait;
44
import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem;
5+
import java.lang.management.ThreadInfo;
6+
import java.util.Arrays;
57
import java.util.HashSet;
68
import java.util.Set;
79
import org.bukkit.inventory.ItemStack;
@@ -48,4 +50,77 @@ public static String getTraitsStr(Set<UniversalDataTrait> traits) {
4850

4951
return String.join(",", traits.stream().map(Enum::name).toList());
5052
}
53+
54+
/**
55+
* 格式化详细线程信息
56+
*/
57+
public static String formatDetailedThreadInfo(ThreadInfo threadInfo) {
58+
StringBuilder sb = new StringBuilder();
59+
60+
// 线程名和基本信息
61+
sb.append(threadInfo.getThreadName());
62+
sb.append(" #").append(threadInfo.getThreadId());
63+
sb.append(" ").append(threadInfo.getThreadState());
64+
65+
// 锁信息
66+
if (threadInfo.getLockName() != null) {
67+
sb.append(" on ").append(threadInfo.getLockName());
68+
if (threadInfo.getLockOwnerName() != null) {
69+
sb.append(" owned by \"").append(threadInfo.getLockOwnerName()).append("\"");
70+
sb.append(" #").append(threadInfo.getLockOwnerId());
71+
}
72+
}
73+
74+
sb.append("\n");
75+
76+
// 线程状态详情
77+
sb.append(" Thread.State: ").append(threadInfo.getThreadState());
78+
if (threadInfo.getBlockedTime() > 0) {
79+
sb.append(" (blocked for ").append(threadInfo.getBlockedTime()).append("ms)");
80+
}
81+
if (threadInfo.getWaitedTime() > 0) {
82+
sb.append(" (waited for ").append(threadInfo.getWaitedTime()).append("ms)");
83+
}
84+
sb.append("\n");
85+
86+
// 堆栈跟踪
87+
StackTraceElement[] stackTrace = threadInfo.getStackTrace();
88+
for (int i = 0; i < stackTrace.length; i++) {
89+
StackTraceElement element = stackTrace[i];
90+
sb.append("\tat ").append(element.toString()).append("\n");
91+
92+
// 第一个堆栈元素的锁信息
93+
if (i == 0 && threadInfo.getLockName() != null) {
94+
sb.append("\t- ").append(getThreadStateDescription(threadInfo)).append("\n");
95+
}
96+
}
97+
98+
// 持有的锁
99+
if (threadInfo.getLockedMonitors().length > 0) {
100+
sb.append(" Locked monitors:\n");
101+
Arrays.stream(threadInfo.getLockedMonitors())
102+
.forEach(monitor ->
103+
sb.append("\t- locked ").append(monitor.toString()).append("\n"));
104+
}
105+
106+
// 持有的同步器
107+
if (threadInfo.getLockedSynchronizers().length > 0) {
108+
sb.append(" Locked synchronizers:\n");
109+
Arrays.stream(threadInfo.getLockedSynchronizers())
110+
.forEach(sync -> sb.append("\t- ").append(sync.toString()).append("\n"));
111+
}
112+
113+
return sb.toString();
114+
}
115+
116+
private static String getThreadStateDescription(ThreadInfo threadInfo) {
117+
switch (threadInfo.getThreadState()) {
118+
case BLOCKED:
119+
return "blocked on " + threadInfo.getLockName();
120+
case WAITING, TIMED_WAITING:
121+
return "waiting on " + threadInfo.getLockName();
122+
default:
123+
return threadInfo.getThreadState().toString().toLowerCase();
124+
}
125+
}
51126
}

0 commit comments

Comments
 (0)