Repository: fppt/jedis-mock (java)

Issue:
RedisClient not closed  when RedisCommands forgot to quit.
When following code was running, redisClient meet a EOFException and program hang on. 
```
RedisServer redis = RedisServer.newRedisServer(6379);
redis.start();
try {
    RedisClient redisClient = RedisClient.create("redis://passwd@127.0.0.1:6379/0");
    try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
        RedisCommands<String, String> syncCommands = connection.sync();
        syncCommands.set("key", "Hello World!");
        System.out.printf("%s\n", syncCommands.get("key"));
        //syncCommands.quit();
    }
    redisClient.shutdown();
} finally {
    redis.stop();
}
```
Please add socket close check to RedisClient's run() procedure
from
```
while (running.get()) {
    Optional<RedisCommand> command = nextCommand();
    ...
```
to
```
while (running.get()) {
    if (socket.isClosed()){
        close(); //resources release here.
    }else{
        Optional<RedisCommand> command = nextCommand();
    ...
```



Pull request description:
fix #135
Fixes #135: guards RedisClient from further execution when the socket is closed. It also provides a much simpler solution for the problem than in #106

Patch:
diff --git a/pom.xml b/pom.xml
index 60741ee..0354ec8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,7 +26,11 @@
             <name>Filipe Peliz Pinto Teixeira</name>
             <email>fppintoteixeira@gmail.com</email>
         </developer>
-
+        <developer>
+            <id>inponomarev</id>
+            <name>Ivan Ponomarev</name>
+            <email>iponomarev@curs.ru</email>
+        </developer>
         <!-- Original developer of library no longer maintaining project (Kudos To You Sir!!)-->
         <developer>
             <id>zxl0714</id>
@@ -96,6 +100,12 @@
             <version>${testcontainers.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>3.10.0</version>
+            <scope>test</scope>
+        </dependency>
         <!-- SLF4J is the logging API all modules should use -->
         <dependency>
             <groupId>org.slf4j</groupId>
diff --git a/src/main/java/com/github/fppt/jedismock/server/RedisClient.java b/src/main/java/com/github/fppt/jedismock/server/RedisClient.java
index 0e32fca..6ab0674 100644
--- a/src/main/java/com/github/fppt/jedismock/server/RedisClient.java
+++ b/src/main/java/com/github/fppt/jedismock/server/RedisClient.java
@@ -47,10 +47,9 @@ public class RedisClient implements Runnable {
 
     public void run() {
         int count = 0;
-        while (running.get()) {
+        while (running.get() && !socket.isClosed()) {
             Optional<RedisCommand> command = nextCommand();
-
-            if(command.isPresent()){
+            if (command.isPresent()) {
                 Slice response = executor.execCommand(command.get());
                 sendResponse(response, command.toString());
 
@@ -69,10 +68,10 @@ public class RedisClient implements Runnable {
      *
      * @return The next command on the stream if one was issues
      */
-    private Optional<RedisCommand> nextCommand(){
+    private Optional<RedisCommand> nextCommand() {
         try {
             return Optional.of(RedisCommandParser.parse(in));
-        } catch (ParseErrorException e){
+        } catch (ParseErrorException e) {
             return Optional.empty(); // This simply means there is no next command
         }
     }
@@ -80,7 +79,7 @@ public class RedisClient implements Runnable {
     /**
      * Send a response due to a specific command.
      *
-     * @param response The respond to send.
+     * @param response     The respond to send.
      * @param respondingTo The reason for sending this response
      */
     public void sendResponse(Slice response, String respondingTo) {
@@ -88,8 +87,8 @@ public class RedisClient implements Runnable {
             if (!response.equals(Response.SKIP)) {
                 out.write(response.data());
             }
-        } catch (IOException e){
-            LOG.error("unable to send [" + response + "] as response to [" + respondingTo +"]", e);
+        } catch (IOException e) {
+            LOG.error("unable to send [" + response + "] as response to [" + respondingTo + "]", e);
         }
     }
 
@@ -97,7 +96,7 @@ public class RedisClient implements Runnable {
      * Close all the streams used by this client effectively closing the client.
      * Also signals the client to stop working.
      */
-    public void close(){
+    public void close() {
         running.set(false);
         Utils.closeQuietly(socket);
         Utils.closeQuietly(in);


Test patch:
diff --git a/src/test/java/com/github/fppt/jedismock/server/RedisClientTest.java b/src/test/java/com/github/fppt/jedismock/server/RedisClientTest.java
new file mode 100644
index 0000000..d7f708a
--- /dev/null
+++ b/src/test/java/com/github/fppt/jedismock/server/RedisClientTest.java
@@ -0,0 +1,31 @@
+package com.github.fppt.jedismock.server;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.util.Collections;
+
+class RedisClientTest {
+    Socket s;
+    RedisClient redisClient;
+
+    @BeforeEach
+    void init() throws IOException {
+        s = Mockito.mock(Socket.class);
+        Mockito.when(s.getInputStream()).thenReturn(Mockito.mock(InputStream.class));
+        Mockito.when(s.getOutputStream()).thenReturn(Mockito.mock(OutputStream.class));
+        redisClient = new RedisClient(Collections.emptyMap(), s, ServiceOptions.defaultOptions());
+    }
+
+    @Test
+    void testClosedSocket() throws IOException {
+        Mockito.when(s.isClosed()).thenReturn(true);
+        redisClient.run();
+    }
+
+}
\ No newline at end of file