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
@ConditionalOnCheckpointRestore
HikariCheckpointRestoreLifecycle hikariCheckpointRestoreLifecycle(HikariDataSource hikariDataSource) {
HikariCheckpointRestoreLifecycle hikariCheckpointRestoreLifecycle(DataSource hikariDataSource) {
return new HikariCheckpointRestoreLifecycle(hikariDataSource);
}

@ -22,12 +22,15 @@ import com.zaxxer.hikari.HikariDataSource;
import org.assertj.core.api.InstanceOfAssertFactories;
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.jdbc.HikariCheckpointRestoreLifecycle;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.ClassPathOverrides;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DelegatingDataSource;
import static org.assertj.core.api.Assertions.assertThat;
@ -131,6 +134,14 @@ class HikariDataSourceConfigurationTests {
.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
void whenCheckpointRestoreIsNotAvailableHikariAutoConfigDoesNotRegisterLifecycleBean() {
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.function.Function;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariConfigMXBean;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariPoolMXBean;
@ -71,10 +73,12 @@ public class HikariCheckpointRestoreLifecycle implements Lifecycle {
/**
* 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
*/
public HikariCheckpointRestoreLifecycle(HikariDataSource dataSource) {
public HikariCheckpointRestoreLifecycle(DataSource dataSource) {
this.dataSource = DataSourceUnwrapper.unwrap(dataSource, HikariConfigMXBean.class, HikariDataSource.class);
this.hasOpenConnections = (pool) -> {
ThreadPoolExecutor closeConnectionExecutor = (ThreadPoolExecutor) ReflectionUtils
@ -86,7 +90,7 @@ public class HikariCheckpointRestoreLifecycle implements Lifecycle {
@Override
public void start() {
if (this.dataSource.isRunning()) {
if (this.dataSource == null || this.dataSource.isRunning()) {
return;
}
Assert.state(!this.dataSource.isClosed(), "DataSource has been closed and cannot be restarted");
@ -98,7 +102,7 @@ public class HikariCheckpointRestoreLifecycle implements Lifecycle {
@Override
public void stop() {
if (!this.dataSource.isRunning()) {
if (this.dataSource == null || !this.dataSource.isRunning()) {
return;
}
if (this.dataSource.isAllowPoolSuspension()) {
@ -143,7 +147,7 @@ public class HikariCheckpointRestoreLifecycle implements Lifecycle {
@Override
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 javax.sql.DataSource;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
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.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link HikariCheckpointRestoreLifecycle}.
@ -82,4 +85,22 @@ class HikariCheckpointRestoreLifecycleTests {
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