Skip to content

Commit 0ff200b

Browse files
committed
Trigger cancellation on context close for non-managed objects only
Specifically for prototype/scoped beans and FactoryBean-exposed objects. Closes gh-33009
1 parent 261dac8 commit 0ff200b

File tree

3 files changed

+195
-32
lines changed

3 files changed

+195
-32
lines changed

spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java

+27-15
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.springframework.beans.factory.ListableBeanFactory;
4545
import org.springframework.beans.factory.SmartInitializingSingleton;
4646
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
47+
import org.springframework.beans.factory.config.SingletonBeanRegistry;
4748
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
4849
import org.springframework.beans.factory.support.RootBeanDefinition;
4950
import org.springframework.context.ApplicationContext;
@@ -155,6 +156,8 @@ public class ScheduledAnnotationBeanPostProcessor
155156

156157
private final Map<Object, List<Runnable>> reactiveSubscriptions = new IdentityHashMap<>(16);
157158

159+
private final Set<Object> manualCancellationOnContextClose = Collections.newSetFromMap(new IdentityHashMap<>(16));
160+
158161

159162
/**
160163
* Create a default {@code ScheduledAnnotationBeanPostProcessor}.
@@ -305,6 +308,12 @@ public Object postProcessAfterInitialization(Object bean, String beanName) {
305308
logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
306309
"': " + annotatedMethods);
307310
}
311+
if ((this.beanFactory != null && !this.beanFactory.isSingleton(beanName)) ||
312+
(this.beanFactory instanceof SingletonBeanRegistry sbr && sbr.containsSingleton(beanName))) {
313+
// Either a prototype/scoped bean or a FactoryBean with a pre-existing managed singleton
314+
// -> trigger manual cancellation when ContextClosedEvent comes in
315+
this.manualCancellationOnContextClose.add(bean);
316+
}
308317
}
309318
}
310319
return bean;
@@ -595,6 +604,18 @@ public Set<ScheduledTask> getScheduledTasks() {
595604

596605
@Override
597606
public void postProcessBeforeDestruction(Object bean, String beanName) {
607+
cancelScheduledTasks(bean);
608+
this.manualCancellationOnContextClose.remove(bean);
609+
}
610+
611+
@Override
612+
public boolean requiresDestruction(Object bean) {
613+
synchronized (this.scheduledTasks) {
614+
return (this.scheduledTasks.containsKey(bean) || this.reactiveSubscriptions.containsKey(bean));
615+
}
616+
}
617+
618+
private void cancelScheduledTasks(Object bean) {
598619
Set<ScheduledTask> tasks;
599620
List<Runnable> liveSubscriptions;
600621
synchronized (this.scheduledTasks) {
@@ -613,13 +634,6 @@ public void postProcessBeforeDestruction(Object bean, String beanName) {
613634
}
614635
}
615636

616-
@Override
617-
public boolean requiresDestruction(Object bean) {
618-
synchronized (this.scheduledTasks) {
619-
return (this.scheduledTasks.containsKey(bean) || this.reactiveSubscriptions.containsKey(bean));
620-
}
621-
}
622-
623637
@Override
624638
public void destroy() {
625639
synchronized (this.scheduledTasks) {
@@ -636,7 +650,10 @@ public void destroy() {
636650
liveSubscription.run(); // equivalent to cancelling the subscription
637651
}
638652
}
653+
this.reactiveSubscriptions.clear();
654+
this.manualCancellationOnContextClose.clear();
639655
}
656+
640657
this.registrar.destroy();
641658
if (this.localScheduler != null) {
642659
this.localScheduler.destroy();
@@ -659,15 +676,10 @@ public void onApplicationEvent(ApplicationContextEvent event) {
659676
finishRegistration();
660677
}
661678
else if (event instanceof ContextClosedEvent) {
662-
synchronized (this.scheduledTasks) {
663-
Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values();
664-
for (Set<ScheduledTask> tasks : allTasks) {
665-
for (ScheduledTask task : tasks) {
666-
// At this early point, let in-progress tasks complete still
667-
task.cancel(false);
668-
}
669-
}
679+
for (Object bean : this.manualCancellationOnContextClose) {
680+
cancelScheduledTasks(bean);
670681
}
682+
this.manualCancellationOnContextClose.clear();
671683
}
672684
}
673685
}

0 commit comments

Comments
 (0)