You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ratis.apache.org by lj...@apache.org on 2020/01/06 15:55:43 UTC

[incubator-ratis] branch master updated: RATIS-776. Handle ResourceUnavailabeException properly in Ratis Server. Contributed by Lokesh Jain.

This is an automated email from the ASF dual-hosted git repository.

ljain pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-ratis.git


The following commit(s) were added to refs/heads/master by this push:
     new 66774b5  RATIS-776. Handle ResourceUnavailabeException properly in Ratis Server. Contributed by Lokesh Jain.
66774b5 is described below

commit 66774b5e4a4f655bd81a55686db9578fe60773a2
Author: Lokesh Jain <lj...@apache.org>
AuthorDate: Mon Jan 6 21:25:18 2020 +0530

    RATIS-776. Handle ResourceUnavailabeException properly in Ratis Server. Contributed by Lokesh Jain.
---
 .../grpc/client/GrpcClientProtocolService.java     |  9 +++
 .../apache/ratis/server/impl/RaftServerImpl.java   |  6 +-
 .../apache/ratis/grpc/TestRetryCacheWithGrpc.java  | 67 ++++++++++++++++++++++
 3 files changed, 80 insertions(+), 2 deletions(-)

diff --git a/ratis-grpc/src/main/java/org/apache/ratis/grpc/client/GrpcClientProtocolService.java b/ratis-grpc/src/main/java/org/apache/ratis/grpc/client/GrpcClientProtocolService.java
index 68c1b3e..d878c8d 100644
--- a/ratis-grpc/src/main/java/org/apache/ratis/grpc/client/GrpcClientProtocolService.java
+++ b/ratis-grpc/src/main/java/org/apache/ratis/grpc/client/GrpcClientProtocolService.java
@@ -215,6 +215,10 @@ public class GrpcClientProtocolService extends RaftClientProtocolServiceImplBase
       return isClosed.compareAndSet(false, true);
     }
 
