 layue13andClaude Opus 5.5
|
39e7f11a96
|
fix: 关闭监听器时同时关闭 boss 与 worker 事件循环
ProxySessionListener.close() 原先等 boss 组 shutdownGracefully 完成后才开始关闭
worker 组。两组各自要等 2 秒静默期,所以每次 close 至少约 4 秒。测试用
close().get(5, SECONDS) 收尾,在较慢的 Gitea runner 上 SessionTransferTest 有两个
用例因此超时(run 14129);本机 SessionTransferTest 每个用例也都在 4.16 秒以上,最慢
5.18 秒。
改为同时开始关闭两组,两者都结束后再完成 shutdown。各组仍保留默认静默期,关闭前已
提交的任务照常执行。本机 SessionTransferTest 单用例中位数 4.19 秒降到 2.15 秒,最慢
5.18 秒降到 3.13 秒。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-24 01:05:53 +08:00 |
|
layue13
|
cd57bef0fd
|
Merge pull request #14 from layue13/perf/websocket-cdn
perf: WebSocket 经 CDN:wss 改用 BoringSSL、保活 Ping、整合包形态基准
|
2026-10-23 17:50:11 +08:00 |
|
layue13
|
590e594e59
|
Merge pull request #17 from layue13/fix/shutdown-accept-flake
test: 修复 shutdownWhileAccepting… 在 Linux 上的偶发超时
|
2026-10-23 17:50:07 +08:00 |
|
 layue13andClaude Opus 5.5
|
6d196a83bb
|
test: 修复关闭监听时半开连接导致的偶发超时
shutdownWhileAcceptingClientsClosesEveryRegisteredSession 在 Linux CI 上偶发
SocketTimeoutException。在 Docker(Linux, 4 CPU)中复现 3/80,超时时 ss -tan 显示
卡住的客户端为 ESTAB,而服务端端口上没有该四元组的任何 socket:内核在监听 socket
关闭的竞争窗口内丢弃了握手的最后一个 ACK 且不发 RST,Netty 从未 accept 这条连接,
客户端不发数据就永远收不到任何东西。
读超时时改为写一个字节并要求收到 RST:无服务端 socket 必然回 RST;若代理 accept
后泄漏了连接,字节会被接收、读仍然超时,测试依旧失败。修复后 Docker 中 150/150 通过,
其中 8 次走到半开分支。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-23 17:36:30 +08:00 |
|
 layue13andClaude Opus 5.5
|
85f56d042c
|
fix: 关闭插件宿主时不再持有宿主锁等待 onDisable
- PluginHost.close() 原本整个方法持有宿主锁,包括等待各插件 onDisable 的最长 10 秒。插件在
onDisable 里关闭自己的命令注册(文档推荐的做法)时,CommandService 的注销要拿同一把锁,
于是一直等到截止时间被放弃:onDisable 后面的清理(线程池、数据库连接池)不会执行,日志里
出现 “onDisable exceeded shutdown deadline”。AetherShard 每次停机都会遇到
- 现在只在 onDisable 之前和之后的步骤持锁,等待插件时不持锁;每个插件的 deactivateAndRemove
仍在锁内。同时调用 close() 的第二个线程等第一个完成后返回,与之前被锁挡住时的效果一致
- PluginHostTest:插件在 onDisable 里关闭命令注册,close() 远早于截止时间返回且 onDisable
执行完;修复前该测试失败
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-23 16:47:13 +08:00 |
|
 layue13andClaude Opus 5.5
|
b7d6497ca8
|
perf: WebSocket 经 CDN:wss 改用 BoringSSL、保活 Ping、整合包形态基准
- ProxySessionBenchmark 新增 --profile modded(仿大型整合包的双向包组与 2 MiB 登录突发,逐字节校验)、
--websocket-tls 1(keytool 一次性证书的 wss)和 --client-coalesce 1。
- wss 监听改用随包附带的 BoringSSL(netty-tcnative-boringssl-static 2.0.84):TLS 占 wss 代理 I/O CPU 的 55%,
换用后每帧 CPU 在每一对配对测量中都更低(窗口 1 -22%,窗口 8 -36%),帧率 +31%/+34%;其他平台回落 JDK,
启动日志标明所用实现。jar 由 10.4 MB 增至 16.5 MB。
- 新增 websocket.pingIntervalSeconds(默认 30,0 关闭):升级后代理静默不超过该时长,期间无写出就发空 Ping,
避免 CDN/负载均衡在安静的登录、选服阶段按空闲超时断开。IdleStateHandler 版本在窗口 8 多耗 7.6% CPU,
未达规则;改为写时置位标志、每半个间隔检查的实现,JFR 中几乎不可见。
- 文档:WebSocket 接入新增“经 CDN 部署”,更正消息大小描述;记录见
testing/benchmarks/results/2026-10-23-websocket-cdn.md。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-23 16:40:48 +08:00 |
|
 layue13andClaude Opus 5.5
