DATAREDIS-721 - Switch to non-blocking connect methods.

We now connect asynchronously to expose non-blocking connection behavior for reactive API usage. We also use a non-blocking, asynchronous pool implementation for connections that are used through a reactive API. Shared connection usage, which is blocking on the very first access can be pre-initialized during connection factory initialization instead of initialization on first access:

LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory();
connectionFactory.setEagerInitialization(true);
connectionFactory.afterPropertiesSet();

We no longer require offloading of blocking connects to a dedicated scheduler which makes reactive API usage fully non-blocking.

Original Pull Request: #315
This commit is contained in:
Mark Paluch
2018-02-22 10:43:46 +01:00
committed by Christoph Strobl
parent 2b094850a2
commit b496aab2fb
14 changed files with 542 additions and 106 deletions

View File

@@ -29,6 +29,8 @@ import io.lettuce.core.AbstractRedisClient;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulConnection;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.codec.ByteArrayCodec;
@@ -38,6 +40,7 @@ import io.lettuce.core.resource.ClientResources;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
import org.junit.After;
import org.junit.Before;
@@ -590,7 +593,7 @@ public class LettuceConnectionFactoryUnitTests {
RedisClusterClient clientMock = mock(RedisClusterClient.class);
StatefulRedisClusterConnection<byte[], byte[]> connectionMock = mock(StatefulRedisClusterConnection.class);
when(clientMock.connect(ByteArrayCodec.INSTANCE)).thenReturn(connectionMock);
when(clientMock.connectAsync(ByteArrayCodec.INSTANCE)).thenReturn(CompletableFuture.completedFuture(connectionMock));
LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(clusterConfig,
LettuceClientConfiguration.defaultConfiguration()) {
@@ -608,7 +611,31 @@ public class LettuceConnectionFactoryUnitTests {
connectionFactory.getClusterConnection().close();
connectionFactory.getClusterConnection().close();
verify(clientMock).connect(ArgumentMatchers.any(RedisCodec.class));
verify(clientMock).connectAsync(ArgumentMatchers.any(RedisCodec.class));
}
@Test // DATAREDIS-721
@SuppressWarnings("unchecked")
public void shouldEagerlyInitializeSharedConnection() {
LettuceConnectionProvider connectionProviderMock = mock(LettuceConnectionProvider.class);
StatefulRedisConnection connectionMock = mock(StatefulRedisConnection.class);
when(connectionProviderMock.getConnectionAsync(any()))
.thenReturn(CompletableFuture.completedFuture(connectionMock));
LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory() {
@Override
protected LettuceConnectionProvider doCreateConnectionProvider(AbstractRedisClient client,
RedisCodec<?, ?> codec) {
return connectionProviderMock;
}
};
connectionFactory.setEagerInitialization(true);
connectionFactory.afterPropertiesSet();
verify(connectionProviderMock, times(2)).getConnection(StatefulConnection.class);
}
@Test // DATAREDIS-842

View File

@@ -21,6 +21,7 @@ import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.springframework.data.redis.connection.ClusterTestVariables.*;
import io.lettuce.core.ConnectionFuture;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.reactive.RedisReactiveCommands;
import io.lettuce.core.cluster.RedisClusterClient;
@@ -30,6 +31,7 @@ import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletableFuture;
import org.junit.Before;
import org.junit.Test;
@@ -60,8 +62,8 @@ public class LettuceReactiveRedisClusterConnectionUnitTests {
@Before
public void before() {
when(connectionProvider.getConnection(any())).thenReturn(sharedConnection);
when(sharedConnection.getConnection(anyString(), anyInt())).thenReturn(nodeConnection);
when(connectionProvider.getConnectionAsync(any())).thenReturn(CompletableFuture.completedFuture(sharedConnection));
when(sharedConnection.getConnectionAsync(anyString(), anyInt())).thenReturn(CompletableFuture.completedFuture(nodeConnection));
when(nodeConnection.reactive()).thenReturn(reactiveNodeCommands);
}

View File

@@ -27,7 +27,6 @@ import reactor.test.StepVerifier;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
@@ -53,7 +52,8 @@ public class LettuceReactiveRedisConnectionUnitTests {
@Before
public void before() {
when(connectionProvider.getConnection(any())).thenReturn(sharedConnection);
when(connectionProvider.getConnectionAsync(any())).thenReturn(CompletableFuture.completedFuture(sharedConnection));
when(connectionProvider.releaseAsync(any())).thenReturn(CompletableFuture.completedFuture(null));
when(sharedConnection.reactive()).thenReturn(reactiveCommands);
}
@@ -65,24 +65,24 @@ public class LettuceReactiveRedisConnectionUnitTests {
verifyZeroInteractions(connectionProvider);
}
@Test // DATAREDIS-720
@Test // DATAREDIS-720, DATAREDIS-721
public void shouldExecuteUsingConnectionProvider() {
LettuceReactiveRedisConnection connection = new LettuceReactiveRedisConnection(connectionProvider);
StepVerifier.create(connection.execute(cmd -> Mono.just("foo"))).expectNext("foo").verifyComplete();
verify(connectionProvider).getConnection(StatefulConnection.class);
verify(connectionProvider).getConnectionAsync(StatefulConnection.class);
}
@Test // DATAREDIS-720
@Test // DATAREDIS-720, DATAREDIS-721
public void shouldExecuteDedicatedUsingConnectionProvider() {
LettuceReactiveRedisConnection connection = new LettuceReactiveRedisConnection(connectionProvider);
StepVerifier.create(connection.executeDedicated(cmd -> Mono.just("foo"))).expectNext("foo").verifyComplete();
verify(connectionProvider).getConnection(StatefulConnection.class);
verify(connectionProvider).getConnectionAsync(StatefulConnection.class);
}
@Test // DATAREDIS-720
@@ -96,7 +96,7 @@ public class LettuceReactiveRedisConnectionUnitTests {
verifyZeroInteractions(connectionProvider);
}
@Test // DATAREDIS-720
@Test // DATAREDIS-720, DATAREDIS-721
public void shouldExecuteDedicatedWithSharedConnection() {
LettuceReactiveRedisConnection connection = new LettuceReactiveRedisConnection(sharedConnection,
@@ -104,20 +104,20 @@ public class LettuceReactiveRedisConnectionUnitTests {
StepVerifier.create(connection.executeDedicated(cmd -> Mono.just("foo"))).expectNext("foo").verifyComplete();
verify(connectionProvider).getConnection(StatefulConnection.class);
verify(connectionProvider).getConnectionAsync(StatefulConnection.class);
}
@Test // DATAREDIS-720
@Test // DATAREDIS-720, DATAREDIS-721
public void shouldOperateOnDedicatedConnection() {
LettuceReactiveRedisConnection connection = new LettuceReactiveRedisConnection(connectionProvider);
StepVerifier.create(connection.getConnection()).expectNextCount(1).verifyComplete();
verify(connectionProvider).getConnection(StatefulConnection.class);
verify(connectionProvider).getConnectionAsync(StatefulConnection.class);
}
@Test // DATAREDIS-720
@Test // DATAREDIS-720, DATAREDIS-721
public void shouldCloseOnlyDedicatedConnection() {
LettuceReactiveRedisConnection connection = new LettuceReactiveRedisConnection(sharedConnection,
@@ -128,11 +128,11 @@ public class LettuceReactiveRedisConnectionUnitTests {
connection.close();
verify(sharedConnection, never()).close();
verify(connectionProvider, times(1)).release(sharedConnection);
verify(sharedConnection, never()).closeAsync();
verify(connectionProvider, times(1)).releaseAsync(sharedConnection);
}
@Test // DATAREDIS-720
@Test // DATAREDIS-720, DATAREDIS-721
public void shouldCloseConnectionOnlyOnce() {
LettuceReactiveRedisConnection connection = new LettuceReactiveRedisConnection(connectionProvider);
@@ -142,21 +142,16 @@ public class LettuceReactiveRedisConnectionUnitTests {
connection.close();
connection.close();
verify(connectionProvider, times(1)).release(sharedConnection);
verify(connectionProvider, times(1)).releaseAsync(sharedConnection);
}
@Test // DATAREDIS-720
@Test // DATAREDIS-720, DATAREDIS-721
@SuppressWarnings("unchecked")
public void multipleCallsInProgressShouldConnectOnlyOnce() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
CompletableFuture<StatefulConnection<?, ?>> connectionFuture = new CompletableFuture<>();
reset(connectionProvider);
when(connectionProvider.getConnection(any())).thenAnswer(invocation -> {
latch.await();
return sharedConnection;
});
when(connectionProvider.getConnectionAsync(any())).thenReturn(connectionFuture);
LettuceReactiveRedisConnection connection = new LettuceReactiveRedisConnection(connectionProvider);
@@ -168,9 +163,9 @@ public class LettuceReactiveRedisConnectionUnitTests {
assertThat(first).isNotDone();
assertThat(second).isNotDone();
verify(connectionProvider, times(1)).getConnection(StatefulConnection.class);
verify(connectionProvider, times(1)).getConnectionAsync(StatefulConnection.class);
latch.countDown();
connectionFuture.complete(sharedConnection);
first.get(10, TimeUnit.SECONDS);
second.get(10, TimeUnit.SECONDS);
@@ -179,24 +174,25 @@ public class LettuceReactiveRedisConnectionUnitTests {
assertThat(second).isCompletedWithValue(sharedConnection);
}
@Test // DATAREDIS-720
@Test // DATAREDIS-720, DATAREDIS-721
public void shouldPropagateConnectionFailures() {
reset(connectionProvider);
when(connectionProvider.getConnection(any())).thenThrow(new RedisConnectionException("something went wrong"));
when(connectionProvider.getConnectionAsync(any()))
.thenReturn(LettuceFutureUtils.failed(new RedisConnectionException("something went wrong")));
LettuceReactiveRedisConnection connection = new LettuceReactiveRedisConnection(connectionProvider);
StepVerifier.create(connection.getConnection()).expectError(RedisConnectionFailureException.class).verify();
}
@Test(expected = IllegalStateException.class) // DATAREDIS-720
@Test // DATAREDIS-720, DATAREDIS-721
public void shouldRejectCommandsAfterClose() {
LettuceReactiveRedisConnection connection = new LettuceReactiveRedisConnection(connectionProvider);
connection.close();
connection.getConnection();
StepVerifier.create(connection.getConnection()).expectError(IllegalStateException.class).verify();
}
@Test // DATAREDIS-659, DATAREDIS-708