Fix checkpoint-restore with replaced or wrapped HikariDataSource

Closes gh-37580
pull/37576/merge
Andy Wilkinson 1 year ago
parent ee9c74556d
commit ab06d10d64

@ -126,7 +126,7 @@ abstract class DataSourceConfiguration {
@Bean @Bean
@ConditionalOnCheckpointRestore @ConditionalOnCheckpointRestore
HikariCheckpointRestoreLifecycle hikariCheckpointRestoreLifecycle(HikariDataSource hikariDataSource) { HikariCheckpointRestoreLifecycle hikariCheckpointRestoreLifecycle(DataSource hikariDataSource) {
return new HikariCheckpointRestoreLifecycle(hikariDataSource); return new HikariCheckpointRestoreLifecycle(hikariDataSource);
} }

@ -22,12 +22,15 @@ import com.zaxxer.hikari.HikariDataSource;
import org.assertj.core.api.InstanceOfAssertFactories; import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.jdbc.HikariCheckpointRestoreLifecycle; import org.springframework.boot.jdbc.HikariCheckpointRestoreLifecycle;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.ClassPathOverrides; import org.springframework.boot.testsupport.classpath.ClassPathOverrides;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DelegatingDataSource;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -131,6 +134,14 @@ class HikariDataSourceConfigurationTests {
.run((context) -> assertThat(context).hasSingleBean(HikariCheckpointRestoreLifecycle.class)); .run((context) -> assertThat(context).hasSingleBean(HikariCheckpointRestoreLifecycle.class));
} }
@Test
@ClassPathOverrides("org.crac:crac:1.3.0")
void whenCheckpointRestoreIsAvailableAndDataSourceHasBeenWrappedHikariAutoConfigRegistersLifecycleBean() {
this.contextRunner.withUserConfiguration(DataSourceWrapperConfiguration.class)
.withPropertyValues("spring.datasource.type=" + HikariDataSource.class.getName())
.run((context) -> assertThat(context).hasSingleBean(HikariCheckpointRestoreLifecycle.class));
}
@Test @Test
void whenCheckpointRestoreIsNotAvailableHikariAutoConfigDoesNotRegisterLifecycleBean() { void whenCheckpointRestoreIsNotAvailableHikariAutoConfigDoesNotRegisterLifecycleBean() {
this.contextRunner.withPropertyValues("spring.datasource.type=" + HikariDataSource.class.getName()) this.contextRunner.withPropertyValues("spring.datasource.type=" + HikariDataSource.class.getName())
@ -147,4 +158,24 @@ class HikariDataSourceConfigurationTests {
} }
@Configuration(proxyBeanMethods = false)
static class DataSourceWrapperConfiguration {
@Bean
static BeanPostProcessor dataSourceWrapper() {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof DataSource dataSource) {
return new DelegatingDataSource(dataSource);
}
return bean;
}
};
}
}
} }

@ -25,6 +25,8 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.function.Function; import java.util.function.Function;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariConfigMXBean; import com.zaxxer.hikari.HikariConfigMXBean;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariPoolMXBean; import com.zaxxer.hikari.HikariPoolMXBean;
@ -71,10 +73,12 @@ public class HikariCheckpointRestoreLifecycle implements Lifecycle {
/** /**
* Creates a new {@code HikariCheckpointRestoreLifecycle} that will allow the given * Creates a new {@code HikariCheckpointRestoreLifecycle} that will allow the given
* {@code dataSource} to participate in checkpoint-restore. * {@code dataSource} to participate in checkpoint-restore. The {@code dataSource} is
* {@link DataSourceUnwrapper#unwrap unwrapped} to a {@link HikariDataSource}. If such
* unwrapping is not possible, the lifecycle will have no effect.
* @param dataSource the checkpoint-restore participant * @param dataSource the checkpoint-restore participant
*/ */
public HikariCheckpointRestoreLifecycle(HikariDataSource dataSource) { public HikariCheckpointRestoreLifecycle(DataSource dataSource) {
this.dataSource = DataSourceUnwrapper.unwrap(dataSource, HikariConfigMXBean.class, HikariDataSource.class); this.dataSource = DataSourceUnwrapper.unwrap(dataSource, HikariConfigMXBean.class, HikariDataSource.class);
this.hasOpenConnections = (pool) -> { this.hasOpenConnections = (pool) -> {
ThreadPoolExecutor closeConnectionExecutor = (ThreadPoolExecutor) ReflectionUtils ThreadPoolExecutor closeConnectionExecutor = (ThreadPoolExecutor) ReflectionUtils
@ -86,7 +90,7 @@ public class HikariCheckpointRestoreLifecycle implements Lifecycle {
@Override @Override
public void start() { public void start() {
if (this.dataSource.isRunning()) { if (this.dataSource == null || this.dataSource.isRunning()) {
return; return;
} }
Assert.state(!this.dataSource.isClosed(), "DataSource has been closed and cannot be restarted"); Assert.state(!this.dataSource.isClosed(), "DataSource has been closed and cannot be restarted");
@ -98,7 +102,7 @@ public class HikariCheckpointRestoreLifecycle implements Lifecycle {
@Override @Override
public void stop() { public void stop() {
if (!this.dataSource.isRunning()) { if (this.dataSource == null || !this.dataSource.isRunning()) {
return; return;
} }
if (this.dataSource.isAllowPoolSuspension()) { if (this.dataSource.isAllowPoolSuspension()) {
@ -143,7 +147,7 @@ public class HikariCheckpointRestoreLifecycle implements Lifecycle {
@Override @Override
public boolean isRunning() { public boolean isRunning() {
return this.dataSource.isRunning(); return this.dataSource != null && this.dataSource.isRunning();
} }
} }

@ -18,6 +18,8 @@ package org.springframework.boot.jdbc;
import java.util.UUID; import java.util.UUID;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -25,6 +27,7 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link HikariCheckpointRestoreLifecycle}. * Tests for {@link HikariCheckpointRestoreLifecycle}.
@ -82,4 +85,22 @@ class HikariCheckpointRestoreLifecycleTests {
assertThatExceptionOfType(RuntimeException.class).isThrownBy(this.lifecycle::start); assertThatExceptionOfType(RuntimeException.class).isThrownBy(this.lifecycle::start);
} }
@Test
void startHasNoEffectWhenDataSourceIsNotAHikariDataSource() {
HikariCheckpointRestoreLifecycle nonHikariLifecycle = new HikariCheckpointRestoreLifecycle(
mock(DataSource.class));
assertThat(nonHikariLifecycle.isRunning()).isFalse();
nonHikariLifecycle.start();
assertThat(nonHikariLifecycle.isRunning()).isFalse();
}
@Test
void stopHasNoEffectWhenDataSourceIsNotAHikariDataSource() {
HikariCheckpointRestoreLifecycle nonHikariLifecycle = new HikariCheckpointRestoreLifecycle(
mock(DataSource.class));
assertThat(nonHikariLifecycle.isRunning()).isFalse();
nonHikariLifecycle.stop();
assertThat(nonHikariLifecycle.isRunning()).isFalse();
}
} }

Loading…
Cancel
Save