|
284c2f81cf
|
feat: 管理控制台(JLine)与内置命令
- core.admin.AdminView:在线玩家(地址、TCP/WebSocket、所在服务器、在线时长)、服务器
(地址、人数、来源)与状态(版本、运行时长、传输层、监听地址、堆内存、最近 10 分钟活动)
的只读快照;不从控制台线程直接读取会话状态
- Session 在发布玩家时记录连接详情;ProxySessionListener.onlinePlayers()
- ConnectionLog 用 RecentCounter(10 个一分钟桶)统计加入、离开、拒绝、转服与失败;
连接数上限与插件池拒绝计入过载
- app.console:Console 在终端中使用 JLine(提示符固定在底部、历史、Tab 补全),否则按行读取
标准输入;ConsoleLogAppender 让日志打印在提示符上方并为警告/错误着色
- 内置命令 help/list/servers/player/status/send/kick/loglevel/stop,先于插件控制台命令;
同名插件命令在启动时警告;send/kick/loglevel/stop 写入 moonbridge.admin
- 依赖:JLine 3.30.17(reader、terminal、FFM 提供者),logback 改为编译期依赖
- 文档:operations.md 控制台命令、logging.md、CLAUDE.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-22 21:59:09 +08:00 |
|
 layue13andClaude Opus 5.5
|
21a5adf18d
|
perf: WebSocket 出站每次 flush 合并为一条二进制消息
- WebSocketFrameAdapter:flush 前写入的字节用 CoalescingBufferQueue 合并为一条
BinaryWebSocketFrame,最大 64 KiB。之前每个 Minecraft 帧一条消息,而 Netty 的编码器对
小于 1 KiB 的消息每条都分配并复制一次。每次写入的 promise 在承载它的消息写出时完成;
未 flush 的写入在关闭时失败并释放;排队字节计入通道可写性。入站不变
- ProxySessionBenchmark:新增 --websocket 1,代理阶段客户端走 ws://
- 记录 testing/benchmarks/results/2026-10-22-websocket-coalescing:burst32 每帧 CPU 低 21%
且范围不重叠、吞吐更高;window 1 无变化;window 16 噪声大,补跑 3 对后合计无回退。
满足事先规则,采用
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-22 20:56:00 +08:00 |
|
layue13
|
74afaba6f8
|
Merge remote-tracking branch 'origin/main' into feat/plugin-dependencies
# Conflicts:
# proxy/core/src/main/java/dev/moonbridge/core/plugin/PluginHost.java
|
2026-10-22 19:36:10 +08:00 |
|
 layue13andClaude Opus 5.5
|
4565af2475
|
perf: Uranium 上读取 GameProfile 不再用反射,代理用 MethodHandle 创建插件
- 后端:ProfileAccess 按服务器选择访问方式,启动时选一次。Uranium 使用编译进 JAR 的
UraniumProfileAccess(CraftPlayer#getProfile、authlib 1.5.16,签名来自 TDLM/Uranium 源码与
authlib 字节码),没有反射;其他服务器(如 Spigot 1.7.10)使用缓存反射回退
ReflectiveProfileAccess;编译调用链接失败时记警告并改用回退
- 剥离会话证明的算法只保留一份,两种访问方式共用
- src/serverStubs:只用于编译和测试的服务器类副本,不进 JAR;verifyBackendJar 额外检查
不含 com/mojang/
- 代理:PluginConstructors 用 MethodHandles.publicLookup().findConstructor 创建插件,
不再使用 java.lang.reflect 与 ServiceLoader
- 测试:UraniumProfileAccessTest、PluginConstructorsTest;文档说明各服务器的访问方式
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-22 19:19:41 +08:00 |
|
 layue13andClaude Opus 5.5
|
25a7d0e1bb
|
test: 转服截止测试把后端连接被重置也视为代理已关闭
cutoverTimesOutWhenOldBackendWriteNeverCompletes 在 CI 上偶发失败:代理在截止后关闭候选
后端连接时,若该连接上还有未读取的后端帧,Linux 会发送 RST,后端读到 Connection reset
而不是 EOF。两者都表示代理已关闭连接。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-22 18:57:48 +08:00 |
|
 layue13andClaude Opus 5.5
|
fc1345d010
|
perf: 去掉冷路径上的重复开销,并记录未采用的中继批量完成实验
- Message.validateChannel:通道名正则只编译一次(原来每条消息 String.matches 都重新编译),
长度检查不再分配字节数组
- 后端 OwnedBukkitSessionService:反射查找按接收者类用 ClassValue 缓存(含未找到),
玩家每次加入不再重复 getMethod
- testing/benchmarks/results/2026-10-22-relay-batch-completion:中继只给每批最后一次写入
挂完成回调。I/O 线程分配降 22%、中继 lambda 降 97%,但每帧 CPU 只低 2.4% 且范围重叠,
未达到事先设定的 10% 规则,未采用;补丁随记录保留
- PluginHostTest.placementFailuresOnlyFailTheirOwnRequest:40 ms 超时在负载下可能早于插件
处理器运行而偶发失败,改为 500 ms 并断言首个请求确实到达插件
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-22 18:34:36 +08:00 |
|
 layue13andClaude Opus 5.5