+    boolean isClosed() {
+      return isClosed.get();
+    }
+
     CompletableFuture<Void> processClientRequest(RaftClientRequest request, Consumer<RaftClientReply> replyHandler) {
       try {
         String errMsg = LOG.isDebugEnabled() ? "processClientRequest for " + request.toString() : "";
@@ -330,6 +334,11 @@ public class GrpcClientProtocolService extends RaftClientProtocolServiceImplBase
 
     @Override
     void processClientRequest(RaftClientRequest r) {
+      if (isClosed()) {
+        final AlreadyClosedException exception = new AlreadyClosedException(getName() + ": the stream is closed");
+        responseError(exception, () -> "processClientRequest (stream already closed) for " + r);
+      }
+
       final RaftGroupId requestGroupId = r.getRaftGroupId();
       // use the group id in the first request as the group id of this observer
       final RaftGroupId updated = groupId.updateAndGet(g -> g != null ? g: requestGroupId);
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 d3c4e4f..364cb9e 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
@@ -509,8 +509,9 @@ public class RaftServerImpl implements RaftServerProtocol, RaftServerAsynchronou
       final LeaderState leaderState = role.getLeaderStateNonNull();
       final PendingRequests.Permit permit = leaderState.tryAcquirePendingRequest(request.getMessage());
       if (permit == null) {
-        return JavaUtils.completeExceptionally(new ResourceUnavailableException(
+        cacheEntry.failWithException(new ResourceUnavailableException(
             getMemberId() + ": Failed to acquire a pending write request for " + request));
+        return cacheEntry.getReplyFuture();
       }
       try {
         state.appendLog(context);
@@ -529,8 +530,9 @@ public class RaftServerImpl implements RaftServerProtocol, RaftServerAsynchronou
       // put the request into the pending queue
       pending = leaderState.addPendingRequest(permit, request, context);
       if (pending == null) {
-        return JavaUtils.completeExceptionally(new ResourceUnavailableException(
+        cacheEntry.failWithException(new ResourceUnavailableException(
             getMemberId() + ": Failed to add a pending write request for " + request));
+        return cacheEntry.getReplyFuture();
       }
       leaderState.notifySenders();
     }
diff --git a/ratis-test/src/test/java/org/apache/ratis/grpc/TestRetryCacheWithGrpc.java b/ratis-test/src/test/java/org/apache/ratis/grpc/TestRetryCacheWithGrpc.java
index 579cd24..192b25c 100644
--- a/ratis-test/src/test/java/org/apache/ratis/grpc/TestRetryCacheWithGrpc.java
+++ b/ratis-test/src/test/java/org/apache/ratis/grpc/TestRetryCacheWithGrpc.java
@@ -17,9 +17,76 @@
  */
 package org.apache.ratis.grpc;
 
+import org.apache.ratis.MiniRaftCluster;
+import org.apache.ratis.RaftTestUtil;
 import org.apache.ratis.RetryCacheTests;
+import org.apache.ratis.conf.RaftProperties;
+import org.apache.ratis.protocol.ClientId;
+import org.apache.ratis.protocol.RaftClientReply;
+import org.apache.ratis.protocol.RaftClientRequest;
+import org.apache.ratis.protocol.exceptions.ResourceUnavailableException;
+import org.apache.ratis.server.RaftServerConfigKeys;
+import org.apache.ratis.server.impl.RaftServerImpl;
+import org.apache.ratis.server.impl.RaftServerProxy;
+import org.apache.ratis.server.impl.RaftServerTestUtil;
+import org.apache.ratis.statemachine.SimpleStateMachine4Testing;
+import org.apache.ratis.statemachine.StateMachine;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 public class TestRetryCacheWithGrpc
     extends RetryCacheTests<MiniRaftClusterWithGrpc>
     implements MiniRaftClusterWithGrpc.FactoryGet {
+
+  @Test(timeout = 10000)
+  public void testRetryOnResourceUnavailableException()
+      throws InterruptedException, IOException {
+    RaftProperties properties = new RaftProperties();
+    properties.setClass(MiniRaftCluster.STATEMACHINE_CLASS_KEY,
+        SimpleStateMachine4Testing.class, StateMachine.class);
+    RaftServerConfigKeys.Write.setElementLimit(properties, 1);
+    MiniRaftClusterWithGrpc cluster = getFactory().newCluster(NUM_SERVERS, properties);
+    cluster.start();
+
+    RaftServerImpl leader = RaftTestUtil.waitForLeader(cluster);
+    RaftServerProxy leaderProxy = leader.getProxy();
+    for (RaftServerImpl follower : cluster.getFollowers()) {
+      // block followers to trigger ResourceUnavailableException
+      ((SimpleStateMachine4Testing) follower.getStateMachine()).blockWriteStateMachineData();
+    }
+    AtomicBoolean failure = new AtomicBoolean(false);
+    long callId = 1;
+    ClientId clientId = ClientId.randomId();
+    RaftClientRequest r = null;
+    while (!failure.get()) {
+      long cid = callId;
+      r = cluster.newRaftClientRequest(clientId, leaderProxy.getId(), callId++,
+          new RaftTestUtil.SimpleMessage("message"));
+      CompletableFuture<RaftClientReply> f = leaderProxy.submitClientRequestAsync(r);
+      f.exceptionally(e -> {
+        if (e.getCause() instanceof ResourceUnavailableException) {
+          RaftServerTestUtil.isRetryCacheEntryFailed(RaftServerTestUtil.getRetryEntry(leader, clientId, cid));
+          failure.set(true);
+        }
+        return null;
+      });
+    }
+    for (RaftServerImpl follower : cluster.getFollowers()) {
+      // unblock followers
+      ((SimpleStateMachine4Testing)follower.getStateMachine()).unblockWriteStateMachineData();
+    }
+
+    while (failure.get()) {
+      try {
+        // retry until the request failed with ResourceUnavailableException succeeds.
+        leaderProxy.submitClientRequestAsync(r).get();
+      } catch (Exception e) {
+        failure.set(false);
+      }
+    }
+    cluster.shutdown();
+  }
 }