mirror of
https://github.com/layue13/MoonBridge.git
synced 2026-10-25 18:46:18 +00:00
Close handed-off backend candidate on pipeline error
This commit is contained in:
1 parent
d90ebf903e
commit
86a3fad5b8
4 files changed
+57
-1
No files matched your search
@@ -93,6 +93,8 @@ plugins:
|
||||
|
||||
当前提交的多轮、同条件本机测量及原始输出见 [生命周期修复后的会话基准记录](benchmarks/results/2026-09-25-session-after-lifecycle.md)。之前提交的测量见 [原会话基准记录](benchmarks/results/2026-09-25-current-session-benchmark.md)。这些是代理自身的合成流量性能验证,不是后端负载观测。
|
||||
|
||||
当前会话路径的 [JFR 采样记录](benchmarks/results/2026-09-25-session-jfr-screening.md) 只提供了分配线索,代理 I/O 线程的 CPU 执行样本不足以定位热点;因此没有据此改动普通转发路径。
|
||||
|
||||
将 `-Window` 改为 `16` 可测每连接最多 16 个在途往返的情形。
|
||||
|
||||
原 stop-and-wait 小样本见 `benchmarks/results/proxy-session-benchmark-smoke-2026-09-25.md`;Window 参数的小样本和 Window=1/16 重复测量见 `benchmarks/results/2026-09-25-proxy-session-window.md`。这些是环回网络上的合成帧对照,不能代表 Forge 整合包、真实后端或跨主机部署的性能。
|
||||
@@ -0,0 +1,16 @@
|
||||
# JFR screening of the session hot path
|
||||
|
||||
Run date: 2026-09-25 (Asia/Shanghai). Profiled revision: `d90ebf9` on Windows 11 Enterprise, AMD Ryzen 7 7800X3D, Zulu OpenJDK 25.0.4.1. The recording ran for 44 seconds with Java Flight Recorder's `profile` settings. The local JFR file is under ignored `benchmarks/build/`; it is not published because JFR includes process environment events.
|
||||
|
||||
Command, after compiling `benchmarks/ProxySessionBenchmark.java` and installing the distribution:
|
||||
|
||||
```powershell
|
||||
java -XX:StartFlightRecording=filename=benchmarks/build/session-w1.jfr,settings=profile,dumponexit=true `
|
||||
-cp "benchmarks/build;proxy-core/build/install/strataproxy/lib/*" `
|
||||
dev.strataproxy.core.session.ProxySessionBenchmark `
|
||||
--connections 4 --messages 40000 --warmup 4000 --payload 4096 --repeats 5 --window 1
|
||||
```
|
||||
|
||||
All direct and proxy echoes were correct with zero errors. The full recording contained 11,845 `jdk.ObjectAllocationSample` events and 167 `jdk.ExecutionSample` events. In the full recording, the benchmark client's `readFrame` byte-array allocation accounted for 82.97% of weighted allocation pressure. After filtering to `strataproxy-session-io-*` threads, 2,172 allocation samples remained, but no execution samples. Weighted I/O-thread allocation pressure was led by `DirectByteBuffer.duplicate` (34.01%) and `DirectByteBuffer.slice` (25.68%); `Integer.valueOf` (6.90%) sampled under the Windows selector's `WEPollSelectorImpl`.
|
||||
|
||||
These are sampled allocation-pressure shares, not exact allocated bytes or a per-packet cost. The recording combines direct and proxy phases, and JFR did not capture enough proxy I/O execution samples to locate a CPU bottleneck. It therefore does not justify changing the ordinary relay path. The earlier [unprofiled paired run](2026-09-25-session-after-lifecycle.md) remains the throughput reference for this revision family. Profile again with representative Forge traffic and a method that yields usable CPU samples before attributing a production bottleneck.
|
||||
@@ -113,7 +113,8 @@ final class TransferCandidate extends ChannelInboundHandlerAdapter {
|
||||
}
|
||||
|
||||
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
fail(ctx, "replacement backend failed");
|
||||
if (state == State.HANDED_OFF) ctx.close();
|
||||
else fail(ctx, "replacement backend failed");
|
||||
}
|
||||
|
||||
Minecraft1710PlayPackets.JoinGame joinGame() { return joinGame; }
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@@ -17,6 +18,42 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
final class TransferCandidateTest {
|
||||
@Test
|
||||
void errorAfterHandoffClosesTheCandidateChannel() {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
var ready = new AtomicBoolean();
|
||||
var candidate = new TransferCandidate(playerId, "Player", new TransferCandidate.Listener() {
|
||||
@Override public void ready(TransferCandidate ignored) { ready.set(true); }
|
||||
@Override public void failed(TransferCandidate ignored, String reason) {
|
||||
throw new AssertionError("candidate has already been handed off");
|
||||
}
|
||||
});
|
||||
var channel = new EmbeddedChannel(candidate);
|
||||
try {
|
||||
ByteBuf login = Unpooled.buffer();
|
||||
try {
|
||||
ProtocolVarInt.write(login, 2);
|
||||
writeString(login, playerId.toString());
|
||||
writeString(login, "Player");
|
||||
channel.writeInbound(Minecraft1710PlayPackets.frame(channel.alloc(), login));
|
||||
} finally { login.release(); }
|
||||
ByteBuf join = Unpooled.buffer();
|
||||
try {
|
||||
ProtocolVarInt.write(join, Minecraft1710PlayPackets.JOIN_GAME);
|
||||
join.writeInt(200).writeByte(0).writeByte(0).writeByte(1).writeByte(20);
|
||||
writeString(join, "default");
|
||||
channel.writeInbound(Minecraft1710PlayPackets.frame(channel.alloc(), join));
|
||||
} finally { join.release(); }
|
||||
assertTrue(ready.get());
|
||||
candidate.handOff();
|
||||
channel.pipeline().fireExceptionCaught(new IllegalStateException("backend read failed"));
|
||||
assertFalse(channel.isOpen());
|
||||
} finally {
|
||||
candidate.close();
|
||||
channel.finishAndReleaseAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void boundsTinyQueuedPacketsAndReleasesTheirBuffersOnFailure() {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
|
||||
Reference in new issue
Block a user