diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java index 17e76d0190..55ca08ed9b 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java @@ -36,7 +36,10 @@ import org.springframework.lang.Nullable; * Base class for setting up a {@link java.util.concurrent.ExecutorService} * (typically a {@link java.util.concurrent.ThreadPoolExecutor} or * {@link java.util.concurrent.ScheduledThreadPoolExecutor}). - * Defines common configuration settings and common lifecycle handling. + * + *

Defines common configuration settings and common lifecycle handling, + * inheriting thread customization options (name, priority, etc) from + * {@link org.springframework.util.CustomizableThreadCreator}. * * @author Juergen Hoeller * @since 3.0 @@ -199,8 +202,7 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac /** - * Calls {@code shutdown} when the BeanFactory destroys - * the task executor instance. + * Calls {@code shutdown} when the BeanFactory destroys the executor instance. * @see #shutdown() */ @Override diff --git a/spring-context/src/test/java/org/springframework/context/support/GenericApplicationContextTests.java b/spring-context/src/test/java/org/springframework/context/support/GenericApplicationContextTests.java index f90a428128..dbcbb90cff 100644 --- a/spring-context/src/test/java/org/springframework/context/support/GenericApplicationContextTests.java +++ b/spring-context/src/test/java/org/springframework/context/support/GenericApplicationContextTests.java @@ -76,11 +76,13 @@ class GenericApplicationContextTests { private final GenericApplicationContext context = new GenericApplicationContext(); + @AfterEach void closeContext() { context.close(); } + @Test void getBeanForClass() { context.registerBeanDefinition("testBean", new RootBeanDefinition(String.class)); @@ -91,7 +93,7 @@ class GenericApplicationContextTests { assertThat(context.getBean(CharSequence.class)).isSameAs(context.getBean("testBean")); assertThatExceptionOfType(NoUniqueBeanDefinitionException.class) - .isThrownBy(() -> context.getBean(Object.class)); + .isThrownBy(() -> context.getBean(Object.class)); } @Test @@ -509,6 +511,7 @@ class GenericApplicationContextTests { context.close(); } + private MergedBeanDefinitionPostProcessor registerMockMergedBeanDefinitionPostProcessor(GenericApplicationContext context) { MergedBeanDefinitionPostProcessor bpp = mock(); context.registerBeanDefinition("bpp", BeanDefinitionBuilder.rootBeanDefinition( @@ -563,9 +566,9 @@ class GenericApplicationContextTests { public void setCounter(Integer counter) { this.counter = counter; } - } + static class TestAotFactoryBean extends AbstractFactoryBean { TestAotFactoryBean() { @@ -584,6 +587,7 @@ class GenericApplicationContextTests { } } + static class PingPongProtocolResolver implements ProtocolResolver { @Override diff --git a/spring-jms/src/test/java/org/springframework/jms/listener/DefaultMessageListenerContainerTests.java b/spring-jms/src/test/java/org/springframework/jms/listener/DefaultMessageListenerContainerTests.java index 4315cce0a4..837d6ea69b 100644 --- a/spring-jms/src/test/java/org/springframework/jms/listener/DefaultMessageListenerContainerTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/listener/DefaultMessageListenerContainerTests.java @@ -29,6 +29,7 @@ import org.mockito.stubbing.Answer; import org.springframework.util.backoff.BackOff; import org.springframework.util.backoff.BackOffExecution; +import org.springframework.util.backoff.FixedBackOff; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -38,6 +39,7 @@ import static org.mockito.Mockito.verify; /** * @author Stephane Nicoll + * @author Juergen Hoeller */ public class DefaultMessageListenerContainerTests { @@ -58,6 +60,8 @@ public class DefaultMessageListenerContainerTests { assertThat(container.isRunning()).isFalse(); verify(backOff).start(); verify(execution).nextBackOff(); + + container.destroy(); } @Test @@ -75,6 +79,8 @@ public class DefaultMessageListenerContainerTests { assertThat(container.isRunning()).isFalse(); verify(backOff).start(); verify(execution, times(2)).nextBackOff(); + + container.destroy(); } @Test @@ -92,27 +98,65 @@ public class DefaultMessageListenerContainerTests { assertThat(container.isRunning()).isTrue(); verify(backOff).start(); verify(execution, times(1)).nextBackOff(); // only on attempt as the second one lead to a recovery + + container.destroy(); } @Test - public void runnableIsInvokedEvenIfContainerIsNotRunning() throws InterruptedException { + public void stopAndRestart() { + DefaultMessageListenerContainer container = createRunningContainer(); + container.stop(); + + container.start(); + container.destroy(); + } + + @Test + public void stopWithCallbackAndRestart() throws InterruptedException { + DefaultMessageListenerContainer container = createRunningContainer(); + + TestRunnable stopCallback = new TestRunnable(); + container.stop(stopCallback); + stopCallback.waitForCompletion(); + + container.start(); + container.destroy(); + } + + @Test + public void stopCallbackIsInvokedEvenIfContainerIsNotRunning() throws InterruptedException { DefaultMessageListenerContainer container = createRunningContainer(); container.stop(); // container is stopped but should nevertheless invoke the runnable argument - TestRunnable runnable2 = new TestRunnable(); - container.stop(runnable2); - runnable2.waitForCompletion(); + TestRunnable stopCallback = new TestRunnable(); + container.stop(stopCallback); + stopCallback.waitForCompletion(); + + container.destroy(); } private DefaultMessageListenerContainer createRunningContainer() { DefaultMessageListenerContainer container = createContainer(createSuccessfulConnectionFactory()); + container.setCacheLevel(DefaultMessageListenerContainer.CACHE_CONNECTION); + container.setBackOff(new FixedBackOff(100, 1)); container.afterPropertiesSet(); container.start(); return container; } + private ConnectionFactory createSuccessfulConnectionFactory() { + try { + ConnectionFactory connectionFactory = mock(); + given(connectionFactory.createConnection()).willReturn(mock()); + return connectionFactory; + } + catch (JMSException ex) { + throw new IllegalStateException(ex); // never happen + } + } + private DefaultMessageListenerContainer createContainer(ConnectionFactory connectionFactory) { Destination destination = new Destination() {}; @@ -159,17 +203,6 @@ public class DefaultMessageListenerContainerTests { } } - private ConnectionFactory createSuccessfulConnectionFactory() { - try { - ConnectionFactory connectionFactory = mock(); - given(connectionFactory.createConnection()).willReturn(mock()); - return connectionFactory; - } - catch (JMSException ex) { - throw new IllegalStateException(ex); // never happen - } - } - private static class TestRunnable implements Runnable { @@ -181,7 +214,7 @@ public class DefaultMessageListenerContainerTests { } public void waitForCompletion() throws InterruptedException { - this.countDownLatch.await(2, TimeUnit.SECONDS); + this.countDownLatch.await(1, TimeUnit.SECONDS); assertThat(this.countDownLatch.getCount()).as("callback was not invoked").isEqualTo(0); } } diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java index c0a1805a60..e1e95bae23 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java @@ -1083,10 +1083,11 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init * @param ex the throwable to try to unwrap */ private Throwable unwrapIfResourceCleanupFailure(Throwable ex) { - if (ex instanceof RuntimeException && - ex.getCause() != null && - ex.getMessage().startsWith("Async resource cleanup failed")) { - return ex.getCause(); + if (ex instanceof RuntimeException && ex.getCause() != null) { + String msg = ex.getMessage(); + if (msg != null && msg.startsWith("Async resource cleanup failed")) { + return ex.getCause(); + } } return ex; }