Commit 035cbd91 authored by Andy Wilkinson's avatar Andy Wilkinson

Use main thread in OnClassCondition if thread create/start fails

Previously, as a result of the changes made in de50cfa2, 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
parent 2d930fd6
...@@ -16,6 +16,7 @@ ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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;
} }
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment