Skip to content

Commit be79ce7

Browse files
radifalcoHeartSaVioR
authored andcommitted
Addresses issues #779 and #775.
Conflicts: src/main/java/redis/clients/jedis/Connection.java src/main/java/redis/clients/jedis/Protocol.java
1 parent cbf26df commit be79ce7

File tree

4 files changed

+299
-97
lines changed

4 files changed

+299
-97
lines changed

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

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
package redis.clients.jedis;
22

3+
import redis.clients.jedis.Protocol.Command;
4+
import redis.clients.jedis.exceptions.JedisConnectionException;
5+
import redis.clients.jedis.exceptions.JedisDataException;
6+
import redis.clients.util.RedisInputStream;
7+
import redis.clients.util.RedisOutputStream;
8+
import redis.clients.util.SafeEncoder;
9+
310
import java.io.Closeable;
411
import java.io.IOException;
512
import java.net.InetSocketAddress;
@@ -8,13 +15,6 @@
815
import java.util.ArrayList;
916
import java.util.List;
1017

11-
import redis.clients.jedis.Protocol.Command;
12-
import redis.clients.jedis.exceptions.JedisConnectionException;
13-
import redis.clients.jedis.exceptions.JedisDataException;
14-
import redis.clients.util.RedisInputStream;
15-
import redis.clients.util.RedisOutputStream;
16-
import redis.clients.util.SafeEncoder;
17-
1818
public class Connection implements Closeable {
1919
private String host;
2020
private int port = Protocol.DEFAULT_PORT;
@@ -176,7 +176,7 @@ public boolean isConnected() {
176176
&& !socket.isOutputShutdown();
177177
}
178178

179-
protected String getStatusCodeReply() {
179+
public String getStatusCodeReply() {
180180
flush();
181181
pipelinedCommands--;
182182
final byte[] resp = (byte[]) readProtocolWithCheckingBroken();
@@ -286,4 +286,5 @@ protected Object readProtocolWithCheckingBroken() {
286286
throw exc;
287287
}
288288
}
289+
289290
}

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

+32-38
Original file line numberDiff line numberDiff line change
@@ -127,67 +127,61 @@ private static String[] parseTargetHostAndSlot(
127127
}
128128

129129
private static Object process(final RedisInputStream is) {
130-
try {
131-
byte b = is.readByte();
132-
if (b == MINUS_BYTE) {
133-
processError(is);
134-
} else if (b == ASTERISK_BYTE) {
135-
return processMultiBulkReply(is);
136-
} else if (b == COLON_BYTE) {
137-
return processInteger(is);
138-
} else if (b == DOLLAR_BYTE) {
139-
return processBulkReply(is);
140-
} else if (b == PLUS_BYTE) {
141-
return processStatusCodeReply(is);
142-
} else {
143-
throw new JedisConnectionException("Unknown reply: " + (char) b);
144-
}
145-
} catch (IOException e) {
146-
throw new JedisConnectionException(e);
130+
131+
final byte b = is.readByte();
132+
if (b == PLUS_BYTE) {
133+
return processStatusCodeReply(is);
134+
} else if (b == DOLLAR_BYTE) {
135+
return processBulkReply(is);
136+
} else if (b == ASTERISK_BYTE) {
137+
return processMultiBulkReply(is);
138+
} else if (b == COLON_BYTE) {
139+
return processInteger(is);
140+
} else if (b == MINUS_BYTE) {
141+
processError(is);
142+
return null;
143+
} else {
144+
throw new JedisConnectionException("Unknown reply: " + (char) b);
147145
}
148-
return null;
149146
}
150147

151148
private static byte[] processStatusCodeReply(final RedisInputStream is) {
152-
return SafeEncoder.encode(is.readLine());
149+
return is.readLineBytes();
153150
}
154151

155152
private static byte[] processBulkReply(final RedisInputStream is) {
156-
int len = Integer.parseInt(is.readLine());
153+
final int len = is.readIntCrLf();
157154
if (len == -1) {
158155
return null;
159156
}
160-
byte[] read = new byte[len];
157+
158+
final byte[] read = new byte[len];
161159
int offset = 0;
162-
try {
163-
while (offset < len) {
164-
int size = is.read(read, offset, (len - offset));
165-
if (size == -1)
166-
throw new JedisConnectionException(
167-
"It seems like server has closed the connection.");
168-
offset += size;
169-
}
170-
// read 2 more bytes for the command delimiter
171-
is.readByte();
172-
is.readByte();
173-
} catch (IOException e) {
174-
throw new JedisConnectionException(e);
160+
while (offset < len) {
161+
final int size = is.read(read, offset, (len - offset));
162+
if (size == -1)
163+
throw new JedisConnectionException(
164+
"It seems like server has closed the connection.");
165+
offset += size;
175166
}
176167

168+
// read 2 more bytes for the command delimiter
169+
is.readByte();
170+
is.readByte();
171+
177172
return read;
178173
}
179174

180175
private static Long processInteger(final RedisInputStream is) {
181-
String num = is.readLine();
182-
return Long.valueOf(num);
176+
return is.readLongCrLf();
183177
}
184178

185179
private static List<Object> processMultiBulkReply(final RedisInputStream is) {
186-
int num = Integer.parseInt(is.readLine());
180+
final int num = is.readIntCrLf();
187181
if (num == -1) {
188182
return null;
189183
}
190-
List<Object> ret = new ArrayList<Object>(num);
184+
final List<Object> ret = new ArrayList<Object>(num);
191185
for (int i = 0; i < num; i++) {
192186
try {
193187
ret.add(process(is));

src/main/java/redis/clients/util/RedisInputStream.java

+153-51
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616

1717
package redis.clients.util;
1818

19-
import java.io.FilterInputStream;
20-
import java.io.IOException;
21-
import java.io.InputStream;
19+
import java.io.*;
2220

2321
import redis.clients.jedis.exceptions.JedisConnectionException;
2422

23+
/**
24+
* This class assumes (to some degree) that we are reading a RESP stream. As such it assumes
25+
* certain conventions regarding CRLF line termination. It also assumes that if the Protocol
26+
* layer requires a byte that if that byte is not there it is a stream error.
27+
*/
2528
public class RedisInputStream extends FilterInputStream {
2629

2730
protected final byte buf[];
@@ -40,73 +43,172 @@ public RedisInputStream(InputStream in) {
4043
this(in, 8192);
4144
}
4245

43-
public byte readByte() throws IOException {
44-
if (count == limit) {
45-
fill();
46-
}
47-
46+
public byte readByte() throws JedisConnectionException {
47+
ensureFill();
4848
return buf[count++];
4949
}
5050

5151
public String readLine() {
52-
int b;
53-
byte c;
54-
StringBuilder sb = new StringBuilder();
55-
56-
try {
57-
while (true) {
58-
if (count == limit) {
59-
fill();
60-
}
61-
if (limit == -1)
62-
break;
52+
final StringBuilder sb = new StringBuilder();
53+
while (true) {
54+
ensureFill();
55+
56+
byte b = buf[count++];
57+
if (b == '\r') {
58+
ensureFill(); // Must be one more byte
6359

64-
b = buf[count++];
65-
if (b == '\r') {
66-
if (count == limit) {
67-
fill();
68-
}
69-
70-
if (limit == -1) {
71-
sb.append((char) b);
72-
break;
73-
}
74-
75-
c = buf[count++];
76-
if (c == '\n') {
77-
break;
78-
}
79-
sb.append((char) b);
80-
sb.append((char) c);
81-
} else {
82-
sb.append((char) b);
60+
byte c = buf[count++];
61+
if (c == '\n') {
62+
break;
8363
}
64+
sb.append((char) b);
65+
sb.append((char) c);
66+
} else {
67+
sb.append((char) b);
8468
}
85-
} catch (IOException e) {
86-
throw new JedisConnectionException(e);
8769
}
88-
String reply = sb.toString();
70+
71+
final String reply = sb.toString();
8972
if (reply.length() == 0) {
90-
throw new JedisConnectionException(
91-
"It seems like server has closed the connection.");
73+
throw new JedisConnectionException("It seems like server has closed the connection.");
9274
}
75+
9376
return reply;
9477
}
9578

96-
public int read(byte[] b, int off, int len) throws IOException {
97-
if (count == limit) {
98-
fill();
99-
if (limit == -1)
100-
return -1;
79+
public byte[] readLineBytes() {
80+
81+
/* This operation should only require one fill. In that typical
82+
case we optimize allocation and copy of the byte array. In the
83+
edge case where more than one fill is required then we take a
84+
slower path and expand a byte array output stream as is
85+
necessary. */
86+
87+
ensureFill();
88+
89+
int pos = count;
90+
final byte[] buf = this.buf;
91+
while (true) {
92+
if (pos == limit) {
93+
return readLineBytesSlowly();
94+
}
95+
96+
if (buf[pos++] == '\r') {
97+
if (pos == limit) {
98+
return readLineBytesSlowly();
99+
}
100+
101+
if (buf[pos++] == '\n') {
102+
break;
103+
}
104+
}
105+
}
106+
107+
final int N = (pos - count) - 2;
108+
final byte[] line = new byte[N];
109+
System.arraycopy(buf, count, line, 0, N);
110+
count = pos;
111+
return line;
112+
}
113+
114+
/**
115+
* Slow path in case a line of bytes cannot be read in one #fill() operation. This is still faster
116+
* than creating the StrinbBuilder, String, then encoding as byte[] in Protocol, then decoding back
117+
* into a String.
118+
*/
119+
private byte[] readLineBytesSlowly() {
120+
ByteArrayOutputStream bout = null;
121+
while (true) {
122+
ensureFill();
123+
124+
byte b = buf[count++];
125+
if (b == '\r') {
126+
ensureFill(); // Must be one more byte
127+
128+
byte c = buf[count++];
129+
if (c == '\n') {
130+
break;
131+
}
132+
133+
if (bout == null) {
134+
bout = new ByteArrayOutputStream(16);
135+
}
136+
137+
bout.write(b);
138+
bout.write(c);
139+
} else {
140+
if (bout == null) {
141+
bout = new ByteArrayOutputStream(16);
142+
}
143+
144+
bout.write(b);
145+
}
146+
}
147+
148+
return bout == null ? new byte[0] : bout.toByteArray();
149+
}
150+
151+
public int readIntCrLf() {
152+
return (int)readLongCrLf();
153+
}
154+
155+
public long readLongCrLf() {
156+
final byte[] buf = this.buf;
157+
158+
ensureFill();
159+
160+
final boolean isNeg = buf[count] == '-';
161+
if (isNeg) {
162+
++count;
101163
}
164+
165+
long value = 0;
166+
while (true) {
167+
ensureFill();
168+
169+
final int b = buf[count++];
170+
if (b == '\r') {
171+
ensureFill();
172+
173+
if (buf[count++] != '\n') {
174+
throw new JedisConnectionException("Unexpected character!");
175+
}
176+
177+
break;
178+
}
179+
else {
180+
value = value * 10 + b - '0';
181+
}
182+
}
183+
184+
return (isNeg ? -value : value);
185+
}
186+
187+
public int read(byte[] b, int off, int len) throws JedisConnectionException {
188+
ensureFill();
189+
102190
final int length = Math.min(limit - count, len);
103191
System.arraycopy(buf, count, b, off, length);
104192
count += length;
105193
return length;
106194
}
107195

108-
private void fill() throws IOException {
109-
limit = in.read(buf);
110-
count = 0;
196+
/**
197+
* This methods assumes there are required bytes to be read. If we cannot read
198+
* anymore bytes an exception is thrown to quickly ascertain that the stream
199+
* was smaller than expected.
200+
*/
201+
private void ensureFill() throws JedisConnectionException {
202+
if (count >= limit) {
203+
try {
204+
limit = in.read(buf);
205+
count = 0;
206+
if (limit == -1) {
207+
throw new JedisConnectionException("Unexpected end of stream.");
208+
}
209+
} catch (IOException e) {
210+
throw new JedisConnectionException(e);
211+
}
212+
}
111213
}
112214
}

0 commit comments

Comments
 (0)