diff --git a/dev-support/checkstyle.xml b/dev-support/checkstyle.xml
index db4954fb49..16dd31ad77 100644
--- a/dev-support/checkstyle.xml
+++ b/dev-support/checkstyle.xml
@@ -60,6 +60,10 @@
+
+
+
+
diff --git a/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientRequest.java b/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientRequest.java
index b04402fe15..ca7b6cc88a 100644
--- a/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientRequest.java
+++ b/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientRequest.java
@@ -46,11 +46,11 @@ public class RaftClientRequest extends RaftClientMessage {
private static final Type WATCH_DEFAULT = new Type(
WatchRequestTypeProto.newBuilder().setIndex(0L).setReplication(ReplicationLevel.MAJORITY).build());
- private static final Type READ_AFTER_WRITE_CONSISTENT_DEFAULT
- = new Type(ReadRequestTypeProto.newBuilder().setReadAfterWriteConsistent(true).build());
- private static final Type READ_DEFAULT = new Type(ReadRequestTypeProto.getDefaultInstance());
- private static final Type READ_NONLINEARIZABLE_DEFAULT
- = new Type(ReadRequestTypeProto.newBuilder().setPreferNonLinearizable(true).build());
+ private static final ReadTypes READ_TYPES = new ReadTypes();
+ private static final Type READ_DEFAULT = readRequestType(false, false, false);
+ private static final Type READ_NONLINEARIZABLE_DEFAULT = readRequestType(true, false, false);
+ private static final Type READ_AFTER_WRITE_CONSISTENT_DEFAULT = readRequestType(false, true, false);
+
private static final Type STALE_READ_DEFAULT = new Type(StaleReadRequestTypeProto.getDefaultInstance());
private static final Map WRITE_REQUEST_TYPES;
@@ -71,6 +71,44 @@ public static Type writeRequestType(ReplicationLevel replication) {
return WRITE_REQUEST_TYPES.get(replication);
}
+ private static final class ReadTypes {
+ private final Type[] array = new Type[8];
+
+ private ReadTypes() {
+ for (int i = 0; i < array.length; i++) {
+ array[i] = new Type(ReadRequestTypeProto.newBuilder()
+ .setPreferNonLinearizable((i & 1) != 0)
+ .setReadAfterWriteConsistent((i & 2) != 0)
+ .setDummy((i & 4) != 0)
+ .build());
+ }
+
+ assertArray();
+ }
+
+ private Type getImpl(boolean nonLinearizable, boolean readAfterWriteConsistent, boolean dummy) {
+ final int i = (nonLinearizable ? 1 : 0)
+ | (readAfterWriteConsistent ? 2 : 0)
+ | (dummy ? 4 : 0);
+ return array[i];
+ }
+
+ Type get(boolean nonLinearizable, boolean readAfterWriteConsistent, boolean dummy) {
+ if (nonLinearizable && readAfterWriteConsistent) {
+ throw new IllegalArgumentException("Cannot be both nonLinearizable and readAfterWriteConsistent");
+ }
+ return getImpl(nonLinearizable, readAfterWriteConsistent, dummy);
+ }
+
+ private void assertArray() {
+ for (final Type type : array) {
+ final ReadRequestTypeProto read = type.getRead();
+ final Type got = getImpl(read.getPreferNonLinearizable(), read.getReadAfterWriteConsistent(), read.getDummy());
+ Preconditions.assertSame(type, got, "type");
+ }
+ }
+ }
+
public static Type writeRequestType() {
return writeRequestType(ReplicationLevel.MAJORITY);
}
@@ -91,6 +129,10 @@ public static Type messageStreamRequestType(long streamId, long messageId, boole
.build());
}
+ public static Type readRequestType(boolean nonLinearizable, boolean readAfterWriteConsistent, boolean dummy) {
+ return READ_TYPES.get(nonLinearizable, readAfterWriteConsistent, dummy);
+ }
+
public static Type readAfterWriteConsistentRequestType() {
return READ_AFTER_WRITE_CONSISTENT_DEFAULT;
}
@@ -312,6 +354,22 @@ public RaftClientRequest build() {
return new RaftClientRequest(this);
}
+ public Builder set(RaftClientRequest request) {
+ this.clientId = request.getClientId();
+ this.serverId = request.getServerId();
+ this.groupId = request.getRaftGroupId();
+ this.callId = request.getCallId();
+ this.toLeader = request.isToLeader();
+ this.repliedCallIds = request.getRepliedCallIds();
+ this.message = request.getMessage();
+ this.type = request.getType();
+ this.slidingWindowEntry = request.getSlidingWindowEntry();
+ this.routingTable = request.getRoutingTable();
+ this.timeoutMs = request.getTimeoutMs();
+ this.spanContext = request.getSpanContext();
+ return this;
+ }
+
public Builder setClientId(ClientId clientId) {
this.clientId = clientId;
return this;
diff --git a/ratis-netty/src/main/java/org/apache/ratis/netty/server/ReadStreamManagement.java b/ratis-netty/src/main/java/org/apache/ratis/netty/server/ReadStreamManagement.java
index 5336760a0b..46d169bbfa 100644
--- a/ratis-netty/src/main/java/org/apache/ratis/netty/server/ReadStreamManagement.java
+++ b/ratis-netty/src/main/java/org/apache/ratis/netty/server/ReadStreamManagement.java
@@ -17,9 +17,11 @@
*/
package org.apache.ratis.netty.server;
+import org.apache.ratis.client.impl.OrderedAsync;
import org.apache.ratis.conf.RaftProperties;
import org.apache.ratis.datastream.impl.DataStreamReplyByteBuffer;
import org.apache.ratis.datastream.impl.DataStreamRequestByteBuf;
+import org.apache.ratis.proto.RaftProtos;
import org.apache.ratis.proto.RaftProtos.DataStreamPacketHeaderProto.Type;
import org.apache.ratis.proto.RaftProtos.RaftClientRequestProto;
import org.apache.ratis.proto.RaftProtos.RaftClientRequestProto.TypeCase;
@@ -47,6 +49,7 @@
import static org.apache.ratis.client.impl.ClientProtoUtils.toRaftClientRequest;
import static org.apache.ratis.client.impl.ClientProtoUtils.toRaftClientReplyProto;
+import static org.apache.ratis.netty.server.DataStreamManagement.newDataStreamReplyByteBuffer;
import static org.apache.ratis.netty.server.DataStreamManagement.replyDataStreamException;
public class ReadStreamManagement {
@@ -60,23 +63,24 @@ static class ReadStream implements WritableByteChannel {
private final DataStreamReplyByteBuffer terminalReply;
private long streamOffset;
- ReadStream(RaftClientRequest request, long streamId, ChannelHandlerContext ctx) {
+ ReadStream(RaftClientRequest request, long streamId, ChannelHandlerContext ctx, RaftClientReply terminalReply) {
this.clientId = request.getClientId();
this.streamId = streamId;
this.ctx = ctx;
- final RaftClientReply reply = RaftClientReply.newBuilder()
- .setRequest(request)
- .setSuccess()
- .build();
- this.terminalReply = DataStreamReplyByteBuffer.newBuilder()
+ this.terminalReply = newReadStreamTerminalReply(clientId, streamId, terminalReply);
+ }
+
+ private static DataStreamReplyByteBuffer newReadStreamTerminalReply(
+ ClientId clientId, long streamId, RaftClientReply reply) {
+ return DataStreamReplyByteBuffer.newBuilder()
.setClientId(clientId)
.setType(Type.STREAM_HEADER)
.setStreamId(streamId)
.setStreamOffset(0)
.setBuffer(toRaftClientReplyProto(reply).toByteString().asReadOnlyByteBuffer())
- .setSuccess(true)
- .setBytesWritten(0)
+ .setSuccess(reply.isSuccess())
+ .setCommitInfos(reply.getCommitInfos())
.build();
}
@@ -186,17 +190,45 @@ private boolean processImpl(DataStreamRequestByteBuf requestBuf, ChannelHandlerC
return true;
}
- final ReadStream stream = new ReadStream(request, requestBuf.getStreamId(), ctx);
- requestExecutor.execute(() -> {
+ final CompletableFuture readCheck;
+ try {
+ readCheck = server.submitClientRequestAsync(newDummyReadRequest(request));
+ } catch (IOException e) {
+ replyDataStreamException(server, e, request, requestBuf, ctx);
+ return true;
+ }
+
+ readCheck.whenCompleteAsync((readCheckReply, exception) -> {
+ if (exception != null) {
+ replyDataStreamException(server, exception, request, requestBuf, ctx);
+ return;
+ }
+
+ if (!readCheckReply.isSuccess()) {
+ ctx.writeAndFlush(newDataStreamReplyByteBuffer(requestBuf, readCheckReply));
+ return;
+ }
+
+ final ReadStream stream = new ReadStream(request, requestBuf.getStreamId(), ctx, readCheckReply);
try {
division.getStateMachine().data().query(request.getMessage(), stream);
} catch (Throwable t) {
LOG.error("{}: Failed read-only data stream query for {}", this, request, t);
}
- });
+ }, requestExecutor);
return true;
}
+ private static RaftClientRequest newDummyReadRequest(RaftClientRequest request) {
+ final RaftProtos.ReadRequestTypeProto original = request.getType().getRead();
+ return RaftClientRequest.newBuilder()
+ .set(request)
+ .setMessage(OrderedAsync.DUMMY)
+ .setType(RaftClientRequest.readRequestType(
+ original.getPreferNonLinearizable(), original.getReadAfterWriteConsistent(), true))
+ .build();
+ }
+
@Override
public String toString() {
return name;
diff --git a/ratis-proto/src/main/proto/Raft.proto b/ratis-proto/src/main/proto/Raft.proto
index eba5de3b74..c1bb9d64ee 100644
--- a/ratis-proto/src/main/proto/Raft.proto
+++ b/ratis-proto/src/main/proto/Raft.proto
@@ -302,6 +302,7 @@ message ForwardRequestTypeProto {
message ReadRequestTypeProto {
bool preferNonLinearizable = 1;
bool readAfterWriteConsistent = 2;
+ bool dummy = 3;
}
message StaleReadRequestTypeProto {
diff --git a/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java b/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java
index f758fd0edc..f4e740407a 100644
--- a/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java
+++ b/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java
@@ -43,6 +43,7 @@
import org.apache.ratis.proto.RaftProtos.RoleInfoProto;
import org.apache.ratis.proto.RaftProtos.StartLeaderElectionReplyProto;
import org.apache.ratis.proto.RaftProtos.StartLeaderElectionRequestProto;
+import org.apache.ratis.protocol.ClientId;
import org.apache.ratis.protocol.ClientInvocationId;
import org.apache.ratis.protocol.GroupInfoReply;
import org.apache.ratis.protocol.GroupInfoRequest;
@@ -260,6 +261,8 @@ public long[] getFollowerMatchIndices() {
private final ExecutorService clientExecutor;
private final ThreadGroup threadGroup;
+ private final CompletableFuture dummySuccessReply;
+
RaftServerImpl(RaftGroup group, StateMachine stateMachine, RaftServerProxy proxy, RaftStorage.StartupOption option)
throws IOException {
final RaftPeerId id = proxy.getId();
@@ -303,6 +306,13 @@ public long[] getFollowerMatchIndices() {
RaftServerConfigKeys.ThreadPool.clientSize(properties),
id + "-client");
this.threadGroup = new ThreadGroup(proxy.getThreadGroup(), getMemberId().toString());
+
+ this.dummySuccessReply = CompletableFuture.completedFuture(RaftClientReply.newBuilder()
+ .setClientId(ClientId.emptyClientId())
+ .setServerId(id)
+ .setGroupId(group.getGroupId())
+ .setSuccess()
+ .build());
}
private long getCommitIndex(RaftPeerId id) {
@@ -1113,11 +1123,12 @@ private CompletableFuture readAsync(RaftClientRequest request)
if (request.getType().getRead().getPreferNonLinearizable()
|| readOption == RaftServerConfigKeys.Read.Option.DEFAULT) {
final CompletableFuture reply = checkLeaderState(request);
- if (reply != null) {
- return reply;
- }
- return queryStateMachine(request);
- } else if (readOption == RaftServerConfigKeys.Read.Option.LINEARIZABLE){
+ if (reply != null) {
+ return reply;
+ }
+ return isDummyRead(request) ? CompletableFuture.completedFuture(newSuccessReply(request))
+ : queryStateMachine(request);
+ } else if (readOption == RaftServerConfigKeys.Read.Option.LINEARIZABLE) {
final LeaderStateImpl leader = role.getLeaderState().orElse(null);
final CompletableFuture replyFuture;
if (leader != null) {
@@ -1136,12 +1147,17 @@ private CompletableFuture readAsync(RaftClientRequest request)
return replyFuture
.thenCompose(readIndex -> getState().getReadRequests().waitToAdvance(readIndex,
() -> getReadException("add", snapshotInstallationHandler.getInProgressInstallSnapshotIndex(), false)))
- .thenCompose(readIndex -> queryStateMachine(request))
+ .thenCompose(readIndex -> isDummyRead(request)
+ ? CompletableFuture.completedFuture(newSuccessReply(request)) : queryStateMachine(request))
.exceptionally(e -> readException2Reply(request, e));
} else {
throw new IllegalStateException("Unexpected read option: " + readOption);
}
}
+ private static boolean isDummyRead(RaftClientRequest request) {
+ return request.getMessage() != null && OrderedAsync.DUMMY.getContent().equals(request.getMessage().getContent());
+ }
+
private RaftClientReply readException2Reply(RaftClientRequest request, Throwable e) {
e = JavaUtils.unwrapCompletionException(e);
if (e instanceof StateMachineException ) {
@@ -1183,6 +1199,9 @@ private CompletableFuture streamEndOfRequestAsync(RaftClientR
}
CompletableFuture queryStateMachine(RaftClientRequest request) {
+ if (request.getType().getRead().getDummy()) {
+ return dummySuccessReply;
+ }
return processQueryFuture(stateMachine.query(request.getMessage()), request);
}
diff --git a/ratis-test/src/test/java/org/apache/ratis/client/impl/TestDataStreamClientImpl.java b/ratis-test/src/test/java/org/apache/ratis/client/impl/TestDataStreamClientImpl.java
index 523c49f053..a85a9f4830 100644
--- a/ratis-test/src/test/java/org/apache/ratis/client/impl/TestDataStreamClientImpl.java
+++ b/ratis-test/src/test/java/org/apache/ratis/client/impl/TestDataStreamClientImpl.java
@@ -22,9 +22,7 @@
import org.apache.ratis.client.api.DataStreamInput;
import org.apache.ratis.conf.RaftProperties;
import org.apache.ratis.datastream.DataStreamObserver;
-import org.apache.ratis.datastream.impl.DataStreamReplyByteBuf;
import org.apache.ratis.datastream.impl.DataStreamRequestByteBuffer;
-import org.apache.ratis.proto.RaftProtos.DataStreamPacketHeaderProto.Type;
import org.apache.ratis.proto.RaftProtos.RaftClientRequestProto;
import org.apache.ratis.protocol.ClientId;
import org.apache.ratis.protocol.DataStreamReply;
@@ -32,7 +30,6 @@
import org.apache.ratis.protocol.RaftClientRequest;
import org.apache.ratis.protocol.RaftGroupId;
import org.apache.ratis.protocol.RaftPeer;
-import org.apache.ratis.thirdparty.io.netty.buffer.Unpooled;
import org.apache.ratis.util.ReferenceCountedObject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -69,20 +66,6 @@ public CompletableFuture streamAsync(
return future;
}
- RaftClientRequest getRequest() {
- return request.get();
- }
-
- void receive(DataStreamReplyByteBuf reply) {
- final ReferenceCountedObject ref = DataStreamReplyByteBuf.asReferenceCounted(reply);
- ref.retain();
- try {
- replyHandler.get().onNext(ref);
- } finally {
- ref.release();
- }
- }
-
void complete() {
replyHandler.get().onCompleted();
replyFuture.get().complete(null);
diff --git a/ratis-test/src/test/java/org/apache/ratis/datastream/DataStreamClusterTests.java b/ratis-test/src/test/java/org/apache/ratis/datastream/DataStreamClusterTests.java
index 95e9bef49c..7478c36287 100644
--- a/ratis-test/src/test/java/org/apache/ratis/datastream/DataStreamClusterTests.java
+++ b/ratis-test/src/test/java/org/apache/ratis/datastream/DataStreamClusterTests.java
@@ -32,9 +32,6 @@
import org.apache.ratis.proto.RaftProtos.DataStreamPacketHeaderProto.Type;
import org.apache.ratis.proto.RaftProtos.ReplicationLevel;
import org.apache.ratis.protocol.DataStreamReply;
-import org.apache.ratis.protocol.DataStreamRequest;
-import org.apache.ratis.protocol.DataStreamRequestHeader;
-import org.apache.ratis.protocol.Message;
import org.apache.ratis.protocol.RaftClientReply;
import org.apache.ratis.protocol.RaftClientRequest;
import org.apache.ratis.retry.RetryPolicies;
diff --git a/ratis-test/src/test/java/org/apache/ratis/netty/server/TestDataStreamManagement.java b/ratis-test/src/test/java/org/apache/ratis/netty/server/TestDataStreamManagement.java
index 188f119fca..56ecd166bc 100644
--- a/ratis-test/src/test/java/org/apache/ratis/netty/server/TestDataStreamManagement.java
+++ b/ratis-test/src/test/java/org/apache/ratis/netty/server/TestDataStreamManagement.java
@@ -19,6 +19,7 @@
import org.apache.ratis.client.impl.ClientProtoUtils;
import org.apache.ratis.client.impl.DataStreamClientImpl.DataStreamOutputImpl;
+import org.apache.ratis.client.impl.OrderedAsync;
import org.apache.ratis.conf.RaftProperties;
import org.apache.ratis.datastream.impl.DataStreamReplyByteBuffer;
import org.apache.ratis.datastream.impl.DataStreamRequestByteBuf;
@@ -28,10 +29,12 @@
import org.apache.ratis.protocol.ClientId;
import org.apache.ratis.protocol.DataStreamReply;
import org.apache.ratis.protocol.Message;
+import org.apache.ratis.protocol.RaftClientReply;
import org.apache.ratis.protocol.RaftClientRequest;
import org.apache.ratis.protocol.RaftGroupId;
import org.apache.ratis.protocol.RaftPeer;
import org.apache.ratis.protocol.RaftPeerId;
+import org.apache.ratis.protocol.exceptions.ReadIndexException;
import org.apache.ratis.server.RaftServer;
import org.apache.ratis.statemachine.StateMachine;
import org.apache.ratis.statemachine.StateMachine.DataApi;
@@ -59,14 +62,17 @@
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
class TestDataStreamManagement {
@@ -96,8 +102,9 @@ public void query(Message request, WritableByteChannel stream) {
assertTrue(management.process(readOnlyRequest.request, embeddedChannel.pipeline().firstContext()));
assertEquals(0, readOnlyRequest.headerBuf.refCnt());
+ JavaUtils.attempt(() -> assertNotNull(streamRef.get()), 10,
+ TimeDuration.valueOf(100, TimeUnit.MILLISECONDS), "read-only stream", null);
final WritableByteChannel stream = streamRef.get();
- assertNotNull(stream);
stream.write(response.asReadOnlyByteBuffer());
stream.close();
@@ -116,6 +123,99 @@ public void query(Message request, WritableByteChannel stream) {
assertTrue(ClientProtoUtils.getRaftClientReply(replies.get(1)).isSuccess());
} finally {
embeddedChannel.finishAndReleaseAll();
+ management.shutdown();
+ }
+ }
+
+ @Test
+ void readOnlyRequestWaitsForLinearizableCheck() throws Exception {
+ final RaftPeerId serverId = RaftPeerId.valueOf("s1");
+ final ClientId clientId = ClientId.randomId();
+ final RaftGroupId groupId = RaftGroupId.randomId();
+ final ByteString query = ByteString.copyFromUtf8("query");
+ final CompletableFuture readOnlyCheck = new CompletableFuture<>();
+ final AtomicReference submittedReadOnlyCheck = new AtomicReference<>();
+ final AtomicReference messageRef = new AtomicReference<>();
+ final AtomicReference streamRef = new AtomicReference<>();
+
+ final DataApi dataApi = new DataApi() {
+ @Override
+ public void query(Message request, WritableByteChannel stream) {
+ messageRef.set(request);
+ streamRef.set(stream);
+ }
+ };
+ final ReadStreamManagement management = newReadStreamManagement(serverId, groupId, dataApi, request -> {
+ submittedReadOnlyCheck.set(request);
+ return readOnlyCheck;
+ });
+ final EmbeddedChannel embeddedChannel = new EmbeddedChannel(new ChannelInboundHandlerAdapter());
+ final ReadOnlyRequest readOnlyRequest = newReadOnlyRequest(clientId, serverId, groupId, 1L, query);
+
+ try {
+ assertTrue(management.process(readOnlyRequest.request, embeddedChannel.pipeline().firstContext()));
+ assertEquals(0, readOnlyRequest.headerBuf.refCnt());
+
+ final RaftClientRequest checkRequest = submittedReadOnlyCheck.get();
+ assertNotNull(checkRequest);
+ assertEquals(OrderedAsync.DUMMY.getContent(), checkRequest.getMessage().getContent());
+ assertNull(streamRef.get(), "state machine query should wait for the read-only check");
+
+ readOnlyCheck.complete(RaftClientReply.newBuilder().setRequest(checkRequest).setSuccess().build());
+ JavaUtils.attempt(() -> assertNotNull(streamRef.get()), 10,
+ TimeDuration.valueOf(100, TimeUnit.MILLISECONDS), "linearizable read-only stream", null);
+ assertEquals(query, messageRef.get().getContent());
+ } finally {
+ embeddedChannel.finishAndReleaseAll();
+ management.shutdown();
+ }
+ }
+
+ @Test
+ void readOnlyCheckFailureSkipsStateMachineQuery() throws Exception {
+ final RaftPeerId serverId = RaftPeerId.valueOf("s1");
+ final ClientId clientId = ClientId.randomId();
+ final RaftGroupId groupId = RaftGroupId.randomId();
+ final ByteString query = ByteString.copyFromUtf8("query");
+ final AtomicBoolean queryCalled = new AtomicBoolean();
+
+ final DataApi dataApi = new DataApi() {
+ @Override
+ public void query(Message request, WritableByteChannel stream) {
+ queryCalled.set(true);
+ }
+ };
+ final ReadStreamManagement management = newReadStreamManagement(serverId, groupId, dataApi, request ->
+ CompletableFuture.completedFuture(RaftClientReply.newBuilder()
+ .setRequest(request)
+ .setException(new ReadIndexException("read index failed"))
+ .build()));
+ final EmbeddedChannel embeddedChannel = new EmbeddedChannel(new ChannelInboundHandlerAdapter());
+ final ReadOnlyRequest readOnlyRequest = newReadOnlyRequest(clientId, serverId, groupId, 1L, query);
+
+ try {
+ assertTrue(management.process(readOnlyRequest.request, embeddedChannel.pipeline().firstContext()));
+ assertEquals(0, readOnlyRequest.headerBuf.refCnt());
+
+ final List replies = new ArrayList<>();
+ JavaUtils.attempt(() -> {
+ for (Object outbound; (outbound = embeddedChannel.readOutbound()) != null;) {
+ replies.add((DataStreamReply) outbound);
+ }
+ assertEquals(1, replies.size());
+ }, 10, TimeDuration.valueOf(100, TimeUnit.MILLISECONDS), "read-only check failure reply", null);
+
+ assertFalse(queryCalled.get(), "state machine query should not run when the read-only check fails");
+ final DataStreamReply reply = replies.get(0);
+ assertEquals(Type.STREAM_HEADER, reply.getType());
+ assertFalse(reply.isSuccess());
+ final RaftClientReply clientReply = ClientProtoUtils.getRaftClientReply(reply);
+ assertFalse(clientReply.isSuccess());
+ assertNotNull(clientReply.getReadIndexException());
+ assertEquals(serverId, clientReply.getServerId());
+ } finally {
+ embeddedChannel.finishAndReleaseAll();
+ management.shutdown();
}
}
@@ -231,13 +331,19 @@ private static class ReadOnlyRequest {
private static ReadStreamManagement newReadStreamManagement(
RaftPeerId serverId, RaftGroupId groupId, DataApi dataApi) {
+ return newReadStreamManagement(serverId, groupId, dataApi, TestDataStreamManagement::successReply);
+ }
+
+ private static ReadStreamManagement newReadStreamManagement(RaftPeerId serverId, RaftGroupId groupId,
+ DataApi dataApi, Function> submitClientRequestAsync) {
final StateMachine stateMachine = new BaseStateMachine() {
@Override
public DataApi data() {
return dataApi;
}
};
- final RaftServer server = newRaftServer(serverId, new RaftProperties(), groupId, newDivision(stateMachine));
+ final RaftServer server = newRaftServer(serverId, new RaftProperties(), groupId, newDivision(stateMachine),
+ submitClientRequestAsync);
return new ReadStreamManagement(server);
}
@@ -275,6 +381,16 @@ private static RaftServer newRaftServer(RaftPeerId serverId, RaftProperties prop
private static RaftServer newRaftServer(RaftPeerId serverId, RaftProperties properties,
RaftGroupId groupId, RaftServer.Division division) {
+ return newRaftServer(serverId, properties, groupId, division, TestDataStreamManagement::successReply);
+ }
+
+ private static CompletableFuture successReply(RaftClientRequest request) {
+ return CompletableFuture.completedFuture(RaftClientReply.newBuilder().setRequest(request).setSuccess().build());
+ }
+
+ private static RaftServer newRaftServer(RaftPeerId serverId, RaftProperties properties,
+ RaftGroupId groupId, RaftServer.Division division,
+ Function> submitClientRequestAsync) {
return (RaftServer) Proxy.newProxyInstance(RaftServer.class.getClassLoader(), new Class>[]{RaftServer.class},
(proxy, method, args) -> {
switch (method.getName()) {
@@ -287,6 +403,8 @@ private static RaftServer newRaftServer(RaftPeerId serverId, RaftProperties prop
return division;
}
throw new IOException("Division not found: " + args[0]);
+ case "submitClientRequestAsync":
+ return submitClientRequestAsync.apply((RaftClientRequest) args[0]);
case "close":
return null;
case "toString":