fix: enforce gb32960 kafka ack boundary
This commit is contained in:
@@ -2,6 +2,7 @@ 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.codec.BccChecksum;
|
||||
import com.lingniu.ingest.core.concurrency.AsyncBatchExecutor;
|
||||
import com.lingniu.ingest.core.concurrency.DisruptorEventBus;
|
||||
import com.lingniu.ingest.core.dispatcher.Dispatcher;
|
||||
@@ -21,6 +22,9 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@@ -36,6 +40,7 @@ class Gb32960ChannelHandlerAckBoundaryTest {
|
||||
assertThat(channel.writeInbound(Gb32960DecoderTest.buildRealtimeFrame("LTEST000000000001"))).isFalse();
|
||||
assertThat((Object) channel.readOutbound()).isNull();
|
||||
|
||||
dispatch.awaitPublishCount(1);
|
||||
dispatch.completeDurability();
|
||||
channel.runPendingTasks();
|
||||
|
||||
@@ -55,6 +60,7 @@ class Gb32960ChannelHandlerAckBoundaryTest {
|
||||
|
||||
assertThat(channel.writeInbound(Gb32960DecoderTest.buildRealtimeFrame("LTEST000000000001"))).isFalse();
|
||||
|
||||
dispatch.awaitPublishCount(1);
|
||||
dispatch.failDurability();
|
||||
channel.runPendingTasks();
|
||||
|
||||
@@ -64,6 +70,35 @@ class Gb32960ChannelHandlerAckBoundaryTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void durableAcksAreWrittenInInboundOrderWhenLaterDispatchCompletesFirst() {
|
||||
try (DispatchHarness dispatch = new DispatchHarness()) {
|
||||
EmbeddedChannel channel = new EmbeddedChannel(handlerWith(dispatch.dispatcher()));
|
||||
|
||||
assertThat(channel.writeInbound(Gb32960DecoderTest.buildRealtimeFrame("LTEST000000000001"))).isFalse();
|
||||
assertThat(channel.writeInbound(buildHeartbeatFrame("LTEST000000000001"))).isFalse();
|
||||
assertThat((Object) channel.readOutbound()).isNull();
|
||||
|
||||
dispatch.awaitPublishCount(2);
|
||||
dispatch.completeDurability(1);
|
||||
channel.runPendingTasks();
|
||||
assertThat((Object) channel.readOutbound()).isNull();
|
||||
|
||||
dispatch.completeDurability(0);
|
||||
channel.runPendingTasks();
|
||||
|
||||
ByteBuf firstAck = channel.readOutbound();
|
||||
ByteBuf secondAck = channel.readOutbound();
|
||||
assertThat(firstAck).isNotNull();
|
||||
assertThat(secondAck).isNotNull();
|
||||
assertThat(firstAck.getUnsignedByte(2)).isEqualTo((short) 0x02);
|
||||
assertThat(secondAck.getUnsignedByte(2)).isEqualTo((short) 0x07);
|
||||
firstAck.release();
|
||||
secondAck.release();
|
||||
channel.finishAndReleaseAll();
|
||||
}
|
||||
}
|
||||
|
||||
private static Gb32960ChannelHandler handlerWith(Dispatcher dispatcher) {
|
||||
return new Gb32960ChannelHandler(
|
||||
decoder(),
|
||||
@@ -87,6 +122,23 @@ class Gb32960ChannelHandlerAckBoundaryTest {
|
||||
new Gb32960PlatformAuthorizer(auth.getPlatforms()));
|
||||
}
|
||||
|
||||
private static byte[] buildHeartbeatFrame(String vin) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(0x23);
|
||||
out.write(0x23);
|
||||
out.write(0x07);
|
||||
out.write(0xFE);
|
||||
byte[] vinBytes = vin.getBytes(StandardCharsets.US_ASCII);
|
||||
out.write(vinBytes, 0, 17);
|
||||
out.write(0x01);
|
||||
out.write(0x00);
|
||||
out.write(0x00);
|
||||
|
||||
byte[] almost = out.toByteArray();
|
||||
out.write(BccChecksum.compute(almost, 2, almost.length - 2) & 0xFF);
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private static final class DispatchHarness implements AutoCloseable {
|
||||
private final ControlledSink sink = new ControlledSink();
|
||||
private final DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink));
|
||||
@@ -103,11 +155,28 @@ class Gb32960ChannelHandlerAckBoundaryTest {
|
||||
}
|
||||
|
||||
void completeDurability() {
|
||||
sink.future.complete(null);
|
||||
completeDurability(0);
|
||||
}
|
||||
|
||||
void completeDurability(int index) {
|
||||
sink.future(index).complete(null);
|
||||
}
|
||||
|
||||
void failDurability() {
|
||||
sink.future.completeExceptionally(new IllegalStateException("durability failed"));
|
||||
sink.future(0).completeExceptionally(new IllegalStateException("durability failed"));
|
||||
}
|
||||
|
||||
void awaitPublishCount(int count) {
|
||||
long deadline = System.currentTimeMillis() + 3000;
|
||||
while (System.currentTimeMillis() < deadline) {
|
||||
synchronized (sink.futures) {
|
||||
if (sink.futures.size() >= count) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
throw new AssertionError("expected " + count + " sink publishes, actual=" + sink.futures.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -118,16 +187,26 @@ class Gb32960ChannelHandlerAckBoundaryTest {
|
||||
}
|
||||
|
||||
private static final class ControlledSink implements EventSink {
|
||||
private final CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
private final List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "controlled";
|
||||
return "kafka";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> publish(VehicleEvent event) {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
synchronized (futures) {
|
||||
futures.add(future);
|
||||
}
|
||||
return future;
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> future(int index) {
|
||||
synchronized (futures) {
|
||||
return futures.get(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user