Repository: camel Updated Branches: refs/heads/master 483fd8e12 -> c9bd1dd10
CAMEL-11225: Deadlock in component creation Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/c9bd1dd1 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/c9bd1dd1 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/c9bd1dd1 Branch: refs/heads/master Commit: c9bd1dd108921ddb7c7c2f9ab3b409a0e6c49d23 Parents: 483fd8e Author: lburgazzoli <lburgazz...@gmail.com> Authored: Tue May 2 15:44:48 2017 +0200 Committer: lburgazzoli <lburgazz...@gmail.com> Committed: Mon May 8 14:56:17 2017 +0200 ---------------------------------------------------------------------- .../apache/camel/impl/DefaultCamelContext.java | 53 ++++++++++++++++-- .../spring/CircularComponentCreationTest.java | 57 ++++++++++++++++++++ .../spring/CircularComponentInjectionTest.java | 40 ++++++++++++++ .../CircularComponentCreationComplexTest.xml | 35 ++++++++++++ .../CircularComponentCreationSimpleTest.xml | 35 ++++++++++++ .../CircularComponentInjectionTest-context.xml | 35 ++++++++++++ 6 files changed, 251 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/c9bd1dd1/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java b/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java index b53efc8..82644d5 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java +++ b/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java @@ -26,6 +26,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -302,7 +303,7 @@ public class DefaultCamelContext extends ServiceSupport implements ModelCamelCon private ReloadStrategy reloadStrategy; private final RuntimeCamelCatalog runtimeCamelCatalog = new DefaultRuntimeCamelCatalog(this, true); private SSLContextParameters sslContextParameters; - + private final ThreadLocal<Set<String>> componentsInCreation = ThreadLocal.withInitial(HashSet::new); /** * Creates the {@link CamelContext} using {@link JndiRegistry} as registry, * but will silently fallback and use {@link SimpleRegistry} if JNDI cannot be used. @@ -436,8 +437,19 @@ public class DefaultCamelContext extends ServiceSupport implements ModelCamelCon } public Component getComponent(String name, boolean autoCreateComponents, boolean autoStart) { - // atomic operation to get/create a component. Avoid global locks. - return components.computeIfAbsent(name, comp -> initComponent(name, autoCreateComponents, autoStart)); + // Check if the named component is already being created, that would mean + // that the initComponent has triggered a new getComponent + if (componentsInCreation.get().contains(name)) { + throw new IllegalStateException("Circular dependency detected, the component " + name + " is already being created"); + } + + try { + // atomic operation to get/create a component. Avoid global locks. + return components.computeIfAbsent(name, comp -> initComponent(name, autoCreateComponents, autoStart)); + } finally { + // cremove the reference to the component being created + componentsInCreation.get().remove(name); + } } /** @@ -450,12 +462,45 @@ public class DefaultCamelContext extends ServiceSupport implements ModelCamelCon if (log.isDebugEnabled()) { log.debug("Using ComponentResolver: {} to resolve component with name: {}", getComponentResolver(), name); } + + // Mark the component as being created so we can detect circular + // requests. + // + // In spring apps, the component resolver may trigger a new getComponent + // because of the underlying bean factory and as the endpoints are + // registered as singleton, the spring factory creates the bean + // and then check the type so the getComponent is always triggered. + // + // Simple circular dependency: + // + // <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring"> + // <route> + // <from id="twitter" uri="twitter://timeline/home?type=polling"/> + // <log message="Got ${body}"/> + // </route> + // </camelContext> + // + // Complex circular dependency: + // + // <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring"> + // <route> + // <from id="log" uri="seda:test"/> + // <to id="seda" uri="log:test"/> + // </route> + // </camelContext> + // + // This would freeze the app (lock or infinite loop). + // + // See https://issues.apache.org/jira/browse/CAMEL-11225 + componentsInCreation.get().add(name); + component = getComponentResolver().resolveComponent(name, this); if (component != null) { component.setCamelContext(this); postInitComponent(name, component); if (autoStart && (isStarted() || isStarting())) { - // If the component is looked up after the context is started, lets start it up. + // If the component is looked up after the context is started, + // lets start it up. if (component instanceof Service) { startService((Service)component); } http://git-wip-us.apache.org/repos/asf/camel/blob/c9bd1dd1/components/camel-spring/src/test/java/org/apache/camel/spring/CircularComponentCreationTest.java ---------------------------------------------------------------------- diff --git a/components/camel-spring/src/test/java/org/apache/camel/spring/CircularComponentCreationTest.java b/components/camel-spring/src/test/java/org/apache/camel/spring/CircularComponentCreationTest.java new file mode 100644 index 0000000..05f50ed --- /dev/null +++ b/components/camel-spring/src/test/java/org/apache/camel/spring/CircularComponentCreationTest.java @@ -0,0 +1,57 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.spring; + + +import org.apache.camel.CamelContext; +import org.apache.camel.FailedToCreateRouteException; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.util.IOHelper; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.context.support.AbstractXmlApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public class CircularComponentCreationTest { + @Test + public void testSimple() { + doTest("org/apache/camel/spring/CircularComponentCreationSimpleTest.xml"); + } + + @Test + public void testComplex() { + doTest("org/apache/camel/spring/CircularComponentCreationComplexTest.xml"); + } + + // ******************************* + // Test implementation + // ******************************* + + private void doTest(String path) { + AbstractXmlApplicationContext applicationContext = null; + CamelContext camelContext = null; + try { + applicationContext = new ClassPathXmlApplicationContext(path); + camelContext = new SpringCamelContext(applicationContext); + } catch (Exception e) { + Assert.assertTrue(e instanceof RuntimeCamelException); + Assert.assertTrue(e.getCause() instanceof FailedToCreateRouteException); + } finally { + IOHelper.close(applicationContext); + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/c9bd1dd1/components/camel-spring/src/test/java/org/apache/camel/spring/CircularComponentInjectionTest.java ---------------------------------------------------------------------- diff --git a/components/camel-spring/src/test/java/org/apache/camel/spring/CircularComponentInjectionTest.java b/components/camel-spring/src/test/java/org/apache/camel/spring/CircularComponentInjectionTest.java new file mode 100644 index 0000000..92500b7 --- /dev/null +++ b/components/camel-spring/src/test/java/org/apache/camel/spring/CircularComponentInjectionTest.java @@ -0,0 +1,40 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.spring; + + +import org.apache.camel.Endpoint; +import org.apache.camel.EndpointInject; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; + +@ContextConfiguration +public class CircularComponentInjectionTest extends SpringRunWithTestSupport { + @EndpointInject(ref = "seda") + protected Endpoint sedaEndpoint; + @EndpointInject(ref = "log") + protected Endpoint logEndpoint; + + @DirtiesContext + @Test + public void test() { + Assert.assertNotNull(sedaEndpoint); + Assert.assertNotNull(logEndpoint); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/c9bd1dd1/components/camel-spring/src/test/resources/org/apache/camel/spring/CircularComponentCreationComplexTest.xml ---------------------------------------------------------------------- diff --git a/components/camel-spring/src/test/resources/org/apache/camel/spring/CircularComponentCreationComplexTest.xml b/components/camel-spring/src/test/resources/org/apache/camel/spring/CircularComponentCreationComplexTest.xml new file mode 100644 index 0000000..4b72c4c --- /dev/null +++ b/components/camel-spring/src/test/resources/org/apache/camel/spring/CircularComponentCreationComplexTest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd + http://camel.apache.org/schema/spring + http://camel.apache.org/schema/spring/camel-spring.xsd"> + + <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring"> + <route> + <from id="log" uri="seda:test"/> + <to id="seda" uri="log:test"/> + </route> + </camelContext> + +</beans> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/c9bd1dd1/components/camel-spring/src/test/resources/org/apache/camel/spring/CircularComponentCreationSimpleTest.xml ---------------------------------------------------------------------- diff --git a/components/camel-spring/src/test/resources/org/apache/camel/spring/CircularComponentCreationSimpleTest.xml b/components/camel-spring/src/test/resources/org/apache/camel/spring/CircularComponentCreationSimpleTest.xml new file mode 100644 index 0000000..927519e --- /dev/null +++ b/components/camel-spring/src/test/resources/org/apache/camel/spring/CircularComponentCreationSimpleTest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd + http://camel.apache.org/schema/spring + http://camel.apache.org/schema/spring/camel-spring.xsd"> + + <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring"> + <route> + <from id="twitter" uri="twitter://timeline/home?type=polling"/> + <log message="Got ${body}"/> + </route> + </camelContext> + +</beans> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/c9bd1dd1/components/camel-spring/src/test/resources/org/apache/camel/spring/CircularComponentInjectionTest-context.xml ---------------------------------------------------------------------- diff --git a/components/camel-spring/src/test/resources/org/apache/camel/spring/CircularComponentInjectionTest-context.xml b/components/camel-spring/src/test/resources/org/apache/camel/spring/CircularComponentInjectionTest-context.xml new file mode 100644 index 0000000..4b72c4c --- /dev/null +++ b/components/camel-spring/src/test/resources/org/apache/camel/spring/CircularComponentInjectionTest-context.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd + http://camel.apache.org/schema/spring + http://camel.apache.org/schema/spring/camel-spring.xsd"> + + <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring"> + <route> + <from id="log" uri="seda:test"/> + <to id="seda" uri="log:test"/> + </route> + </camelContext> + +</beans> \ No newline at end of file