- 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>
8.5 KiB
Uranium login stalls with early-stage diagnostics
Date: 2026-09-26 (Asia/Shanghai). The installed StrataProxy plugin smoke used two fresh local Uranium instances, -ReturnToOld, and -DebugSession. The debug option enabled Uranium logging.user-login in the copied servers and proxy session DEBUG logs. The source bundle was not changed.
Observed failures
- In
build/local-uranium-transfer-f05fc625bcb34716bd597807fd159ef9, the initial login tooldcompleted. The plugin's later transfer tonewfailed after 15 seconds withbackend login timed out. Thenewserver had printedDoneand haduser-login: true, but its log contained noLogin attempt. This run predates proxy-side candidate connection and write markers, so it does not show whether the candidate TCP connection completed. - In
build/local-uranium-transfer-cd4885b1e7894cd7bd28b2b5e4146556, the proxy logged an initial TCP connection tooldat 03:39:45.217 and a successful Netty login-frame write at 03:39:45.220. Uranium haduser-login: truebut printed noLogin attempt. The proxy's 15-second login deadline then sentLogin timed out.to the client. Three immediately preceding runs of the same smoke passed. Netty's successful write means the socket write completed; it does not prove that Uranium decoded or processed Login Start. - With the same instrumented proxy and a probe that now decodes Login Disconnect reasons, the first of six later runs failed in
build/local-uranium-transfer-2ead16dd7c7d46c980b8c2e839010708: the probe received{"text":"Login timed out."}. The proxy logged TCP connection and login-frame write success; theoldUranium configuration haduser-login: truebut its log again had noLogin attempt. The other five runs completed both transfers. This reproduces the initial-login stall without relying on the earlier raw-EOF report.
Source-based explanation to test
In Uranium's NetworkSystem.addLanEndpoint, networkManagers.add(networkmanager) runs inside the Netty channel initializer, before the manager is added to the pipeline and before NetworkManager.channelActive assigns its channel. Concurrently, NetworkSystem.networkTick removes any manager for which isChannelOpen() is false; that method returns false while channel is null. The tick can therefore remove a newly accepted manager before it becomes active. C00Handshake.hasPriority() is true, so the handshake can still be handled on Netty I/O, while Login Start is queued for processReceivedPackets() on the server tick. If the manager has already been removed from the tick list, Login Start can wait indefinitely without a Login attempt line. NetHandlerHandshakeTCP.onDisconnect is empty, so an early close may also leave no player-specific log.
This interleaving matches both observed stalls, but no failing run directly recorded removal of a manager before channelActive. It is a hypothesis, not a proven root cause. The next discriminating check is a disposable Uranium build that records manager registration, activation, and removal by connection, or registers the manager only after activation and compares repeated runs under the same smoke conditions.
An isolation check bypassed StrataProxy and sent protocol-5 Handshake and Login Start directly to one fresh Uranium process 50 times (build/direct-uranium-7e2025db389e4ef6bc6e6dcacf8aa2c8). All 50 received Login Success, and Uranium logged 50 Login attempt lines. This shows the backend can process the same basic protocol without the proxy; it does not rule out a timing-sensitive Uranium race or identify a proxy-specific defect. The temporary client script and server logs remain in ignored build/ directories.
A second fresh Uranium process received 100 direct logins with TCP_NODELAY and both framed packets in one socket write (build/direct-uranium-46c3e33ccd134aaa9ba6cf3d45f08942); all 100 received Login Success. This makes simple packet coalescing an insufficient explanation for the observed proxy-path stalls. It does not exclude differences in exact scheduling or wire bytes.
Backend TCP relay capture
The smoke's optional -TraceBackend runs a local byte-forwarding relay in front of each copied Uranium server. It records the first 512 bytes per direction after a successful socket write. In build/local-uranium-transfer-d13bae0327fe46b8b87b4e7257905aeb, the initial login through the old relay succeeded. The proxy then connected to the new relay, and that relay connected to Uranium and forwarded a 29-byte Handshake plus Login Start (0f0005096c6f63616c686f7374f595020c000a4e6574747950726f6265). No bytes returned from new; its early login log remained empty; the candidate timed out after 15 seconds while its proxy channel was still active. This rules out a missing proxy write as the explanation for that failure. A successful control run, build/local-uranium-transfer-64ee3ab68c2040d497590813d65831ab, forwarded the same protocol payload with a different handshake port and received Login Success and Forge traffic from new within about 0.2 seconds; the full roundtrip passed.
The relay confirms a successful write to a TCP socket connected to Uranium. It still cannot prove that Uranium decoded or queued the bytes, nor that its connection manager was removed before activation. The server-side lifecycle remains the discriminating evidence needed for the source-based race hypothesis.
Real Forge client reproduction
The same class of stall occurred with the Prism Launcher Minecraft 1.7.10 / Forge 10.13.4.1614 client in build/local-uranium-transfer-29e6b7c0fb204d54a61edf847ca18fc0. The client launched, connected through StrataProxy, and logged into old as PrismSmoke at 04:08:44. The installed proxy plugin then requested a transfer to new. StrataProxy logged the candidate TCP connection and successful login-frame flush at 04:08:44.687. After 15 seconds it reported backend login timed out; the candidate channel was still registered and active. new had been ready since 04:08:23 and had logging.user-login: true, but its latest.log contained no Login attempt, disconnect, or player entry. The client's latest.log confirmed its initial connection to the proxy; it did not report a transfer completion. The smoke stopped the client after the failed transfer. The local Uranium bundle JAR SHA-256 was B16747D08BAD4B7C67DB8F1F41C9C31066FEAAAC22B527775F604BDFA885BAD2; its source checkout was d7a68f934f with pre-existing unrelated changes.
This demonstrates that the issue is not limited to the synthetic protocol probe. It does not prove that Uranium is the sole cause; the direct Uranium login checks above did not reproduce it, and the exact server-side lifecycle of the failed connection was not recorded.
The next real-client roundtrip, with the same StrataProxy commit b432074 and the same Uranium bundle, passed in build/local-uranium-transfer-8454541ae9954e869b302efe053103b9. PrismSmoke logged into old, transferred to new, returned to old, and remained connected for the 10-second hold. Both candidate connections received Login Success; each backend printed Login attempt and the expected player login, and the new backend recorded the player's departure on return. This successful control shows the rewrite can complete the full Forge roundtrip under the tested setup. Together with the previous failure it establishes an intermittent pre-transfer-login problem, not a deterministic failure of the Forge handoff path.
Uranium source audit
In the inspected Uranium source, CauldronConfig.userLogin defaults to false. NetHandlerLoginServer.processLoginStart logs Login attempt only when that option is true; the usual logged in with entity id line comes much later in ServerConfigurationManager.initializeConnectionToPlayer. Therefore the latter line cannot be used to decide whether Uranium received the initial login packet. In the failing runs, the option was explicitly true on the target server, so the absence of Login attempt means processLoginStart was not reached.
The possible early-removal interleaving remains a hypothesis: NetworkSystem.addLanEndpoint adds the manager before NetworkManager.channelActive assigns its channel; networkTick removes managers whose isChannelOpen() is false; the Handshake packet has priority and can run on Netty I/O, while Login Start uses the main-thread queue. NetHandlerHandshakeTCP.onDisconnect is empty, which also explains why an early handshake-stage close can lack a player-specific line. A Uranium-side connection lifecycle trace is needed to confirm or reject this hypothesis. No Uranium source change was made for this investigation.