Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open DefaultLifecycleProcessor for extension #34655

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package org.springframework.context.support;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.Lifecycle;

import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.*;

/**
* An example extension of Spring's {@link DefaultLifecycleProcessor}.
* </p>
* All Lifecycle beans of a same phase start _at once_ rather than sequentially.
* Phases still only complete after every bean has returned from start().
* </p>
* A start timeout can be specified for each phase; the default timeout is 10 seconds.
* If any timeout is exceeded, an exception is thrown and the application context refresh() fails.
* </p>
* @author Francis Lalonde
*/
public class ConcurrentLifecycleProcessor extends DefaultLifecycleProcessor {

private final Map<Integer, Long> timeoutsForStartPhases = new ConcurrentHashMap<>();

private final long timeoutPerStartPhase = 10000;

public void setTimeoutsForStartPhases(Map<Integer, Long> timeoutsForShutdownPhases) {
this.timeoutsForStartPhases.putAll(timeoutsForShutdownPhases);
}

protected long determineStartTimeout(int phase) {
Long timeout = this.timeoutsForStartPhases.get(phase);
return (timeout != null ? timeout : this.timeoutPerStartPhase);
}

@Override
protected void startBeans(boolean autoStartupOnly) {
Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
Map<Integer, ConcurentLifecycleGroup> phases = new TreeMap<>();

lifecycleBeans.forEach((beanName, bean) -> {
if (!autoStartupOnly || isAutoStartupCandidate(beanName, bean)) {
int startupPhase = getPhase(bean);
phases.computeIfAbsent(startupPhase,
phase -> new ConcurentLifecycleGroup(phase, determineStartTimeout(phase), determineStopTimeout(phase), lifecycleBeans, autoStartupOnly)
).add(beanName, bean);
}
});

if (!phases.isEmpty()) {
phases.values().forEach(ConcurentLifecycleGroup::start);
}
}

protected class ConcurentLifecycleGroup extends LifecycleGroup {

private final Log logger = LogFactory.getLog(getClass());

private final long startTimeout;

public ConcurentLifecycleGroup(int phase, long startTimeout, long stopTimeout, Map<String, ? extends Lifecycle> lifecycleBeans, boolean autoStartupOnly) {
super(phase, stopTimeout, lifecycleBeans, autoStartupOnly);
this.startTimeout = startTimeout;
}

@Override
public void start() {
if (this.members.isEmpty()) {
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Starting beans in phase " + phase);
}

List<CompletableFuture<Void>> starting = members.stream()
.map(member -> CompletableFuture.runAsync(() -> doStart(lifecycleBeans, member.name(), autoStartupOnly)))
.toList();

try {
CompletableFuture.allOf(starting.toArray(CompletableFuture<?>[]::new))
.get(startTimeout, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
throw new IllegalStateException("Timeout exceeded starting beans in phase " + this.phase, e);
} catch (InterruptedException | ExecutionException e) {
throw new IllegalStateException("Error starting beans in phase " + this.phase, e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ public void setTimeoutPerShutdownPhase(long timeoutPerShutdownPhase) {
this.timeoutPerShutdownPhase = timeoutPerShutdownPhase;
}

private long determineTimeout(int phase) {
protected long determineStopTimeout(int phase) {
Long timeout = this.timeoutsForShutdownPhases.get(phase);
return (timeout != null ? timeout : this.timeoutPerShutdownPhase);
}
Expand All @@ -182,7 +182,7 @@ public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = clbf;
}

private ConfigurableListableBeanFactory getBeanFactory() {
protected ConfigurableListableBeanFactory getBeanFactory() {
ConfigurableListableBeanFactory beanFactory = this.beanFactory;
Assert.state(beanFactory != null, "No BeanFactory available");
return beanFactory;
Expand Down Expand Up @@ -275,15 +275,15 @@ void restartAfterStop() {
}
}

private void startBeans(boolean autoStartupOnly) {
protected void startBeans(boolean autoStartupOnly) {
Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
Map<Integer, LifecycleGroup> phases = new TreeMap<>();

lifecycleBeans.forEach((beanName, bean) -> {
if (!autoStartupOnly || isAutoStartupCandidate(beanName, bean)) {
int startupPhase = getPhase(bean);
phases.computeIfAbsent(startupPhase,
phase -> new LifecycleGroup(phase, determineTimeout(phase), lifecycleBeans, autoStartupOnly)
phase -> new LifecycleGroup(phase, determineStopTimeout(phase), lifecycleBeans, autoStartupOnly)
).add(beanName, bean);
}
});
Expand All @@ -293,7 +293,7 @@ private void startBeans(boolean autoStartupOnly) {
}
}

private boolean isAutoStartupCandidate(String beanName, Lifecycle bean) {
protected boolean isAutoStartupCandidate(String beanName, Lifecycle bean) {
Set<String> stoppedBeans = this.stoppedBeans;
return (stoppedBeans != null ? stoppedBeans.contains(beanName) :
(bean instanceof SmartLifecycle smartLifecycle && smartLifecycle.isAutoStartup()));
Expand All @@ -305,7 +305,7 @@ private boolean isAutoStartupCandidate(String beanName, Lifecycle bean) {
* @param lifecycleBeans a Map with bean name as key and Lifecycle instance as value
* @param beanName the name of the bean to start
*/
private void doStart(Map<String, ? extends Lifecycle> lifecycleBeans, String beanName, boolean autoStartupOnly) {
protected void doStart(Map<String, ? extends Lifecycle> lifecycleBeans, String beanName, boolean autoStartupOnly) {
Lifecycle bean = lifecycleBeans.remove(beanName);
if (bean != null && bean != this) {
String[] dependenciesForBean = getBeanFactory().getDependenciesForBean(beanName);
Expand Down Expand Up @@ -342,7 +342,7 @@ private void stopBeans() {
lifecycleBeans.forEach((beanName, bean) -> {
int shutdownPhase = getPhase(bean);
phases.computeIfAbsent(shutdownPhase,
phase -> new LifecycleGroup(phase, determineTimeout(phase), lifecycleBeans, false)
phase -> new LifecycleGroup(phase, determineStopTimeout(phase), lifecycleBeans, false)
).add(beanName, bean);
});

Expand All @@ -357,7 +357,7 @@ private void stopBeans() {
* @param lifecycleBeans a Map with bean name as key and Lifecycle instance as value
* @param beanName the name of the bean to stop
*/
private void doStop(Map<String, ? extends Lifecycle> lifecycleBeans, final String beanName,
protected void doStop(Map<String, ? extends Lifecycle> lifecycleBeans, final String beanName,
final CountDownLatch latch, final Set<String> countDownBeanNames) {

Lifecycle bean = lifecycleBeans.remove(beanName);
Expand Down Expand Up @@ -466,17 +466,17 @@ protected int getPhase(Lifecycle bean) {
* The group is expected to be created in an ad-hoc fashion and group members are
* expected to always have the same 'phase' value.
*/
private class LifecycleGroup {
protected class LifecycleGroup {

private final int phase;
protected final int phase;

private final long timeout;
protected final long timeout;

private final Map<String, ? extends Lifecycle> lifecycleBeans;
protected final Map<String, ? extends Lifecycle> lifecycleBeans;

private final boolean autoStartupOnly;
protected final boolean autoStartupOnly;

private final List<LifecycleGroupMember> members = new ArrayList<>();
protected final List<LifecycleGroupMember> members = new ArrayList<>();

private int smartMemberCount;

Expand Down Expand Up @@ -545,7 +545,7 @@ else if (member.bean instanceof SmartLifecycle) {
/**
* A simple record of a LifecycleGroup member.
*/
private record LifecycleGroupMember(String name, Lifecycle bean) {}
protected record LifecycleGroupMember(String name, Lifecycle bean) {}


/**
Expand Down