Wrap ThreadPoolTaskScheduler with TaskSchedulerWrapper.
We now expose a wrapped ThreadPoolTaskScheduler to not expose the bean to the context directly to avoid interference with other components requiring a properly configured ThreadPoolTaskScheduler for e.g. AtAsync usage. We remove the risk of accidentally exhausting the pool and in consequence, ensuring the pool has sufficient capacity to handle Vault background jobs. Resolves gh-623.
This commit is contained in:
@@ -175,24 +175,22 @@ public abstract class AbstractVaultConfiguration implements ApplicationContextAw
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link ThreadPoolTaskScheduler} used by
|
||||
* {@link LifecycleAwareSessionManager} and
|
||||
* {@link org.springframework.vault.core.lease.SecretLeaseContainer}. Annotate with
|
||||
* {@link Bean} in case you want to expose a {@link ThreadPoolTaskScheduler} instance
|
||||
* to the {@link org.springframework.context.ApplicationContext}. This might be useful
|
||||
* to supply managed executor instances or {@link ThreadPoolTaskScheduler}s using a
|
||||
* queue/pooled threads.
|
||||
* @return the {@link ThreadPoolTaskScheduler} to use. Must not be {@literal null}.
|
||||
* Create a {@link TaskSchedulerWrapper} used by {@link LifecycleAwareSessionManager}
|
||||
* and {@link org.springframework.vault.core.lease.SecretLeaseContainer} wrapping
|
||||
* {@link ThreadPoolTaskScheduler}. Subclasses may override this method to reuse a
|
||||
* different/existing scheduler.
|
||||
* @return the {@link TaskSchedulerWrapper} to use. Must not be {@literal null}.
|
||||
* @see TaskSchedulerWrapper#fromInstance(ThreadPoolTaskScheduler)
|
||||
*/
|
||||
@Bean("vaultThreadPoolTaskScheduler")
|
||||
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
|
||||
public TaskSchedulerWrapper threadPoolTaskScheduler() {
|
||||
|
||||
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
|
||||
|
||||
threadPoolTaskScheduler.setThreadNamePrefix("spring-vault-ThreadPoolTaskScheduler-");
|
||||
threadPoolTaskScheduler.setDaemon(true);
|
||||
|
||||
return threadPoolTaskScheduler;
|
||||
return new TaskSchedulerWrapper(threadPoolTaskScheduler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,7 +267,11 @@ public abstract class AbstractVaultConfiguration implements ApplicationContextAw
|
||||
return getBeanFactory().getBean(RestTemplateFactory.class);
|
||||
}
|
||||
|
||||
BeanFactory getBeanFactory() {
|
||||
protected ThreadPoolTaskScheduler getVaultThreadPoolTaskScheduler() {
|
||||
return getBeanFactory().getBean("vaultThreadPoolTaskScheduler", TaskSchedulerWrapper.class).getTaskScheduler();
|
||||
}
|
||||
|
||||
protected BeanFactory getBeanFactory() {
|
||||
|
||||
Assert.state(this.applicationContext != null,
|
||||
"ApplicationContext must be set before accessing getBeanFactory()");
|
||||
@@ -277,10 +279,6 @@ public abstract class AbstractVaultConfiguration implements ApplicationContextAw
|
||||
return this.applicationContext;
|
||||
}
|
||||
|
||||
ThreadPoolTaskScheduler getVaultThreadPoolTaskScheduler() {
|
||||
return getBeanFactory().getBean("vaultThreadPoolTaskScheduler", ThreadPoolTaskScheduler.class);
|
||||
}
|
||||
|
||||
private ClientFactoryWrapper getClientFactoryWrapper() {
|
||||
return getBeanFactory().getBean("clientHttpRequestFactoryWrapper", ClientFactoryWrapper.class);
|
||||
}
|
||||
@@ -317,4 +315,65 @@ public abstract class AbstractVaultConfiguration implements ApplicationContextAw
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to keep {@link ThreadPoolTaskScheduler} local to Spring Vault and to not
|
||||
* expose the bean globally.
|
||||
*
|
||||
* @since 2.3.1
|
||||
*/
|
||||
public static class TaskSchedulerWrapper implements InitializingBean, DisposableBean {
|
||||
|
||||
private final ThreadPoolTaskScheduler taskScheduler;
|
||||
|
||||
private final boolean acceptAfterPropertiesSet;
|
||||
|
||||
private final boolean acceptDestroy;
|
||||
|
||||
public TaskSchedulerWrapper(ThreadPoolTaskScheduler taskScheduler) {
|
||||
this(taskScheduler, true, true);
|
||||
}
|
||||
|
||||
protected TaskSchedulerWrapper(ThreadPoolTaskScheduler taskScheduler, boolean acceptAfterPropertiesSet,
|
||||
boolean acceptDestroy) {
|
||||
|
||||
Assert.notNull(taskScheduler, "ThreadPoolTaskScheduler must not be null");
|
||||
|
||||
this.taskScheduler = taskScheduler;
|
||||
this.acceptAfterPropertiesSet = acceptAfterPropertiesSet;
|
||||
this.acceptDestroy = acceptDestroy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to adapt an existing {@link ThreadPoolTaskScheduler} bean
|
||||
* without calling lifecycle methods.
|
||||
* @param scheduler the actual {@code ThreadPoolTaskScheduler}.
|
||||
* @return the wrapper for the given {@link ThreadPoolTaskScheduler}.
|
||||
* @see #afterPropertiesSet()
|
||||
* @see #destroy()
|
||||
*/
|
||||
public static TaskSchedulerWrapper fromInstance(ThreadPoolTaskScheduler scheduler) {
|
||||
return new TaskSchedulerWrapper(scheduler, false, false);
|
||||
}
|
||||
|
||||
ThreadPoolTaskScheduler getTaskScheduler() {
|
||||
return this.taskScheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (acceptDestroy) {
|
||||
this.taskScheduler.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
|
||||
if (this.acceptAfterPropertiesSet) {
|
||||
this.taskScheduler.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,18 +21,21 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.vault.authentication.ClientAuthentication;
|
||||
import org.springframework.vault.authentication.TokenAuthentication;
|
||||
import org.springframework.vault.client.RestTemplateCustomizer;
|
||||
import org.springframework.vault.client.RestTemplateFactory;
|
||||
import org.springframework.vault.client.VaultEndpoint;
|
||||
import org.springframework.vault.config.AbstractVaultConfiguration.TaskSchedulerWrapper;
|
||||
import org.springframework.vault.core.VaultOperations;
|
||||
import org.springframework.vault.support.SslConfiguration;
|
||||
import org.springframework.vault.util.Settings;
|
||||
import org.springframework.vault.util.TestRestTemplateFactory;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link AbstractVaultConfiguration}.
|
||||
@@ -64,6 +67,33 @@ class AbstractVaultConfigurationUnitTests {
|
||||
assertThatExceptionOfType(CustomizedSignal.class).isThrownBy(() -> operations.opsForSys().health());
|
||||
}
|
||||
|
||||
@Test
|
||||
void taskSchedulerWrapperShouldCallLifecycleMethods() {
|
||||
|
||||
ThreadPoolTaskScheduler mock = mock(ThreadPoolTaskScheduler.class);
|
||||
|
||||
TaskSchedulerWrapper wrapper = new TaskSchedulerWrapper(mock);
|
||||
|
||||
wrapper.afterPropertiesSet();
|
||||
wrapper.destroy();
|
||||
|
||||
verify(mock).afterPropertiesSet();
|
||||
verify(mock).destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
void taskSchedulerWrapperFromInstanceShouldNotCallLifecycleMethods() {
|
||||
|
||||
ThreadPoolTaskScheduler mock = mock(ThreadPoolTaskScheduler.class);
|
||||
|
||||
TaskSchedulerWrapper wrapper = TaskSchedulerWrapper.fromInstance(mock);
|
||||
|
||||
wrapper.afterPropertiesSet();
|
||||
wrapper.destroy();
|
||||
|
||||
verifyNoInteractions(mock);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class RestTemplateCustomizerConfiguration extends AbstractVaultConfiguration {
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Mark Paluch;
|
||||
:self-docs-root: https://docs.spring.io/spring-vault/docs/{version}/
|
||||
:example-root: ../../../../spring-vault-core/src/test/java/org/springframework/vault/documentation
|
||||
|
||||
(C) 2016-2020 The original authors.
|
||||
(C) 2016-2021 The original authors.
|
||||
|
||||
NOTE: _Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically._
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
* Documentation of <<vault.core.secret-engines,how to use Vault secret backends>>.
|
||||
* Login credentials for Kubernetes and PCF authentication are reloaded for each login attempt.
|
||||
* `SecretLeaseContainer` publishes `SecretLeaseRotatedEvent` instead of `SecretLeaseExpiredEvent` and `SecretLeaseCreatedEvent` on successful secret rotation.
|
||||
* `AbstractVaultConfiguration.threadPoolTaskScheduler()` bean type changed to `TaskSchedulerWrapper` instead of `ThreadPoolTaskScheduler`.
|
||||
|
||||
[[new-features.2-2-0]]
|
||||
=== What's new in Spring Vault 2.2
|
||||
|
||||
Reference in New Issue
Block a user