Skip to content

Commit 89617c9

Browse files
authored
Support for client-side caching - phase 2 (#3673)
* Code re-use? * Stop forcing to read push notifications before checking cache and remove BCAST * Rename variable * Remove ensureFillSafe() * Refactor peeking and reading push notifications * Cleanup comments
1 parent 5fa2c80 commit 89617c9

File tree

5 files changed

+72
-59
lines changed

5 files changed

+72
-59
lines changed

Diff for: src/main/java/redis/clients/jedis/Connection.java

+1-18
Original file line numberDiff line numberDiff line change
@@ -352,11 +352,7 @@ protected Object readProtocolWithCheckingBroken() {
352352
}
353353

354354
try {
355-
Protocol.readPushes(inputStream, clientSideCache);
356-
return Protocol.read(inputStream);
357-
// Object read = Protocol.read(inputStream);
358-
// System.out.println("REPLY: " + SafeEncoder.encodeObject(read));
359-
// return read;
355+
return Protocol.read(inputStream, clientSideCache);
360356
} catch (JedisConnectionException exc) {
361357
broken = true;
362358
throw exc;
@@ -376,19 +372,6 @@ public List<Object> getMany(final int count) {
376372
return responses;
377373
}
378374

379-
protected void readPushesWithCheckingBroken() {
380-
if (broken) {
381-
throw new JedisConnectionException("Attempting to read pushes from a broken connection");
382-
}
383-
384-
try {
385-
Protocol.readPushes(inputStream, clientSideCache);
386-
} catch (JedisConnectionException exc) {
387-
broken = true;
388-
throw exc;
389-
}
390-
}
391-
392375
/**
393376
* Check if the client name libname, libver, characters are legal
394377
* @param info the name

Diff for: src/main/java/redis/clients/jedis/JedisClientSideCache.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public JedisClientSideCache(final HostAndPort hostPort, final JedisClientConfig
2424

2525
private void clientTrackingOn() {
2626
String reply = connection.executeCommand(new CommandObject<>(
27-
new CommandArguments(Protocol.Command.CLIENT).add("TRACKING").add("ON").add("BCAST"),
27+
new CommandArguments(Protocol.Command.CLIENT).add("TRACKING").add("ON"),
2828
BuilderFactory.STRING));
2929
if (!"OK".equals(reply)) {
3030
throw new JedisException("Could not enable client tracking. Reply: " + reply);
@@ -33,7 +33,6 @@ private void clientTrackingOn() {
3333

3434
@Override
3535
public String get(String key) {
36-
connection.readPushesWithCheckingBroken();
3736
String cachedValue = cache.getValue(key);
3837
if (cachedValue != null) return cachedValue;
3938

Diff for: src/main/java/redis/clients/jedis/Protocol.java

+17-12
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import java.util.Arrays;
88
import java.util.List;
99
import java.util.Locale;
10-
import java.util.Objects;
1110

1211
import redis.clients.jedis.exceptions.*;
1312
import redis.clients.jedis.args.Rawable;
@@ -171,15 +170,6 @@ private static Object process(final RedisInputStream is) {
171170
}
172171
}
173172

174-
private static void processPush(final RedisInputStream is, ClientSideCache cache) {
175-
List<Object> list = processMultiBulkReply(is);
176-
//System.out.println("PUSH: " + SafeEncoder.encodeObject(list));
177-
if (list.size() == 2 && list.get(0) instanceof byte[]
178-
&& Arrays.equals(INVALIDATE_BYTES, (byte[]) list.get(0))) {
179-
cache.invalidateKeys((List) list.get(1));
180-
}
181-
}
182-
183173
private static byte[] processBulkReply(final RedisInputStream is) {
184174
final int len = is.readIntCrLf();
185175
if (len == -1) {
@@ -232,20 +222,35 @@ private static List<KeyValue> processMapKeyValueReply(final RedisInputStream is)
232222
return ret;
233223
}
234224

225+
@Deprecated
235226
public static Object read(final RedisInputStream is) {
236227
return process(is);
237228
}
238229

239-
static void readPushes(final RedisInputStream is, final ClientSideCache cache) {
230+
public static Object read(final RedisInputStream is, final ClientSideCache cache) {
231+
readPushes(is, cache);
232+
return process(is);
233+
}
234+
235+
private static void readPushes(final RedisInputStream is, final ClientSideCache cache) {
240236
if (cache != null) {
241237
//System.out.println("PEEK: " + is.peekByte());
242-
while (Objects.equals(GREATER_THAN_BYTE, is.peekByte())) {
238+
while (is.peek(GREATER_THAN_BYTE)) {
243239
is.readByte();
244240
processPush(is, cache);
245241
}
246242
}
247243
}
248244

245+
private static void processPush(final RedisInputStream is, ClientSideCache cache) {
246+
List<Object> list = processMultiBulkReply(is);
247+
//System.out.println("PUSH: " + SafeEncoder.encodeObject(list));
248+
if (list.size() == 2 && list.get(0) instanceof byte[]
249+
&& Arrays.equals(INVALIDATE_BYTES, (byte[]) list.get(0))) {
250+
cache.invalidateKeys((List) list.get(1));
251+
}
252+
}
253+
249254
public static final byte[] toByteArray(final boolean value) {
250255
return value ? BYTES_TRUE : BYTES_FALSE;
251256
}

Diff for: src/main/java/redis/clients/jedis/util/RedisInputStream.java

+3-17
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ public RedisInputStream(InputStream in) {
4343
this(in, INPUT_BUFFER_SIZE);
4444
}
4545

46-
public Byte peekByte() {
47-
ensureFillSafe();
48-
return buf[count];
46+
public boolean peek(byte b) throws JedisConnectionException {
47+
ensureFill(); // in current design, at least one reply is expected. so ensureFillSafe() is not necessary.
48+
return buf[count] == b;
4949
}
5050

5151
public byte readByte() throws JedisConnectionException {
@@ -257,18 +257,4 @@ private void ensureFill() throws JedisConnectionException {
257257
}
258258
}
259259
}
260-
261-
private void ensureFillSafe() {
262-
if (count >= limit) {
263-
try {
264-
limit = in.read(buf);
265-
count = 0;
266-
if (limit == -1) {
267-
throw new JedisConnectionException("Unexpected end of stream.");
268-
}
269-
} catch (IOException e) {
270-
// do nothing
271-
}
272-
}
273-
}
274260
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package redis.clients.jedis;
22

3+
import static org.hamcrest.MatcherAssert.assertThat;
34
import static org.junit.Assert.assertEquals;
45
import static org.junit.Assert.assertNull;
56

7+
import org.hamcrest.Matchers;
68
import org.junit.After;
79
import org.junit.Before;
810
import org.junit.Test;
@@ -17,7 +19,7 @@ public class JedisClientSideCacheTest {
1719

1820
@Before
1921
public void setUp() throws Exception {
20-
jedis = new Jedis(hnp, DefaultJedisClientConfig.builder().timeoutMillis(500).password("foobared").build());
22+
jedis = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build());
2123
jedis.flushAll();
2224
}
2325

@@ -26,45 +28,83 @@ public void tearDown() throws Exception {
2628
jedis.close();
2729
}
2830

29-
private static final JedisClientConfig configForCache = DefaultJedisClientConfig.builder()
30-
.resp3().socketTimeoutMillis(20).password("foobared").build();
31+
private static final JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().resp3().password("foobared").build();
3132

3233
@Test
3334
public void simple() {
34-
try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, configForCache)) {
35+
try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, clientConfig)) {
3536
jedis.set("foo", "bar");
3637
assertEquals("bar", jCache.get("foo"));
3738
jedis.del("foo");
38-
assertNull(jCache.get("foo"));
39+
assertThat(jCache.get("foo"), Matchers.oneOf("bar", null)); // ?
3940
}
4041
}
4142

4243
@Test
43-
public void simpleMock() {
44+
public void simpleMoreAndMock() {
4445
ClientSideCache cache = Mockito.mock(ClientSideCache.class);
45-
try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, configForCache, cache)) {
46+
Mockito.when(cache.getValue("foo")).thenReturn(null, "bar", null);
47+
48+
try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, clientConfig, cache)) {
4649
jedis.set("foo", "bar");
50+
4751
assertEquals("bar", jCache.get("foo"));
52+
4853
jedis.del("foo");
54+
55+
assertEquals("bar", jCache.get("foo"));
56+
57+
// there should be an invalid pending; any connection command will make it read
58+
jCache.ping();
59+
4960
assertNull(jCache.get("foo"));
5061
}
5162

5263
InOrder inOrder = Mockito.inOrder(cache);
53-
inOrder.verify(cache).invalidateKeys(Mockito.notNull());
5464
inOrder.verify(cache).getValue("foo");
5565
inOrder.verify(cache).setKey("foo", "bar");
66+
inOrder.verify(cache).getValue("foo");
5667
inOrder.verify(cache).invalidateKeys(Mockito.notNull());
5768
inOrder.verify(cache).getValue("foo");
5869
inOrder.verifyNoMoreInteractions();
5970
}
6071

6172
@Test
62-
public void flushall() {
63-
try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, configForCache)) {
73+
public void flushAll() {
74+
try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, clientConfig)) {
75+
jedis.set("foo", "bar");
76+
assertEquals("bar", jCache.get("foo"));
77+
jedis.flushAll();
78+
assertThat(jCache.get("foo"), Matchers.oneOf("bar", null)); // ?
79+
}
80+
}
81+
82+
@Test
83+
public void flushAllMoreAndMock() {
84+
ClientSideCache cache = Mockito.mock(ClientSideCache.class);
85+
Mockito.when(cache.getValue("foo")).thenReturn(null, "bar", null);
86+
87+
try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, clientConfig, cache)) {
6488
jedis.set("foo", "bar");
89+
6590
assertEquals("bar", jCache.get("foo"));
91+
6692
jedis.flushAll();
93+
94+
assertEquals("bar", jCache.get("foo"));
95+
96+
// there should be an invalid pending; any connection command will make it read
97+
jCache.ping();
98+
6799
assertNull(jCache.get("foo"));
68100
}
101+
102+
InOrder inOrder = Mockito.inOrder(cache);
103+
inOrder.verify(cache).getValue("foo");
104+
inOrder.verify(cache).setKey("foo", "bar");
105+
inOrder.verify(cache).getValue("foo");
106+
inOrder.verify(cache).invalidateKeys(Mockito.isNull());
107+
inOrder.verify(cache).getValue("foo");
108+
inOrder.verifyNoMoreInteractions();
69109
}
70110
}

0 commit comments

Comments
 (0)