Skip to content

Allow RedisConnectionFactories to be initialized as part of the context lifecycle #2868

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0-GH-2866-SNAPSHOT</version>

<name>Spring Data Redis</name>
<description>Spring Data module for Redis</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@
* This connection factory implements {@link InitializingBean} and {@link SmartLifecycle} for flexible lifecycle
* control. It must be {@link #afterPropertiesSet() initialized} and {@link #start() started} before you can obtain a
* connection. {@link #afterPropertiesSet() Initialization} {@link SmartLifecycle#start() starts} this bean
* {@link #isAutoStartup() by default}. You can {@link SmartLifecycle#stop()} and {@link SmartLifecycle#start() restart}
* this connection factory if needed.
* {@link #isEarlyStartup() early} by default. You can {@link SmartLifecycle#stop()} and {@link SmartLifecycle#start()
* restart} this connection factory if needed. Disabling {@link #isEarlyStartup() early startup} leaves lifecycle
* management to the container refresh if {@link #isAutoStartup() auto-startup} is enabled.
* <p>
* Note that {@link JedisConnection} and its {@link JedisClusterConnection clustered variant} are not Thread-safe and
* instances should not be shared across threads. Refer to the
Expand All @@ -103,9 +104,10 @@ public class JedisConnectionFactory
private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new PassThroughExceptionTranslationStrategy(
JedisExceptionConverter.INSTANCE);

private boolean convertPipelineAndTxResults = true;

private int phase = 0; // in between min and max values
private boolean autoStartup = true;
private boolean earlyStartup = true;
private boolean convertPipelineAndTxResults = true;

private final AtomicReference<State> state = new AtomicReference<>(State.CREATED);

Expand Down Expand Up @@ -571,6 +573,70 @@ public RedisClusterConfiguration getClusterConfiguration() {
return RedisConfiguration.isClusterConfiguration(configuration) ? (RedisClusterConfiguration) configuration : null;
}

@Override
public int getPhase() {
return this.phase;
}

/**
* Specify the lifecycle phase for pausing and resuming this executor. The default is {@code 0}.
*
* @since 3.2
* @see SmartLifecycle#getPhase()
*/
public void setPhase(int phase) {
this.phase = phase;
}

/**
* @since 3.3
*/
@Override
public boolean isAutoStartup() {
return this.autoStartup;
}

/**
* Configure if this Lifecycle connection factory should get started automatically by the container at the time that
* the containing ApplicationContext gets refreshed.
* <p>
* This connection factory defaults to early auto-startup during {@link #afterPropertiesSet()} and can potentially
* create Redis connections early on in the lifecycle. See {@link #setEarlyStartup(boolean)} for delaying connection
* creation to the ApplicationContext refresh if auto-startup is enabled.
*
* @param autoStartup {@literal true} to automatically {@link #start()} the connection factory; {@literal false}
* otherwise.
* @since 3.3
* @see #setEarlyStartup(boolean)
* @see #start()
*/
public void setAutoStartup(boolean autoStartup) {
this.autoStartup = autoStartup;
}

/**
* @return whether to {@link #start()} the component during {@link #afterPropertiesSet()}.
* @since 3.3
*/
public boolean isEarlyStartup() {
return this.earlyStartup;
}

/**
* Configure if this InitializingBean's component Lifecycle should get started early by {@link #afterPropertiesSet()}
* at the time that the bean is initialized. The component defaults to auto-startup.
* <p>
* This method is related to {@link #setAutoStartup(boolean) auto-startup} and can be used to delay Redis client
* startup until the ApplicationContext refresh. Disabling early startup does not disable auto-startup.
*
* @param earlyStartup {@literal true} to early {@link #start()} the component; {@literal false} otherwise.
* @since 3.3
* @see #setAutoStartup(boolean)
*/
public void setEarlyStartup(boolean earlyStartup) {
this.earlyStartup = earlyStartup;
}

/**
* Specifies if pipelined results should be converted to the expected data type. If {@code false}, results of
* {@link JedisConnection#closePipeline()} and {@link JedisConnection#exec()} will be of the type returned by the
Expand Down Expand Up @@ -616,7 +682,7 @@ public void afterPropertiesSet() {

this.clientConfig = createClientConfig(getDatabase(), getRedisUsername(), getRedisPassword());

if (isAutoStartup()) {
if (isEarlyStartup()) {
start();
}
}
Expand Down Expand Up @@ -724,21 +790,6 @@ public void stop() {
}
}

@Override
public int getPhase() {
return this.phase;
}

/**
* Specify the lifecycle phase for pausing and resuming this executor. The default is {@code 0}.
*
* @since 3.2
* @see SmartLifecycle#getPhase()
*/
public void setPhase(int phase) {
this.phase = phase;
}

@Override
public boolean isRunning() {
return State.STARTED.equals(this.state.get());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,9 @@
* This connection factory implements {@link InitializingBean} and {@link SmartLifecycle} for flexible lifecycle
* control. It must be {@link #afterPropertiesSet() initialized} and {@link #start() started} before you can obtain a
* connection. {@link #afterPropertiesSet() Initialization} {@link SmartLifecycle#start() starts} this bean
* {@link #isAutoStartup() by default}. You can {@link SmartLifecycle#stop()} and {@link SmartLifecycle#start() restart}
* this connection factory if needed.
* {@link #isEarlyStartup() early} by default. You can {@link SmartLifecycle#stop()} and {@link SmartLifecycle#start()
* restart} this connection factory if needed. Disabling {@link #isEarlyStartup() early startup} leaves lifecycle
* management to the container refresh if {@link #isAutoStartup() auto-startup} is enabled.
*
* @author Costin Leau
* @author Jennifer Hickey
Expand All @@ -121,13 +122,14 @@ public class LettuceConnectionFactory implements RedisConnectionFactory, Reactiv
private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new PassThroughExceptionTranslationStrategy(
LettuceExceptionConverter.INSTANCE);

private int phase = 0; // in between min and max values
private boolean autoStartup = true;
private boolean earlyStartup = true;
private boolean convertPipelineAndTxResults = true;
private boolean eagerInitialization = false;

private boolean shareNativeConnection = true;
private boolean validateConnection = false;

private int phase = 0; // in between min and max values

private @Nullable AbstractRedisClient client;

private final AtomicReference<State> state = new AtomicReference<>(State.CREATED);
Expand Down Expand Up @@ -556,12 +558,13 @@ public void setShareNativeConnection(boolean shareNativeConnection) {

/**
* Indicates {@link #setShareNativeConnection(boolean) shared connections} should be eagerly initialized. Eager
* initialization requires a running Redis instance during application startup to allow early validation of connection
* factory configuration. Eager initialization also prevents blocking connect while using reactive API and is
* recommended for reactive API usage.
* initialization requires a running Redis instance during {@link #start() startup} to allow early validation of
* connection factory configuration. Eager initialization also prevents blocking connect while using reactive API and
* is recommended for reactive API usage.
*
* @return {@link true} if the shared connection is initialized upon {@link #afterPropertiesSet()}.
* @return {@link true} if the shared connection is initialized upon {@link #start()}.
* @since 2.2
* @see #start()
*/
public boolean getEagerInitialization() {
return this.eagerInitialization;
Expand Down Expand Up @@ -795,6 +798,70 @@ public RedisClusterConfiguration getClusterConfiguration() {
return isClusterAware() ? (RedisClusterConfiguration) this.configuration : null;
}

@Override
public int getPhase() {
return this.phase;
}

/**
* Specify the lifecycle phase for pausing and resuming this executor. The default is {@code 0}.
*
* @since 3.2
* @see SmartLifecycle#getPhase()
*/
public void setPhase(int phase) {
this.phase = phase;
}

/**
* @since 3.3
*/
@Override
public boolean isAutoStartup() {
return this.autoStartup;
}

/**
* Configure if this Lifecycle connection factory should get started automatically by the container at the time that
* the containing ApplicationContext gets refreshed.
* <p>
* This connection factory defaults to early auto-startup during {@link #afterPropertiesSet()} and can potentially
* create Redis connections early on in the lifecycle. See {@link #setEarlyStartup(boolean)} for delaying connection
* creation to the ApplicationContext refresh if auto-startup is enabled.
*
* @param autoStartup {@literal true} to automatically {@link #start()} the connection factory; {@literal false}
* otherwise.
* @since 3.3
* @see #setEarlyStartup(boolean)
* @see #start()
*/
public void setAutoStartup(boolean autoStartup) {
this.autoStartup = autoStartup;
}

/**
* @return whether to {@link #start()} the component during {@link #afterPropertiesSet()}.
* @since 3.3
*/
public boolean isEarlyStartup() {
return this.earlyStartup;
}

/**
* Configure if this InitializingBean's component Lifecycle should get started early by {@link #afterPropertiesSet()}
* at the time that the bean is initialized. The component defaults to auto-startup.
* <p>
* This method is related to {@link #setAutoStartup(boolean) auto-startup} and can be used to delay Redis client
* startup until the ApplicationContext refresh. Disabling early startup does not disable auto-startup.
*
* @param earlyStartup {@literal true} to early {@link #start()} the component; {@literal false} otherwise.
* @since 3.3
* @see #setAutoStartup(boolean)
*/
public void setEarlyStartup(boolean earlyStartup) {
this.earlyStartup = earlyStartup;
}

/**
* Specifies if pipelined results should be converted to the expected data type. If {@code false}, results of
* {@link LettuceConnection#closePipeline()} and {LettuceConnection#exec()} will be of the type returned by the
Expand Down Expand Up @@ -924,21 +991,6 @@ public void stop() {
state.set(State.STOPPED);
}

@Override
public int getPhase() {
return this.phase;
}

/**
* Specify the lifecycle phase for pausing and resuming this executor. The default is {@code 0}.
*
* @since 3.2
* @see SmartLifecycle#getPhase()
*/
public void setPhase(int phase) {
this.phase = phase;
}

@Override
public boolean isRunning() {
return State.STARTED.equals(this.state.get());
Expand All @@ -947,7 +999,7 @@ public boolean isRunning() {
@Override
public void afterPropertiesSet() {

if (isAutoStartup()) {
if (isEarlyStartup()) {
start();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.junit.jupiter.api.Test;

import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
Expand Down Expand Up @@ -337,6 +338,21 @@ void afterPropertiesTriggersConnectionInitialization() {
assertThat(connectionFactory.isRunning()).isTrue();
}

@Test // GH-2866
void earlyStartupDoesNotStartConnectionFactory() {

JedisConnectionFactory connectionFactory = new JedisConnectionFactory(new JedisPoolConfig());

connectionFactory.setEarlyStartup(false);
connectionFactory.afterPropertiesSet();

assertThat(connectionFactory.isEarlyStartup()).isFalse();
assertThat(connectionFactory.isAutoStartup()).isTrue();
assertThat(connectionFactory.isRunning()).isFalse();

assertThat(ReflectionTestUtils.getField(connectionFactory, "pool")).isNull();
}

private JedisConnectionFactory initSpyedConnectionFactory(RedisSentinelConfiguration sentinelConfiguration,
@Nullable JedisPoolConfig poolConfig) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1260,6 +1260,22 @@ void createRedisConfigurationWithValidRedisUriString() {
.extracting(RedisStandaloneConfiguration::getPort).isEqualTo(6789);
}

@Test // GH-2866
void earlyStartupDoesNotStartConnectionFactory() {

LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(new RedisStandaloneConfiguration(),
LettuceTestClientConfiguration.defaultConfiguration());
connectionFactory.setEarlyStartup(false);
connectionFactory.afterPropertiesSet();

assertThat(connectionFactory.isEarlyStartup()).isFalse();
assertThat(connectionFactory.isAutoStartup()).isTrue();
assertThat(connectionFactory.isRunning()).isFalse();

AbstractRedisClient client = (AbstractRedisClient) getField(connectionFactory, "client");
assertThat(client).isNull();
}

static class CustomRedisConfiguration implements RedisConfiguration, WithHostAndPort {

private String hostName;
Expand Down