Skip to content

Commit 4ddd7f4

Browse files
authored
Fix connection leak in scanIteration with JedisSentineled #4323 (#4328)
* docs: document required optional dependency `resilience4j-all` when using failover client * Fix connection leak in scanIteration with JedisSentineled Override getConnectionMap() in SentineledConnectionProvider to return the pool instead of a raw connection. This prevents connection leaks when using scanIteration() with JedisSentineled. Fixes #4323
1 parent 1d8d075 commit 4ddd7f4

File tree

3 files changed

+141
-0
lines changed

3 files changed

+141
-0
lines changed

src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import java.util.ArrayList;
44
import java.util.Collection;
5+
import java.util.Collections;
56
import java.util.List;
7+
import java.util.Map;
68
import java.util.Set;
79
import java.util.concurrent.atomic.AtomicBoolean;
810
import java.util.concurrent.locks.Lock;
@@ -24,6 +26,7 @@
2426
import redis.clients.jedis.exceptions.JedisConnectionException;
2527
import redis.clients.jedis.exceptions.JedisException;
2628
import redis.clients.jedis.util.IOUtils;
29+
import redis.clients.jedis.util.Pool;
2730

2831
public class SentineledConnectionProvider implements ConnectionProvider {
2932

@@ -112,6 +115,16 @@ public Connection getConnection(CommandArguments args) {
112115
return pool.getResource();
113116
}
114117

118+
@Override
119+
public Map<?, Pool<Connection>> getConnectionMap() {
120+
return Collections.singletonMap(currentMaster, pool);
121+
}
122+
123+
@Override
124+
public Map<?, Pool<Connection>> getPrimaryNodesConnectionMap() {
125+
return Collections.singletonMap(currentMaster, pool);
126+
}
127+
115128
@Override
116129
public void close() {
117130
sentinelListeners.forEach(SentinelListener::shutdown);

src/test/java/redis/clients/jedis/SentineledConnectionProviderTest.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
package redis.clients.jedis;
22

33
import java.util.HashSet;
4+
import java.util.Map;
45
import java.util.Set;
56

67
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
78

89
import org.junit.jupiter.api.BeforeEach;
910
import org.junit.jupiter.api.Test;
1011
import org.junit.jupiter.api.Tag;
12+
import org.junit.jupiter.api.Timeout;
1113
import redis.clients.jedis.exceptions.JedisConnectionException;
1214
import redis.clients.jedis.exceptions.JedisException;
1315
import redis.clients.jedis.providers.SentineledConnectionProvider;
16+
import redis.clients.jedis.util.ReflectionTestUtil;
1417

18+
import static org.hamcrest.MatcherAssert.assertThat;
19+
import static org.hamcrest.Matchers.equalTo;
20+
import static org.hamcrest.Matchers.hasKey;
21+
import static org.hamcrest.Matchers.sameInstance;
1522
import static org.junit.jupiter.api.Assertions.assertEquals;
1623
import static org.junit.jupiter.api.Assertions.assertSame;
1724
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -28,6 +35,8 @@ public class SentineledConnectionProviderTest {
2835
protected static final HostAndPort sentinel1 = HostAndPorts.getSentinelServers().get(1);
2936
protected static final HostAndPort sentinel2 = HostAndPorts.getSentinelServers().get(3);
3037

38+
private static final EndpointConfig primary = HostAndPorts.getRedisEndpoint("standalone2-primary");
39+
3140
protected Set<HostAndPort> sentinels = new HashSet<>();
3241

3342
@BeforeEach
@@ -51,6 +60,68 @@ public void repeatedSentinelPoolInitialization() {
5160
}
5261
}
5362

63+
/**
64+
* Ensure that getConnectionMap() does not cause connection leak. (#4323)
65+
*/
66+
@Test
67+
@Timeout( value = 1)
68+
public void getConnectionMapDoesNotCauseConnectionLeak() {
69+
70+
ConnectionPoolConfig config = new ConnectionPoolConfig();
71+
config.setMaxTotal(1);
72+
73+
try (SentineledConnectionProvider sut = new SentineledConnectionProvider(MASTER_NAME,
74+
primary.getClientConfigBuilder().build(), config, sentinels,
75+
DefaultJedisClientConfig.builder().build())) {
76+
77+
HostAndPort resolvedPrimary = sut.getCurrentMaster();
78+
ConnectionPool pool = ReflectionTestUtil.getField(sut,"pool");
79+
assertThat(pool.getNumActive(), equalTo(0));
80+
81+
Map<?, ?> cm = sut.getConnectionMap();
82+
83+
// exactly one entry for current primary
84+
// and no active connections
85+
assertThat(cm.size(), equalTo(1));
86+
assertThat(cm, hasKey(resolvedPrimary));
87+
assertThat(pool.getNumActive(), equalTo(0));
88+
// primary did not change
89+
assertThat(ReflectionTestUtil.getField(sut,"pool"), sameInstance(pool));
90+
}
91+
}
92+
93+
/**
94+
* Ensure that getPrimaryNodesConnectionMap() does not cause connection leak. (#4323)
95+
*/
96+
@Test
97+
@Timeout( value = 1)
98+
public void getPrimaryNodesConnectionMapDoesNotCauseConnectionLeak() {
99+
100+
ConnectionPoolConfig config = new ConnectionPoolConfig();
101+
config.setMaxTotal(1);
102+
103+
try (SentineledConnectionProvider sut = new SentineledConnectionProvider(MASTER_NAME,
104+
primary.getClientConfigBuilder().build(), config, sentinels,
105+
DefaultJedisClientConfig.builder().build())) {
106+
107+
HostAndPort resolvedPrimary = sut.getCurrentMaster();
108+
ConnectionPool pool = ReflectionTestUtil.getField(sut,"pool");
109+
assertThat(pool.getNumActive(), equalTo(0));
110+
111+
112+
Map<?, ?> cm = sut.getPrimaryNodesConnectionMap();
113+
114+
// exactly one entry for current primary
115+
// and no active connections
116+
assertThat(cm.size(), equalTo(1));
117+
assertThat(cm, hasKey(resolvedPrimary));
118+
assertThat(pool.getNumActive(), equalTo(0));
119+
// primary did not change
120+
assertThat(ReflectionTestUtil.getField(sut,"pool"), sameInstance(pool));
121+
}
122+
123+
}
124+
54125
@Test
55126
public void initializeWithNotAvailableSentinelsShouldThrowException() {
56127
Set<HostAndPort> wrongSentinels = new HashSet<>();
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package redis.clients.jedis.commands.unified.sentinel;
2+
3+
import org.junit.jupiter.api.extension.RegisterExtension;
4+
import org.junit.jupiter.params.ParameterizedClass;
5+
import org.junit.jupiter.params.provider.MethodSource;
6+
import redis.clients.jedis.DefaultJedisClientConfig;
7+
import redis.clients.jedis.EndpointConfig;
8+
import redis.clients.jedis.HostAndPort;
9+
import redis.clients.jedis.HostAndPorts;
10+
import redis.clients.jedis.JedisClientConfig;
11+
import redis.clients.jedis.JedisSentineled;
12+
import redis.clients.jedis.RedisProtocol;
13+
import redis.clients.jedis.UnifiedJedis;
14+
import redis.clients.jedis.commands.unified.AllKindOfValuesCommandsTestBase;
15+
import redis.clients.jedis.util.EnabledOnCommandCondition;
16+
import redis.clients.jedis.util.RedisVersionCondition;
17+
18+
import java.util.Arrays;
19+
import java.util.HashSet;
20+
import java.util.Set;
21+
22+
@ParameterizedClass
23+
@MethodSource("redis.clients.jedis.commands.CommandsTestsParameters#respVersions")
24+
public class SentinelAllKindOfValuesCommandsIT extends AllKindOfValuesCommandsTestBase {
25+
26+
static final HostAndPort sentinel1 = HostAndPorts.getSentinelServers().get(1);
27+
28+
static final HostAndPort sentinel2 = HostAndPorts.getSentinelServers().get(3);
29+
30+
static final Set<HostAndPort> sentinels = new HashSet<>(Arrays.asList(sentinel1, sentinel2));
31+
32+
static final JedisClientConfig sentinelClientConfig = DefaultJedisClientConfig.builder().build();
33+
34+
static final EndpointConfig primary = HostAndPorts.getRedisEndpoint("standalone2-primary");
35+
36+
@RegisterExtension
37+
public RedisVersionCondition versionCondition = new RedisVersionCondition(
38+
primary.getHostAndPort(), primary.getClientConfigBuilder().build());
39+
40+
@RegisterExtension
41+
public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition(
42+
primary.getHostAndPort(), primary.getClientConfigBuilder().build());
43+
44+
public SentinelAllKindOfValuesCommandsIT(RedisProtocol protocol) {
45+
super(protocol);
46+
}
47+
48+
@Override
49+
protected UnifiedJedis createTestClient() {
50+
51+
return JedisSentineled.builder()
52+
.clientConfig(primary.getClientConfigBuilder().protocol(protocol).build())
53+
.sentinels(sentinels).sentinelClientConfig(sentinelClientConfig).masterName("mymaster")
54+
.build();
55+
}
56+
57+
}

0 commit comments

Comments
 (0)