Use main thread in OnClassCondition if thread create/start fails

Previously, as a result of the changes made in de50cfa21e, an
application would fail to start on Google AppEngine as it prevents
the creation of new threads.

This commit updates OnClassCondition so that it falls back to
performing the work on the main thread when its unable to shift it
to a separate thread.

Closes gh-8584
pull/8813/merge
Andy Wilkinson 8 years ago
parent 2d930fd653
commit 035cbd91f4

@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.condition; package org.springframework.boot.autoconfigure.condition;
import java.security.AccessControlException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -90,53 +91,30 @@ class OnClassCondition extends SpringBootCondition
// additional thread seems to offer the best performance. More threads make // additional thread seems to offer the best performance. More threads make
// things worse // things worse
int split = autoConfigurationClasses.length / 2; int split = autoConfigurationClasses.length / 2;
GetOutcomesThread thread = new GetOutcomesThread(autoConfigurationClasses, 0, OutcomesResolver firstHalfResolver = createOutcomesResolver(
split, autoConfigurationMetadata); autoConfigurationClasses, 0, split, autoConfigurationMetadata);
thread.start(); OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(
ConditionOutcome[] secondHalf = getOutcomes(autoConfigurationClasses, split, autoConfigurationClasses, split, autoConfigurationClasses.length,
autoConfigurationClasses.length, autoConfigurationMetadata); autoConfigurationMetadata, this.beanClassLoader);
try { ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
thread.join(); ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
ConditionOutcome[] firstHalf = thread.getResult();
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length]; ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length); System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length); System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
return outcomes; return outcomes;
} }
private ConditionOutcome[] getOutcomes(final String[] autoConfigurationClasses, private OutcomesResolver createOutcomesResolver(String[] autoConfigurationClasses,
int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) { int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[end - start]; OutcomesResolver outcomesResolver = new StandardOutcomesResolver(
for (int i = start; i < end; i++) { autoConfigurationClasses, start, end, autoConfigurationMetadata,
String autoConfigurationClass = autoConfigurationClasses[i]; this.beanClassLoader);
Set<String> candidates = autoConfigurationMetadata
.getSet(autoConfigurationClass, "ConditionalOnClass");
if (candidates != null) {
outcomes[i - start] = getOutcome(candidates);
}
}
return outcomes;
}
private ConditionOutcome getOutcome(Set<String> candidates) {
try { try {
List<String> missing = getMatches(candidates, MatchType.MISSING, return new ThreadedOutcomesResolver(outcomesResolver);
this.beanClassLoader);
if (!missing.isEmpty()) {
return ConditionOutcome
.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class", "required classes")
.items(Style.QUOTE, missing));
}
} }
catch (Exception ex) { catch (AccessControlException ex) {
// We'll get another chance later return outcomesResolver;
} }
return null;
} }
@Override @Override
@ -262,7 +240,45 @@ class OnClassCondition extends SpringBootCondition
} }
private class GetOutcomesThread extends Thread { private interface OutcomesResolver {
ConditionOutcome[] resolveOutcomes();
}
private static final class ThreadedOutcomesResolver implements OutcomesResolver {
private final Thread thread;
private volatile ConditionOutcome[] outcomes;
private ThreadedOutcomesResolver(final OutcomesResolver outcomesResolver) {
this.thread = new Thread(new Runnable() {
@Override
public void run() {
ThreadedOutcomesResolver.this.outcomes = outcomesResolver
.resolveOutcomes();
}
});
this.thread.start();
}
@Override
public ConditionOutcome[] resolveOutcomes() {
try {
this.thread.join();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
return this.outcomes;
}
}
private final class StandardOutcomesResolver implements OutcomesResolver {
private final String[] autoConfigurationClasses; private final String[] autoConfigurationClasses;
@ -272,25 +288,55 @@ class OnClassCondition extends SpringBootCondition
private final AutoConfigurationMetadata autoConfigurationMetadata; private final AutoConfigurationMetadata autoConfigurationMetadata;
private ConditionOutcome[] outcomes; private final ClassLoader beanClassLoader;
GetOutcomesThread(String[] autoConfigurationClasses, int start, int end, private StandardOutcomesResolver(String[] autoConfigurationClasses, int start,
AutoConfigurationMetadata autoConfigurationMetadata) { int end, AutoConfigurationMetadata autoConfigurationMetadata,
ClassLoader beanClassLoader) {
this.autoConfigurationClasses = autoConfigurationClasses; this.autoConfigurationClasses = autoConfigurationClasses;
this.start = start; this.start = start;
this.end = end; this.end = end;
this.autoConfigurationMetadata = autoConfigurationMetadata; this.autoConfigurationMetadata = autoConfigurationMetadata;
this.beanClassLoader = beanClassLoader;
} }
@Override @Override
public void run() { public ConditionOutcome[] resolveOutcomes() {
this.outcomes = getOutcomes(this.autoConfigurationClasses, this.start, return getOutcomes(this.autoConfigurationClasses, this.start, this.end,
this.end, this.autoConfigurationMetadata); this.autoConfigurationMetadata);
} }
public ConditionOutcome[] getResult() { private ConditionOutcome[] getOutcomes(final String[] autoConfigurationClasses,
return this.outcomes; int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
for (int i = start; i < end; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
Set<String> candidates = autoConfigurationMetadata
.getSet(autoConfigurationClass, "ConditionalOnClass");
if (candidates != null) {
outcomes[i - start] = getOutcome(candidates);
}
}
return outcomes;
}
private ConditionOutcome getOutcome(Set<String> candidates) {
try {
List<String> missing = getMatches(candidates, MatchType.MISSING,
this.beanClassLoader);
if (!missing.isEmpty()) {
return ConditionOutcome.noMatch(
ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class", "required classes")
.items(Style.QUOTE, missing));
}
}
catch (Exception ex) {
// We'll get another chance later
}
return null;
} }
} }
} }

Loading…
Cancel
Save