Skip to content

feat(rpc): 新增虚拟线程友好的同步阻塞调用 invokeSync - #109

Open
Gu-ZT wants to merge 2 commits into
Anvil-Dev:dev/1.21.1from
Gu-ZT:rpc/1.21.1
Open

feat(rpc): 新增虚拟线程友好的同步阻塞调用 invokeSync#109
Gu-ZT wants to merge 2 commits into
Anvil-Dev:dev/1.21.1from
Gu-ZT:rpc/1.21.1

Conversation

@Gu-ZT

@Gu-ZT Gu-ZT commented Aug 24, 2026

Copy link
Copy Markdown
Contributor
  • 新增 RPC.invokeSync 系列(0~16 参 + invokeSyncByName),在(虚拟)线程内阻塞等待对端响应,直接返回结果
  • 复用既有 RpcPendingCalls 登记与超时机制,不引入额外线程池或依赖
  • 保持原有 CompletableFuture invoke 形态不变,回调继续走 ForkJoinPool.commonPool / 调用方显式 executor
  • module.test 新增 /rpctest invoke-sync 命令,在虚拟线程中验证同步调用
  • 新增 invokeSync(target, allowNonVirtual, ref, args...) 0~16 参全部重载,由调用方显式声明是否允许在非虚拟线程阻塞
  • 全部 allowNonVirtual 重载标注 @ApiStatus.Experimental,与原有默认警告版本共存

Gu-ZT added 2 commits August 24, 2026 17:24
- 新增 RPC.invokeSync 系列(0~16 参 + invokeSyncByName),在(虚拟)线程内阻塞等待对端响应,直接返回结果

- 复用既有 RpcPendingCalls 登记与超时机制,不引入额外线程池或依赖

- 保持原有 CompletableFuture invoke 形态不变,回调继续走 ForkJoinPool.commonPool / 调用方显式 executor

- module.test 新增 /rpctest invoke-sync 命令,在虚拟线程中验证同步调用
- 新增 invokeSync(target, allowNonVirtual, ref, args...) 0~16 参全部重载,由调用方显式声明是否允许在非虚拟线程阻塞

- 全部 allowNonVirtual 重载标注 @ApiStatus.Experimental,与原有默认警告版本共存

- 移除 AllowSync 远端方法注解方案,将阻塞决策权归还调用方

- 私有 invokeSyncChecked 增加 allowNonVirtual 参数,非虚拟线程仅警告不阻断
@Gugle2308

Copy link
Copy Markdown

分析完成。所有声称均已交叉验证。输出审查意见:

代码审查摘要 — PR #109

操作: opened
范围: 2 个文件 (2 Java) / 800 行 diff — RPC.java(+650 行,35 个新公共 API)、RpcTestCommands.java(+测试命令)
分支: rpc/1.21.1 → dev/1.21.1(基线 5096f215,已 fetch 验证)

🔴 关键

  • RPC.javaallowNonVirtual 重载 javadoc 与实现自相矛盾,会误导调用者:javadoc 明示「如平台线程池、Netty 线程等已知可阻塞的线程」可以安全阻塞。但本实现中,调用方 future 恰恰是在该连接自身的 Netty 事件循环线程上兑现的——RpcResponsePayload.handle() 直接在 payload listener 里 entry.future().complete(result),无 enqueueWork(已核对 dev/1.21.1 源码 line 106)。在 Netty 线程上 invokeSync(..., true) = 承载响应的同一线程被阻塞 → 响应永远无法被处理 → 只能等主线程 ServerTickEvent.Post 驱动 RpcPendingCalls.tick() 5 秒(100 tick)超时自救,期间该玩家的连接完全冻结(keepalive 积压、包堆积)。请删掉「Netty 线程」这个例子,并显式警告「不得在承载 RPC 连接的网络线程上阻塞」。

⚠️ 警告

  • RPC.java — 主线程 + allowNonVirtual=true 时超时自救失效:超时由主线程的 ServerTickEvent.Post 驱动。若在主线程调用 invokeSync(..., true) 且响应丢失(远端崩溃/报文丢失),主线程被阻塞 → tick() 永远不执行 → join() 永久挂起,整个服务器卡死(只有断线 clear() 能救)。javadoc 已警告勿在主线程调用,但建议点明「超时机制依赖主线程 tick,主线程上阻塞时超时自救会失效」这一不变量。
  • RPC.java — 异常语义未文档化:调用方将收到未检查的 CompletionException(包装 TimeoutException/远端异常/CancellationException);阻塞期间线程被 interrupt 时 join()CancellationException 而非 InterruptedException(JDK 行为),调用方无法区分「中断」与「取消」。另外远端失败路径 completeExceptionally(new CompletionException(...)) 会形成双层包装。建议在 invokeSync 系列 javadoc 补充异常契约。

