307 Commits
Author SHA1 Message Date
layue13andClaude Sonnet 5.5 4b9f240c1b Split BackendControlService into registry, router, exchange and writer
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-06 03:22:51 -07:00
layue13andClaude Sonnet 5.5 22739ed0c4 Split PluginHost into lifecycle, event, command and placement services
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-06 03:19:50 -07:00
layue13andClaude Sonnet 5.5 133e46f25b Split Session into login, routing, transfer and play packages
Session delegates frontend login to FrontendLogin, first-backend dialing to
InitialRouter and the whole transfer state machine to TransferCoordinator
behind the TransferHost port. Per-frame PLAY handlers move to session.play and
dialing helpers to session.channel. Relay hot path is unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-06 03:16:09 -07:00
layue13 1efbbafbad Merge pull request #2: add CLAUDE.md
Add CLAUDE.md
2026-10-05 18:17:03 -07:00
Claude 3d197e25e2 Add CLAUDE.md
Build/test commands (including single-test runs and the bash ./gradlew
caveat), the module layout with the Java 8 backend constraint, and the
cross-file architecture: session threading, relay data path, transfers,
transport selection, the buffer-ownership rules behind running without
Netty leak detection, and the benchmark method used in this repository.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcCN2hYyqmU22X2Xfg1nwe
2026-10-06 01:16:04 +00:00
layue13 c834879640 Merge pull request #1: batch relay flushes, native transport, leak gate, Session helpers
Batch relay flushes, native transport, leak gate, Session helpers
2026-10-05 18:14:22 -07:00
Claude 8b28df471d Record GraalVM 25.4 vs Temurin 25.0.4.1 comparison
Same jars and benchmark classes on Temurin 25.0.4.1 (C2), Oracle GraalVM
25.4 (Graal JIT) and the same GraalVM with C2. Graal JIT is not better:
on the 32-small-frame burst workload it was 24% slower and used 45% more
proxy I/O CPU per frame in steady state, with no consistent difference on
1 KiB frames. GraalVM with C2 matches Temurin, so the gap comes from the
Graal compiler, not the distribution.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcCN2hYyqmU22X2Xfg1nwe
2026-10-06 01:13:06 +00:00
Claude 941a499965 Guard the last two Session buffer branches
Session.channelRead releases messages that are not frames of its own
session: anything arriving on a channel that is neither its frontend nor
its backend, and any non-ByteBuf message from either. Neither branch had a
test. SessionFrameOwnershipTest builds a Session directly and checks both,
including that a stray channel does not close the real session; replacing
either release with a no-op makes it fail.

docs/testing.md and docs/operations.md no longer list these as untested.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcCN2hYyqmU22X2Xfg1nwe
2026-10-06 00:38:09 +00:00
Claude 298fd3c4fc Cover the remaining Session buffer paths with unit-testable helpers
A parallel mutation pass over Session and ConnectionGate (17 sites, three
workers, failures re-run to rule out load-induced flakiness) left five
sites unguarded. Three of them were buffer handling that only integration
tests reached, so it moves into small classes with direct tests:
TransitionFrames (mapping buffered start-up frames and appending the
rewritten opening packets) and TransitionBuffer (the bounded queue that
releases what it holds). transferOpening now uses ByteBufs.fill. A
mutation run over the two new classes catches all four of their release
sites.

Two defensive branches in Session stay untested and are listed in
docs/testing.md: a non-ByteBuf message (its input always comes from the
frame decoder) and a message from a channel that is neither side of the
session.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcCN2hYyqmU22X2Xfg1nwe
2026-10-06 00:03:36 +00:00
Claude 7de33e72aa Disable Netty leak detection in the distribution
The start scripts now pass -Dio.netty.leakDetection.level=disabled; it can
be re-enabled per deployment with JAVA_OPTS. In its place every
proxy-core test class runs under PARANOID detection through LeakGate (298
tests, no leaks).

Session also moves off hand-written releases at the sites a mutation pass
found unguarded: the login-wait guard is now a FrameTransformHandler and
buffered transition frames go through one mapTransitionFrame helper that
releases on every failure. ConnectionGate gets unit tests with a
TrackingAllocator covering every exit path.

The second mutation pass over Session was stopped at 11 of 24 sites, so
about ten Session releases (mainly transferOpening and the disconnect
packet) are not verified by mutation; docs/testing.md says so.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcCN2hYyqmU22X2Xfg1nwe
2026-10-05 23:26:27 +00:00
Claude 790abb1426 Centralise buffer ownership and gate leaks in tests
Production is meant to run with Netty leak detection off, so refcount
correctness has to come from structure and tests instead.

Structure: the repeated hand-written release patterns move into three
places. ByteBufs.fill/use covers "allocate, write, release on failure"
for every encoder (including LoginStart, Handshake and the encryption
packets, which had no failure handling at all). FrameTransformHandler
owns the borrow/forward/drop/consume protocol for KeepAliveBridge,
PlayerCommandInterceptor and TransferFrameHandler, which no longer call
release(). The cipher handlers extend Netty's MessageToMessageEncoder and
Decoder. A dead active.release() in TabCompletionBridge.close() is gone.

Tests: LeakGate runs every proxy-core test class with PARANOID leak
detection and fails the class if a buffer was leaked; its own test proves
it sees a deliberate leak. Mutation testing (each release() replaced by a
no-op, then the covering tests run) first showed 23 of 47 release sites
unguarded; after the refactor and new tests 32 of 33 remaining sites are
caught and the last was removed by the cipher change. New tests cover
RawRelay (including a real-NIO race where later frames of a read reach the
relay after it closed), TabCompletionBridge, TransferFrameBuffer,
TransferCandidate, the cipher failure paths and the new helpers.

Session.java and ConnectionGate are not part of this commit: a second
mutation pass over them is still running. The full gradle check has not
been re-run on this state yet.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcCN2hYyqmU22X2Xfg1nwe
2026-10-05 23:15:32 +00:00
Claude 5a90143f88 Fix event-loop task queue regression and update Netty
The previous commit moved the I/O loops from NioEventLoopGroup to
MultiThreadIoEventLoopGroup. That group's loops queue tasks in a
LinkedBlockingQueue instead of the MPSC queue the deprecated groups use,
and the relay posts one write-completion task per frame. A JFR profile
showed the queue's offer at about 9% of Java CPU samples on burst
traffic. NetworkTransport now builds its loops with MPSC queues, and a
test pins the queue type.

Netty 4.2.2 -> 4.2.18. In the same workload, Buffer.memoryAddress fell
from 22% of Java samples to out of the profile and total Java samples
on the I/O threads dropped from 1460 to 836.

Records for both, plus the negative results (pooled allocator, batched
CFB8 encryption) and the leak-detection trade-off, are in
benchmarks/results/2026-10-05-event-loop-queue-and-netty.md. The earlier
transport record gets a correction note.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcCN2hYyqmU22X2Xfg1nwe
2026-10-05 22:05:49 +00:00
Claude 0cf4017567 Select a native network transport automatically
NetworkTransport picks epoll on Linux, kqueue on macOS and NIO
elsewhere, or when the native library cannot load. The player listener,
session event loops, backend dials and the DNS resolver all take their
channel types from the selected transport, so backend channels keep
sharing the player's event loop. A new `transport: AUTO|NIO|EPOLL|KQUEUE`
setting forces one choice; an unavailable native transport fails
startup instead of falling back silently. The event loops move from the
deprecated NioEventLoopGroup to MultiThreadIoEventLoopGroup.

The distribution ships the linux-x86_64/aarch_64 epoll and
osx-x86_64/aarch_64 kqueue natives. The start scripts and tests pass
--enable-native-access=ALL-UNNAMED.

On the loopback session benchmark, epoll beat NIO on every median and
regressed nowhere. Only window-1 p50 latency separated cleanly (0.060 vs
0.070 ms); see benchmarks/results/2026-10-05-network-transport.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcCN2hYyqmU22X2Xfg1nwe
2026-10-05 21:05:32 +00:00
Claude 263299c857 Record inline relay write-completion experiment
Handling successful relay write completions inline, instead of queueing
one event-loop task per frame, showed no effect distinguishable from run
order and noise in two order-reversed A/B runs. The candidate was not
kept; the record and raw output stay for future comparisons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcCN2hYyqmU22X2Xfg1nwe
2026-10-05 20:35:08 +00:00
Claude bffc80857c Split helpers and transfer state out of Session
Behavior-preserving cleanup of the 1.8k-line Session handler:
- TransferAttempt and PendingTransfer move to their own files.
- CommandRateLimiter holds the proxy command token bucket, with tests.
- SessionChannels owns backend dialing, address parsing, codec install
  and frame-decoder lookup that initial routing and transfer duplicated.
- One pauseSource helper replaces the two copies of "buffer both sides,
  pause old relay, read until removed".
- Fully qualified names become imports.

Initial routing and transfer now use the same TCP address check, so a
registration with a port above 65535 fails a transfer as
SERVER_UNAVAILABLE instead of as a dial error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcCN2hYyqmU22X2Xfg1nwe
2026-10-05 20:35:08 +00:00
Claude 90b21a5b88 Flush relayed frames once per read batch
The PLAY relay called writeAndFlush for every decoded frame. With
TCP_NODELAY, a socket read holding many small packets (a typical server
tick) cost one flush, write syscall and TCP segment per frame. Write each
frame and flush the peer on channelReadComplete. A per-batch event-loop
task flushes frames forwarded outside a socket read, such as delayed tab
completion. Paused-frame replay keeps writeAndFlush.

ProxySessionBenchmark gains --burst plus proxy I/O CPU and TCP segment
counters. On a 4 vCPU Linux container with 32 x 33-byte frames per write,
TCP segments/frame fell from 1.379 to 0.117 and proxy I/O CPU/frame from
4079 to 1827 ns (medians of six paired phases). The existing 1 KiB window
1 and 16 workloads showed no regression.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcCN2hYyqmU22X2Xfg1nwe
2026-10-05 20:11:18 +00:00
Claude 7086c4f38d Make relay socket backpressure test hold on Linux
RawRelay stops reading while any forwarded write is incomplete. On Linux a
short socket read can stay below the 2 KiB high water mark, so the target
channel stays writable while the relay correctly waits. The test waited for
non-writability and timed out deterministically (writable=true,
pending=1632). Wait for a real pending socket write instead; the bounded
queue and exact byte-stream assertions are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcCN2hYyqmU22X2Xfg1nwe
2026-10-05 20:11:18 +00:00
layue13 e300160500 Merge pull request 'Use MariaDB 13.0.2 for permission storage' (#23) from feat/mariadb-13.0.2 into main 2026-10-01 21:32:41 +00:00
layue13 15e5abb325 Default permission storage to MariaDB 13.0.2 and verify recovery 2026-10-02 05:30:59 +08:00
layue13 52ffadcd26 Merge pull request 'Restore MoonBridge to approved transfer-event scope' (#22) from revert/authority-only-scope into main 2026-09-30 22:31:42 +00:00
layue13 21e6006104 Revert "Merge pull request 'Authenticate shared backend sessions before Join' (#20) from feature/early-backend-session into main"
This reverts commit 08eb4454f4, reversing
changes made to 043123e168.
2026-10-01 06:27:29 +08:00
layue13 631b623a3f Revert "Merge pull request 'Exercise Uranium authoritative loading on real transfers' (#21) from test/uranium-player-data-authority into main"
This reverts commit a2382b2bd5, reversing
changes made to 08eb4454f4.
2026-10-01 06:27:29 +08:00
layue13 a2382b2bd5 Merge pull request 'Exercise Uranium authoritative loading on real transfers' (#21) from test/uranium-player-data-authority into main 2026-09-30 22:17:11 +00:00
layue13 9675fa42e6 Exercise Uranium authoritative player loading on real transfers 2026-10-01 05:55:18 +08:00
layue13 08eb4454f4 Merge pull request 'Authenticate shared backend sessions before Join' (#20) from feature/early-backend-session into main 2026-09-30 21:04:12 +00:00
layue13 66762ac8bb Authenticate backend player sessions before Join 2026-10-01 05:00:01 +08:00
layue13 043123e168 Merge pull request 'Optional source-release transfer coordination events' (#19) from feature/transfer-coordination into main 2026-09-30 20:10:42 +00:00
layue13 d7e3f13d66 Simplify source release confirmation API 2026-10-01 04:00:07 +08:00
layue13 1bf903ea35 Measure transfer coordination and default PLAY paths 2026-10-01 03:30:45 +08:00
layue13 e8cc2a72bd Add optional source-release transfer coordination events 2026-10-01 02:39:31 +08:00
layue13 876de1a056 Merge pull request 'Revert PlayerDataSQL transfer guard changes from MoonBridge' (#18) from revert-playerdata-transfer-guard into main 2026-09-29 20:38:41 +00:00
layue13 bc6db124a7 Revert "Merge pull request 'Guard all backend transfers and recover failed handoffs' (#17) from feature/transfer-guard-lifecycle into main"
This reverts commit 5d4b80d670, reversing
changes made to a3b13dd47b.
2026-09-30 04:26:17 +08:00
layue13 5d4b80d670 Merge pull request 'Guard all backend transfers and recover failed handoffs' (#17) from feature/transfer-guard-lifecycle into main 2026-09-29 19:53:07 +00:00
layue13 de825615c2 Add central guarded transfer lifecycle 2026-09-30 03:41:11 +08:00
layue13 a3b13dd47b Merge pull request 'Bind backend players to exact proxy sessions' (#15) from codex/backend-session-binding into main v0.1.0-dev.10102.1.a3b13dd47be2 2026-09-27 19:19:57 +00:00
layue13 19766f16f6 Merge pull request 'Permissions API and native LuckPerms platform' (#14) from feature/permissions-luckperms into main 2026-09-27 18:54:57 +00:00
layue13 80a753abc7 Merge branch 'feature/permissions-luckperms' of https://git.nest.potatolab.uk:8443/layue13/MoonBridge into codex/backend-session-binding 2026-09-28 02:45:48 +08:00
layue13 3278ce74b4 Consume private session proofs before legacy player broadcasts 2026-09-28 02:45:04 +08:00
layue13 fc5f8d4753 Verify native LuckPerms integrations and render translated command output 2026-09-28 02:27:15 +08:00
layue13 63f3f97038 Forward exact session proofs for configured offline backends 2026-09-28 02:09:54 +08:00
layue13 672914cdbe Bind Bukkit players to signed exact proxy sessions 2026-09-28 01:50:28 +08:00
layue13 7f1980269a Add permissions service and native LuckPerms platform 2026-09-28 01:32:31 +08:00
layue13 0344f0bc65 Merge pull request '使用 uk.potatolab.moonbridge Maven 命名空间' (#13) from fix/maven-group into main v0.1.0-dev.10084.1.0344f0bc65da 2026-09-27 16:11:58 +00:00
layue13 cea296eeb0 Publish MoonBridge modules under project-specific Maven group 2026-09-28 00:11:41 +08:00
layue13 76c34d9e89 Merge pull request '完善 MoonBridge 开发版发布与接入文档' (#12) from polish/publish-readiness into main 2026-09-27 15:50:53 +00:00
layue13 8401e34639 Make development publishing traceable and polish operator and plugin docs 2026-09-27 23:43:13 +08:00
layue13 7e919a7775 Merge pull request 'Make initial player routing explicit and bound safe connection retries' (#11) from feat/initial-routing into main 2026-09-27 15:20:59 +00:00
layue13 c0c8f05110 Decouple directory update regression from snapshot implementation 2026-09-27 23:13:14 +08:00
layue13 14d9b59b05 Assert candidate exhaustion disconnect reason in login regression 2026-09-27 23:10:20 +08:00
layue13 9481403684 Redesign initial player routing with explicit candidates and bounded retries 2026-09-27 23:05:58 +08:00