Skip to content
This repository was archived by the owner on May 30, 2024. It is now read-only.

Commit d514e6b

Browse files
committed
Merge pull request #37 from launchdarkly/jko/fix-jedis-race
Use a thread-safe Jedis pool
2 parents 523a99a + 8bfeecf commit d514e6b

File tree

4 files changed

+114
-31
lines changed

4 files changed

+114
-31
lines changed

src/main/java/com/launchdarkly/client/FeatureStore.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.launchdarkly.client;
22

3+
import java.io.Closeable;
34
import java.util.Map;
45

56
/**
@@ -13,7 +14,7 @@
1314
* of features based on update messages that may be received out-of-order.
1415
*
1516
*/
16-
public interface FeatureStore {
17+
public interface FeatureStore extends Closeable {
1718
/**
1819
*
1920
* Returns the {@link com.launchdarkly.client.FeatureRep} to which the specified key is mapped, or
@@ -71,4 +72,5 @@ public interface FeatureStore {
7172
* @return true if this store has been initialized
7273
*/
7374
boolean initialized();
75+
7476
}

src/main/java/com/launchdarkly/client/InMemoryFeatureStore.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.launchdarkly.client;
22

3+
import java.io.IOException;
34
import java.util.HashMap;
45
import java.util.Map;
56
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -141,4 +142,14 @@ public void upsert(String key, FeatureRep<?> feature) {
141142
public boolean initialized() {
142143
return initialized;
143144
}
145+
146+
/**
147+
* Does nothing; this class does not have any resources to release
148+
* @throws IOException
149+
*/
150+
@Override
151+
public void close() throws IOException
152+
{
153+
return;
154+
}
144155
}

src/main/java/com/launchdarkly/client/RedisFeatureStore.java

Lines changed: 97 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
import com.google.common.cache.LoadingCache;
66
import com.google.gson.Gson;
77
import com.google.gson.reflect.TypeToken;
8-
import org.apache.http.util.EntityUtils;
98
import redis.clients.jedis.Jedis;
9+
import redis.clients.jedis.JedisPool;
10+
import redis.clients.jedis.JedisPoolConfig;
1011
import redis.clients.jedis.Transaction;
1112

13+
import java.io.IOException;
1214
import java.lang.reflect.Type;
1315
import java.net.URI;
1416
import java.util.HashMap;
@@ -22,7 +24,7 @@
2224
*/
2325
public class RedisFeatureStore implements FeatureStore {
2426
private static final String DEFAULT_PREFIX = "launchdarkly";
25-
private final Jedis jedis;
27+
private final JedisPool pool;
2628
private LoadingCache<String, FeatureRep<?>> cache;
2729
private LoadingCache<String, Boolean> initCache;
2830
private String prefix;
@@ -36,7 +38,7 @@ public class RedisFeatureStore implements FeatureStore {
3638
* @param cacheTimeSecs an optional timeout for the in-memory cache. If set to 0, no in-memory caching will be performed
3739
*/
3840
public RedisFeatureStore(String host, int port, String prefix, long cacheTimeSecs) {
39-
jedis = new Jedis(host, port);
41+
pool = new JedisPool(getPoolConfig(), host, port);
4042
setPrefix(prefix);
4143
createCache(cacheTimeSecs);
4244
createInitCache(cacheTimeSecs);
@@ -50,7 +52,7 @@ public RedisFeatureStore(String host, int port, String prefix, long cacheTimeSec
5052
* @param cacheTimeSecs an optional timeout for the in-memory cache. If set to 0, no in-memory caching will be performed
5153
*/
5254
public RedisFeatureStore(URI uri, String prefix, long cacheTimeSecs) {
53-
jedis = new Jedis(uri);
55+
pool = new JedisPool(getPoolConfig(), uri);
5456
setPrefix(prefix);
5557
createCache(cacheTimeSecs);
5658
createInitCache(cacheTimeSecs);
@@ -61,7 +63,7 @@ public RedisFeatureStore(URI uri, String prefix, long cacheTimeSecs) {
6163
*
6264
*/
6365
public RedisFeatureStore() {
64-
jedis = new Jedis("localhost");
66+
pool = new JedisPool(getPoolConfig(), "localhost");
6567
this.prefix = DEFAULT_PREFIX;
6668
}
6769

@@ -128,18 +130,26 @@ public FeatureRep<?> get(String key) {
128130
*/
129131
@Override
130132
public Map<String, FeatureRep<?>> all() {
131-
Map<String,String> featuresJson = jedis.hgetAll(featuresKey());
132-
Map<String, FeatureRep<?>> result = new HashMap<String, FeatureRep<?>>();
133-
Gson gson = new Gson();
133+
Jedis jedis = null;
134+
try {
135+
jedis = getJedis();
136+
Map<String,String> featuresJson = getJedis().hgetAll(featuresKey());
137+
Map<String, FeatureRep<?>> result = new HashMap<String, FeatureRep<?>>();
138+
Gson gson = new Gson();
134139

135-
Type type = new TypeToken<FeatureRep<?>>() {}.getType();
140+
Type type = new TypeToken<FeatureRep<?>>() {}.getType();
136141

137-
for (Map.Entry<String, String> entry : featuresJson.entrySet()) {
138-
FeatureRep<?> rep = gson.fromJson(entry.getValue(), type);
139-
result.put(entry.getKey(), rep);
142+
for (Map.Entry<String, String> entry : featuresJson.entrySet()) {
143+
FeatureRep<?> rep = gson.fromJson(entry.getValue(), type);
144+
result.put(entry.getKey(), rep);
145+
}
146+
return result;
147+
} finally {
148+
if (jedis != null) {
149+
jedis.close();
150+
}
140151
}
141152

142-
return result;
143153
}
144154
/**
145155
* Initializes (or re-initializes) the store with the specified set of features. Any existing entries
@@ -149,16 +159,25 @@ public Map<String, FeatureRep<?>> all() {
149159
*/
150160
@Override
151161
public void init(Map<String, FeatureRep<?>> features) {
152-
Gson gson = new Gson();
153-
Transaction t = jedis.multi();
162+
Jedis jedis = null;
154163

155-
t.del(featuresKey());
164+
try {
165+
jedis = getJedis();
166+
Gson gson = new Gson();
167+
Transaction t = jedis.multi();
156168

157-
for (FeatureRep<?> f: features.values()) {
158-
t.hset(featuresKey(), f.key, gson.toJson(f));
159-
}
169+
t.del(featuresKey());
160170

161-
t.exec();
171+
for (FeatureRep<?> f: features.values()) {
172+
t.hset(featuresKey(), f.key, gson.toJson(f));
173+
}
174+
175+
t.exec();
176+
} finally {
177+
if (jedis != null) {
178+
jedis.close();
179+
}
180+
}
162181
}
163182

164183

@@ -172,7 +191,9 @@ public void init(Map<String, FeatureRep<?>> features) {
172191
*/
173192
@Override
174193
public void delete(String key, int version) {
194+
Jedis jedis = null;
175195
try {
196+
jedis = getJedis();
176197
Gson gson = new Gson();
177198
jedis.watch(featuresKey());
178199

@@ -192,7 +213,10 @@ public void delete(String key, int version) {
192213
}
193214
}
194215
finally {
195-
jedis.unwatch();
216+
if (jedis != null) {
217+
jedis.unwatch();
218+
jedis.close();
219+
}
196220
}
197221

198222
}
@@ -206,7 +230,9 @@ public void delete(String key, int version) {
206230
*/
207231
@Override
208232
public void upsert(String key, FeatureRep<?> feature) {
233+
Jedis jedis = null;
209234
try {
235+
jedis = getJedis();
210236
Gson gson = new Gson();
211237
jedis.watch(featuresKey());
212238

@@ -223,7 +249,10 @@ public void upsert(String key, FeatureRep<?> feature) {
223249
}
224250
}
225251
finally {
226-
jedis.unwatch();
252+
if (jedis != null) {
253+
jedis.unwatch();
254+
jedis.close();
255+
}
227256
}
228257
}
229258

@@ -245,26 +274,64 @@ public boolean initialized() {
245274
return getInit();
246275
}
247276

277+
/**
278+
* Releases all resources associated with the store. The store must no longer be used once closed.
279+
* @throws IOException
280+
*/
281+
public void close() throws IOException
282+
{
283+
pool.destroy();
284+
}
285+
286+
248287

249288
private String featuresKey() {
250289
return prefix + ":features";
251290
}
252291

253292
private Boolean getInit() {
254-
return jedis.exists(featuresKey());
293+
Jedis jedis = null;
294+
295+
try {
296+
jedis = getJedis();
297+
return jedis.exists(featuresKey());
298+
} finally {
299+
if (jedis != null) {
300+
jedis.close();
301+
}
302+
}
255303
}
256304

257305
private FeatureRep<?> getRedis(String key) {
258-
Gson gson = new Gson();
259-
String featureJson = jedis.hget(featuresKey(), key);
306+
Jedis jedis = null;
307+
try {
308+
jedis = getJedis();
309+
Gson gson = new Gson();
310+
String featureJson = getJedis().hget(featuresKey(), key);
311+
312+
if (featureJson == null) {
313+
return null;
314+
}
315+
316+
Type type = new TypeToken<FeatureRep<?>>() {}.getType();
317+
FeatureRep<?> f = gson.fromJson(featureJson, type);
260318

261-
if (featureJson == null) {
262-
return null;
319+
return f.deleted ? null : f;
320+
} finally {
321+
if (jedis != null) {
322+
jedis.close();
323+
}
263324
}
325+
}
264326

265-
Type type = new TypeToken<FeatureRep<?>>() {}.getType();
266-
FeatureRep<?> f = gson.fromJson(featureJson, type);
327+
private final Jedis getJedis() {
328+
return pool.getResource();
329+
}
267330

268-
return f.deleted ? null : f;
331+
private final JedisPoolConfig getPoolConfig() {
332+
JedisPoolConfig config = new JedisPoolConfig();
333+
config.setMaxTotal(256);
334+
config.setBlockWhenExhausted(false);
335+
return config;
269336
}
270337
}

src/main/java/com/launchdarkly/client/StreamProcessor.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ public void close() throws IOException {
101101
if (es != null) {
102102
es.close();
103103
}
104+
if (store != null) {
105+
store.close();
106+
}
104107
}
105108

106109
boolean initialized() {

0 commit comments

Comments
 (0)