From 035cbd91f4e77873096f33dfb22af4479e2173b6 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 10 Apr 2017 14:23:24 +0100 Subject: [PATCH] Use main thread in OnClassCondition if thread create/start fails Previously, as a result of the changes made in de50cfa21e5f, 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 --- .../condition/OnClassCondition.java | 138 ++++++++++++------ 1 file changed, 92 insertions(+), 46 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java index c97cf6757c..f4ba5c8e7c 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.condition; +import java.security.AccessControlException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -90,53 +91,30 @@ class OnClassCondition extends SpringBootCondition // additional thread seems to offer the best performance. More threads make // things worse int split = autoConfigurationClasses.length / 2; - GetOutcomesThread thread = new GetOutcomesThread(autoConfigurationClasses, 0, - split, autoConfigurationMetadata); - thread.start(); - ConditionOutcome[] secondHalf = getOutcomes(autoConfigurationClasses, split, - autoConfigurationClasses.length, autoConfigurationMetadata); - try { - thread.join(); - } - catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - ConditionOutcome[] firstHalf = thread.getResult(); + OutcomesResolver firstHalfResolver = createOutcomesResolver( + autoConfigurationClasses, 0, split, autoConfigurationMetadata); + OutcomesResolver secondHalfResolver = new StandardOutcomesResolver( + autoConfigurationClasses, split, autoConfigurationClasses.length, + autoConfigurationMetadata, this.beanClassLoader); + ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes(); + ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes(); ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length]; System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length); System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length); return outcomes; } - private ConditionOutcome[] getOutcomes(final String[] autoConfigurationClasses, + private OutcomesResolver createOutcomesResolver(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) { - ConditionOutcome[] outcomes = new ConditionOutcome[end - start]; - for (int i = start; i < end; i++) { - String autoConfigurationClass = autoConfigurationClasses[i]; - Set candidates = autoConfigurationMetadata - .getSet(autoConfigurationClass, "ConditionalOnClass"); - if (candidates != null) { - outcomes[i - start] = getOutcome(candidates); - } - } - return outcomes; - } - - private ConditionOutcome getOutcome(Set candidates) { + OutcomesResolver outcomesResolver = new StandardOutcomesResolver( + autoConfigurationClasses, start, end, autoConfigurationMetadata, + this.beanClassLoader); try { - List 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)); - } + return new ThreadedOutcomesResolver(outcomesResolver); } - catch (Exception ex) { - // We'll get another chance later + catch (AccessControlException ex) { + return outcomesResolver; } - return null; } @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; @@ -272,25 +288,55 @@ class OnClassCondition extends SpringBootCondition private final AutoConfigurationMetadata autoConfigurationMetadata; - private ConditionOutcome[] outcomes; + private final ClassLoader beanClassLoader; - GetOutcomesThread(String[] autoConfigurationClasses, int start, int end, - AutoConfigurationMetadata autoConfigurationMetadata) { + private StandardOutcomesResolver(String[] autoConfigurationClasses, int start, + int end, AutoConfigurationMetadata autoConfigurationMetadata, + ClassLoader beanClassLoader) { this.autoConfigurationClasses = autoConfigurationClasses; this.start = start; this.end = end; this.autoConfigurationMetadata = autoConfigurationMetadata; + this.beanClassLoader = beanClassLoader; } @Override - public void run() { - this.outcomes = getOutcomes(this.autoConfigurationClasses, this.start, - this.end, this.autoConfigurationMetadata); + public ConditionOutcome[] resolveOutcomes() { + return getOutcomes(this.autoConfigurationClasses, this.start, this.end, + this.autoConfigurationMetadata); } - public ConditionOutcome[] getResult() { - return this.outcomes; + private ConditionOutcome[] getOutcomes(final String[] autoConfigurationClasses, + int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) { + ConditionOutcome[] outcomes = new ConditionOutcome[end - start]; + for (int i = start; i < end; i++) { + String autoConfigurationClass = autoConfigurationClasses[i]; + Set candidates = autoConfigurationMetadata + .getSet(autoConfigurationClass, "ConditionalOnClass"); + if (candidates != null) { + outcomes[i - start] = getOutcome(candidates); + } + } + return outcomes; + } + + private ConditionOutcome getOutcome(Set candidates) { + try { + List 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; } } + }