feat: ack gb32960 after kafka boundary

This commit is contained in:
lingniu
2026-06-23 17:10:46 +08:00
parent 6b82144f3c
commit 633b3ea9c9
4 changed files with 322 additions and 47 deletions

View File

@@ -0,0 +1,133 @@
package com.lingniu.ingest.protocol.gb32960.inbound;
import com.lingniu.ingest.api.event.VehicleEvent;
import com.lingniu.ingest.api.sink.EventSink;
import com.lingniu.ingest.core.concurrency.AsyncBatchExecutor;
import com.lingniu.ingest.core.concurrency.DisruptorEventBus;
import com.lingniu.ingest.core.dispatcher.Dispatcher;
import com.lingniu.ingest.core.dispatcher.HandlerInvoker;
import com.lingniu.ingest.core.dispatcher.HandlerRegistry;
import com.lingniu.ingest.core.pipeline.InterceptorChain;
import com.lingniu.ingest.protocol.gb32960.auth.Gb32960PlatformAuthorizer;
import com.lingniu.ingest.protocol.gb32960.auth.Gb32960VinAuthorizer;
import com.lingniu.ingest.protocol.gb32960.codec.Gb32960BodyParser;
import com.lingniu.ingest.protocol.gb32960.codec.Gb32960DecoderTest;
import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder;
import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParserRegistry;
import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.PositionV2016BlockParser;
import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VehicleV2016BlockParser;
import com.lingniu.ingest.protocol.gb32960.config.Gb32960Properties;
import io.netty.buffer.ByteBuf;
import io.netty.channel.embedded.EmbeddedChannel;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import static org.assertj.core.api.Assertions.assertThat;
class Gb32960ChannelHandlerAckBoundaryTest {
@Test
void realtimeReportAckWaitsForDispatchFutureCompletion() {
try (DispatchHarness dispatch = new DispatchHarness()) {
EmbeddedChannel channel = new EmbeddedChannel(handlerWith(dispatch.dispatcher()));
assertThat(channel.writeInbound(Gb32960DecoderTest.buildRealtimeFrame("LTEST000000000001"))).isFalse();
assertThat((Object) channel.readOutbound()).isNull();
dispatch.completeDurability();
channel.runPendingTasks();
ByteBuf ack = channel.readOutbound();
assertThat(ack).isNotNull();
assertThat(ack.getUnsignedByte(2)).isEqualTo((short) 0x02);
assertThat(ack.getUnsignedByte(3)).isEqualTo((short) 0x01);
ack.release();
channel.finishAndReleaseAll();
}
}
@Test
void realtimeReportAckIsNotWrittenWhenDispatchFutureFails() {
try (DispatchHarness dispatch = new DispatchHarness()) {
EmbeddedChannel channel = new EmbeddedChannel(handlerWith(dispatch.dispatcher()));
assertThat(channel.writeInbound(Gb32960DecoderTest.buildRealtimeFrame("LTEST000000000001"))).isFalse();
dispatch.failDurability();
channel.runPendingTasks();
assertThat((Object) channel.readOutbound()).isNull();
assertThat(channel.isActive()).isFalse();
channel.finishAndReleaseAll();
}
}
private static Gb32960ChannelHandler handlerWith(Dispatcher dispatcher) {
return new Gb32960ChannelHandler(
decoder(),
dispatcher,
accessService(),
new Gb32960AckService(),
new Gb32960FrameDiagnostics(16));
}
private static Gb32960MessageDecoder decoder() {
return new Gb32960MessageDecoder(new Gb32960BodyParser(new InfoBlockParserRegistry(List.of(
new VehicleV2016BlockParser(),
new PositionV2016BlockParser()))));
}
private static Gb32960AccessService accessService() {
Gb32960Properties.Auth auth = new Gb32960Properties.Auth();
auth.setEnabled(false);
return new Gb32960AccessService(
new Gb32960VinAuthorizer(auth),
new Gb32960PlatformAuthorizer(auth.getPlatforms()));
}
private static final class DispatchHarness implements AutoCloseable {
private final ControlledSink sink = new ControlledSink();
private final DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink));
private final AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish);
private final Dispatcher dispatcher = new Dispatcher(
new HandlerRegistry(),
new InterceptorChain(List.of()),
new HandlerInvoker(),
eventBus,
batchExecutor);
Dispatcher dispatcher() {
return dispatcher;
}
void completeDurability() {
sink.future.complete(null);
}
void failDurability() {
sink.future.completeExceptionally(new IllegalStateException("durability failed"));
}
@Override
public void close() {
batchExecutor.close();
eventBus.close();
}
}
private static final class ControlledSink implements EventSink {
private final CompletableFuture<Void> future = new CompletableFuture<>();
@Override
public String name() {
return "controlled";
}
@Override
public CompletableFuture<Void> publish(VehicleEvent event) {
return future;
}
}
}