|
fc02bbdda6
|
feat: 插件之间的依赖、类共享与类型化服务
- 可选的 META-INF/moonbridge-plugin.yml(id、version、main、dependencies,可选依赖),
没有描述文件的插件照旧加载
- 加载顺序按依赖图(PluginGraph):依赖先 onLoad/onEnable、后 onDisable;缺少依赖、
依赖未启用、循环依赖、重复 id、描述文件不合法时给出准确原因并启动失败
- PluginClassLoader 在插件 API 与自身之后查找声明的依赖(含传递依赖);只有声明过的依赖可见
- PluginContext.services():每个类型一个提供方,只对提供方和依赖它的插件可见,随停用注销
- PluginContext.plugins():已加载的带描述文件插件(id、版本、依赖)
- plugins.enabled 可以用 id 作键;数据目录仍按实现类名,无需迁移
- LuckPerms 平台插件的描述文件 id 为 luckperms,版本在构建时写入
- 文档:docs/plugins.md「依赖其他插件」
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-22 17:31:35 +08:00 |
|
layue13
|
e890645ba5
|
Merge remote-tracking branch 'origin/main' into feat/logging
# Conflicts:
# proxy/core/src/test/java/dev/moonbridge/app/ProxyConfigurationTest.java
|
2026-10-22 16:57:55 +08:00 |
|
 layue13andClaude Opus 5.5
|
7f68e5682e
|
feat: 运维日志策略、玩家与服务器事件日志、滚动日志文件
- docs/logging.md 规定各级别写什么、永远不写什么,以及新增日志的做法
- core.log.ConnectionLog 统一写玩家(加入、离开、转服、转服失败、未能加入)和服务器
(注册、地址变更、注销、不可达/恢复)事件,logger 为 moonbridge.players / moonbridge.servers
- LogText 清理对端与插件文本(控制字符、双向文字控制字符、长度上限),防止伪造日志行
- logging.playerAddresses 开关:false 时玩家地址写成 [hidden]
- Session 记录结束原因(先记录者为准),在 closePair 中写离开或未能加入的原因
- 合并的 WARN(ThrottledWarning):连接数上限、插件命令/补全/事件/初次选服过载
- 控制通道:租约过期、注册被拒(按地址和原因去重);插件启用/停用;启动与关闭摘要
- logback:控制台 + logs/latest.log,按天压缩滚动保留 14 天;测试只写控制台
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-22 16:44:27 +08:00 |
|
 layue13andClaude Opus 5.5
|
3f8e14f54d
|
fix: 修复代码审查发现的一批缺陷
- 控制通道:注册字段长度上限统一放在 messaging 的 ControlHandshakeLimits,后端启动时
按代理的上限校验,不再出现后端接受、代理每次重连都拒绝的配置;后端按阶段记录连接、
注册与断线失败(每类只记一次,不含对端数据),配置错误时输出具体缺失的键
- 转服登录:握手与 Login Start 的写入合并为 SessionChannels.writeBackendLogin,
Login Start 无法编码时关闭通道释放已排队的握手,不再泄漏缓冲区
- 命令:插件提供的 CommandSource 的 player()/reply() 不再在宿主锁内调用
- 配置:插件条目没有设置时视为空设置而不是空指针;--validate-config 也检查同一后端名的
密钥冲突;地址解析不再做 DNS,后端地址按目录使用的 URI 规则校验
- WebSocket:IPv4 映射写法(::ffff:a.b.c.d)的可信代理现在能匹配 IPv4 对端
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-22 15:40:57 +08:00 |
|
 layue13andClaude Opus 5.5
|
afea023bce
|
build: 代理发行包改为单个可运行的 moonbridge.jar
发行包去掉 bin/ 启动脚本和 lib/ 下的 42 个依赖 jar,改为与其他代理服务端一样
用 java -jar moonbridge.jar 启动:
- proxy-core 用 shadow 打出含全部依赖的 moonbridge.jar(合并 META-INF/services,
清单写入 Main-Class、Multi-Release 与 Enable-Native-Access: ALL-UNNAMED)
- 泄漏检测默认关闭改由 ProxyMain 在加载 Netty 前设置,显式 -D 仍优先
- 移除不再使用的 application 约定插件
- 安装包检查、烟测与基准脚本改用发行包根目录作 classpath;文档同步
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-22 15:00:14 +08:00 |
|
 layue13andClaude Opus 5.5
|
789648c82f
|
refactor: 仓库根目录按用途分组
- proxy/:core、plugin-api、luckperms 及其上游子模块 vendor/luckperms
- messaging/、backend/ 保持在根目录(messaging 是代理与后端共用的 API)
- build-logic/(原 proxy-build-logic)
- testing/:smoke/ 与 benchmarks/,不进入发行物
Gradle 项目名不变(settings.gradle.kts 映射目录),任务路径与 Maven 坐标照旧。
脚本的仓库根定位、文档与记录中的链接同步更新。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-22 14:34:01 +08:00 |
|