Skip to content

Commit 0602593

Browse files
mina-ashamgkorland
authored andcommitted
Throw an exception when trying to read from a broken connection (#1923)
- When attempting to read from a broken connection, Jedis could potentially try to read some corrupt data and interpret it according to the Redis protocol, this makes it throw an exception instead of trying that. - #1747 describes that possible misinterpretations of the protocol can lead to trying to create very large arrays - Also refactor clientPause test, it was testing that clients fail, CLIENT PAUSE command pauses all clients, so they should be expected to be delayed rather than failed
1 parent 2504f23 commit 0602593

File tree

3 files changed

+76
-34
lines changed

3 files changed

+76
-34
lines changed

src/main/java/redis/clients/jedis/Connection.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,10 @@ protected void flush() {
310310
}
311311

312312
protected Object readProtocolWithCheckingBroken() {
313+
if (broken) {
314+
throw new JedisConnectionException("Attempting to read from a broken connection");
315+
}
316+
313317
try {
314318
return Protocol.read(inputStream);
315319
} catch (JedisConnectionException exc) {

src/test/java/redis/clients/jedis/tests/ConnectionTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,30 @@ public void sendCommand(ProtocolCommand cmd, byte[]... args) {
7575
assertEquals("ERR Protocol error: invalid multibulk length", jce.getMessage());
7676
}
7777
}
78+
79+
@Test
80+
public void readWithBrokenConnection() {
81+
class BrokenConnection extends Connection {
82+
private BrokenConnection() {
83+
super("nonexistinghost", 0);
84+
try {
85+
connect();
86+
fail("Client should fail connecting to nonexistinghost");
87+
} catch (JedisConnectionException ignored) {
88+
}
89+
}
90+
91+
private Object read() {
92+
return readProtocolWithCheckingBroken();
93+
}
94+
}
95+
96+
BrokenConnection conn = new BrokenConnection();
97+
try {
98+
conn.read();
99+
fail("Read should fail as connection is broken");
100+
} catch (JedisConnectionException jce) {
101+
assertEquals("Attempting to read from a broken connection", jce.getMessage());
102+
}
103+
}
78104
}

src/test/java/redis/clients/jedis/tests/commands/ControlCommandsTest.java

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
import static org.junit.Assert.assertTrue;
66

77
import java.util.List;
8+
import java.util.concurrent.Callable;
9+
import java.util.concurrent.ExecutionException;
10+
import java.util.concurrent.ExecutorService;
11+
import java.util.concurrent.Executors;
12+
import java.util.concurrent.Future;
13+
import java.util.concurrent.TimeUnit;
814

915
import org.junit.Test;
1016

@@ -138,40 +144,48 @@ public void waitReplicas() {
138144
}
139145

140146
@Test
141-
public void clientPause() throws InterruptedException {
142-
assertEquals("PONG", jedis.ping());
143-
jedis.clientPause(600);
147+
public void clientPause() throws InterruptedException, ExecutionException {
148+
ExecutorService executorService = Executors.newFixedThreadPool(2);
144149
try {
145-
jedis.ping();
146-
} catch (Exception e) {
147-
assertEquals("java.net.SocketTimeoutException: Read timed out", e.getMessage());
148-
}
149-
Thread.sleep(100);
150-
assertEquals("PONG", jedis.ping());
151-
152-
Jedis jedis1 = createJedis();
153-
Jedis jedis2 = createJedis();
154-
assertEquals("PONG", jedis1.ping());
155-
assertEquals("PONG", jedis2.ping());
156-
jedis.clientPause(1200);
157-
try {
158-
jedis1.ping();
159-
} catch (Exception e) {
160-
assertEquals("java.net.SocketTimeoutException: Read timed out", e.getMessage());
161-
}
162-
try {
163-
jedis2.ping();
164-
} catch (Exception e) {
165-
assertEquals("java.net.SocketTimeoutException: Read timed out", e.getMessage());
166-
}
167-
Thread.sleep(200);
168-
assertEquals("PONG", jedis1.ping());
169-
assertEquals("PONG", jedis2.ping());
150+
final Jedis jedisToPause1 = createJedis();
151+
final Jedis jedisToPause2 = createJedis();
152+
153+
int pauseMillis = 1250;
154+
jedis.clientPause(pauseMillis);
155+
156+
Future<Long> latency1 = executorService.submit(new Callable<Long>() {
157+
@Override
158+
public Long call() throws Exception {
159+
long startMillis = System.currentTimeMillis();
160+
assertEquals("PONG", jedisToPause1.ping());
161+
return System.currentTimeMillis() - startMillis;
162+
}
163+
});
164+
Future<Long> latency2 = executorService.submit(new Callable<Long>() {
165+
@Override
166+
public Long call() throws Exception {
167+
long startMillis = System.currentTimeMillis();
168+
assertEquals("PONG", jedisToPause2.ping());
169+
return System.currentTimeMillis() - startMillis;
170+
}
171+
});
170172

171-
jedis1.close();
172-
jedis2.close();
173-
}
173+
long latencyMillis1 = latency1.get();
174+
long latencyMillis2 = latency2.get();
174175

176+
int pauseMillisDelta = 100;
177+
assertTrue(pauseMillis <= latencyMillis1 && latencyMillis1 <= pauseMillis + pauseMillisDelta);
178+
assertTrue(pauseMillis <= latencyMillis2 && latencyMillis2 <= pauseMillis + pauseMillisDelta);
179+
180+
jedisToPause1.close();
181+
jedisToPause2.close();
182+
} finally {
183+
executorService.shutdown();
184+
if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
185+
executorService.shutdownNow();
186+
}
187+
}
188+
}
175189

176190
@Test
177191
public void memoryDoctorString() {
@@ -184,6 +198,4 @@ public void memoryDoctorBinary() {
184198
byte[] memoryInfo = jedis.memoryDoctorBinary();
185199
assertNotNull(memoryInfo);
186200
}
187-
188-
189-
}
201+
}

0 commit comments

Comments
 (0)