💡 建议

  • 测试注释不准确RpcTestCommands.java 中「RPC 响应仍在主线程兑现」与实现不符——future 由 Netty 线程(payload listener)直接兑现。建议改注释,避免后续维护者据此误判线程模型。
  • invokeSyncByName 缺少 allowNonVirtual 变体:与 invokeSync 家族不对称,按名动态调用 + 显式允许非虚拟线程的用户无处可去。建议补一个 @ApiStatus.ExperimentalinvokeSyncByName(target, allowNonVirtual, clazz, name, args...)
  • 默认重载在平台线程上每次调用打一条 log.warn:循环调用会刷日志;同时"已知合法"的平台线程(如专用线程池)会持续误报。可考虑限速或一次性警告。
  • @SuppressWarnings("unused") 挂在类级别:范围过宽,掩盖类内所有未使用告警,建议收敛到具体成员。

🟢 看起来不错

  • invokeSyncChecked 与既有 invokeChecked 完全对称(同校验顺序、同 register→send 顺序、同 RpcRequestPayload.encode),行为一致性有保障;对 void 返回与参数个数在 register 前校验,失败不留脏登记项。
  • join() 不会无限挂起(除上述主线程特例):超时路径 tick()TimeoutException 使 future 失败、断线路径 clear()CancellationException 失败,均已在 dev/1.21.1 源码核实。
  • 默认重载对非虚拟线程发出警告是好的护栏,@ApiStatus.Experimental 标注合理;类级 javadoc 的虚拟线程示例(Executors.newVirtualThreadPerTaskExecutor)清晰实用。
  • 测试在虚拟线程内阻塞,主线程不被占用;source.sendSuccess 从异步线程调用与既有 runInvokeTestthenAccept 于 ForkJoinPool)模式一致;测试方法 returnInt/returnString/computeSum/returnCustomType 均存在于 TestRpcMethods(含自定义 BlockPos codec)。

📋 声称验证表

声称 状态 对应证据
invokeSync 0~16 参全重载 17 个 arity,总计 34 个重载
invokeSyncByName 逃生口 与 invokeByName 对称,走同一 invokeSyncChecked
allowNonVirtual 重载全部 @ApiStatus.Experimental 17 处标注,恰为 0~16 的 experimental 变体
复用 RpcPendingCalls 登记与超时,无新线程池 无新增依赖/线程池,超时/断线异常路径完整
原有 CompletableFuture invoke 形态不变 diff 仅新增,无删改
module.test /rpctest invoke-sync 已注册命令并接入 runAllTests 调度(tick 40)

结论: REQUEST_CHANGES — 功能实现本身质量高、与既有架构一致,唯一阻塞项是 allowNonVirtual javadoc 将 Netty 线程列为可阻塞线程与实现直接矛盾(一行文档修改即可);建议同时补充 invokeSyncByName 的 experimental 变体与异常语义说明。

🧪 测试建议

被测目标 推荐测试场景 优先级
RPC.invokeSyncChecked 超时路径:对端不响应 → 验证 join() 抛 CompletionException(TimeoutException),且调用方可捕获 🔴
RPC.invokeSyncChecked 断线路径:调用后断开连接 → CompletionException(CancellationException) 🟡
RPC.invokeSync(target, ..., true) 主线程调用(allowNonVirtual=true)+ 响应丢失 → 验证当前文档化死锁行为的日志告警 🟡
RPC.invokeSync F0~F16 每 arity 至少一次往返(现测试仅覆盖 F0/F1/F2/F3 等价路径) 🟢

由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown

🌿 Roseau API Breaking Change Report

Module Status Breaking Changes
codec ✅ Compatible 0
collision ✅ Compatible 0
config ✅ Compatible 0
integration ✅ Compatible 0
moveable-entity-block ✅ Compatible 0
network ✅ Compatible 0
rendering ⚪ Skipped
space-select ✅ Compatible 0
font ✅ Compatible 0
util ✅ Compatible 0
explosion ✅ Compatible 0
rpc ✅ Compatible 0
multiblock ✅ Compatible 0
recipe ✅ Compatible 0
registrum ✅ Compatible 0
sync ✅ Compatible 0
wheel ✅ Compatible 0
main ✅ Compatible 0

Full CSVs: see the Artifacts section of this workflow run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants