Describe the bug When using feign client during app context initialization phase it can get dead-locked if the call is made from outside of main thread.

Java: 17 Spring boot: 3.1.5 Spring: 6.0.13 OpenFeign: 4.0.4 Spring cloud: 2022.0.4

Sample Full runnable sample can be found at https://github.com/pcimcioch/openfeign-deadlock-example/tree/master

For reference, it looks like that:

@FeignClient(name="demo", url = "localhost:8081")
interface DemoClient {
    @RequestMapping(method = RequestMethod.GET, value = "/values/{id}")
    Value getValue(@PathVariable String id);
    record Value(String value) {}
}
@SpringBootApplication
@EnableFeignClients
public class DemoApplication {
    public static void main(String[] args) {
        WireMockServer wireMockServer = new WireMockServer(8081);
        wireMockServer.stubFor(get(urlEqualTo("/values/id1")).willReturn(okJson("{\"value\": \"value1\"}")));
        wireMockServer.stubFor(get(urlEqualTo("/values/id2")).willReturn(okJson("{\"value\": \"value2\"}")));
        wireMockServer.start();

        SpringApplication.run(DemoApplication.class, args);
    }
}
@Configuration
class DataConfiguration {
    private static final List<String> ids = List.of("id1", "id2");
    private final DemoClient client;

    DataConfiguration(DemoClient client) {
        this.client = client;
    }

    @Bean
    String data() {
        // Change to 'ids.stream()' to work
        return ids.parallelStream()
                .map(this::getValue)
                .collect(joining());
    }

    private String getValue(String id) {
        System.out.println("Getting value for id " + id);
        String value = client.getValue(id).value();
        System.out.println("Got value for id " + id);

        return value;
    }
}

Generally, the problem is that feign client is called from the parallelStream, which means that two feign calls can be made from different threads. This causes the deadlock. Threads Report:

"main@1" prio=5 tid=0x1 nid=NA waiting
  java.lang.Thread.State: WAITING
     blocks ForkJoinPool.commonPool-worker-1@7273
      at jdk.internal.misc.Unsafe.park(Unsafe.java:-1)
      at java.util.concurrent.locks.LockSupport.park(LockSupport.java:341)
      at java.util.concurrent.ForkJoinTask.awaitDone(ForkJoinTask.java:468)
      at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:687)
      at java.util.stream.ReduceOps$ReduceOp.evaluateParallel(ReduceOps.java:927)
      at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
      at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
      at com.example.demo.DataConfiguration.data(DataConfiguration.java:26)
      at com.example.demo.DataConfiguration$$SpringCGLIB$$0.CGLIB$data$0(<generated>:-1)
      at com.example.demo.DataConfiguration$$SpringCGLIB$$FastClass$$1.invoke(<generated>:-1)
      at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258)
      at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331)
      at com.example.demo.DataConfiguration$$SpringCGLIB$$0.data(<generated>:-1)
      at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
      at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
      at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      at java.lang.reflect.Method.invoke(Method.java:568)
      at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:139)
      at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:650)
      at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:488)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1332)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1162)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520)
      at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325)
      at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$516/0x0000028c893a9d80.getObject(Unknown Source:-1)
      at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
      - locked <0x1ef0> (a java.util.concurrent.ConcurrentHashMap)
      at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323)
      at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
      at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
      at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1417)
      at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337)
      at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:910)
      at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:788)
      at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1352)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1189)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520)
      at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325)
      at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$516/0x0000028c893a9d80.getObject(Unknown Source:-1)
      at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
      at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323)
      at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
      at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:973)
      at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:950)
      at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:616)
      - locked <0x1f27> (a java.lang.Object)
      at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
      at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:738)
      at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:440)
      at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
      at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306)
      at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295)
      at com.example.demo.DemoApplication.main(DemoApplication.java:23)

"ForkJoinPool.commonPool-worker-1@7273" daemon prio=5 tid=0x2c nid=NA waiting for monitor entry
  java.lang.Thread.State: BLOCKED
     waiting for main@1 to release lock on <0x1ef0> (a java.util.concurrent.ConcurrentHashMap)
      at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:186)
      at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:168)
      at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:247)
      at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:274)
      at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
      at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
      at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1417)
      at org.springframework.beans.factory.support.DefaultListableBeanFactory$DependencyObjectProvider.getObject(DefaultListableBeanFactory.java:2014)
      at org.springframework.cloud.openfeign.support.SpringDecoder.decode(SpringDecoder.java:70)
      at org.springframework.cloud.openfeign.support.ResponseEntityDecoder.decode(ResponseEntityDecoder.java:61)
      at feign.optionals.OptionalDecoder.decode(OptionalDecoder.java:36)
      at feign.InvocationContext.proceed(InvocationContext.java:36)
      at feign.ResponseInterceptor$$Lambda$808/0x0000028c894f13a0.aroundDecode(Unknown Source:-1)
      at feign.ResponseHandler.decode(ResponseHandler.java:122)
      at feign.ResponseHandler.handleResponse(ResponseHandler.java:73)
      at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:114)
      at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:70)
      at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:96)
      at com.example.demo.$Proxy63.getValue(Unknown Source:-1)
      at com.example.demo.DataConfiguration.getValue(DataConfiguration.java:31)
      at com.example.demo.DataConfiguration$$Lambda$839/0x0000028c894ff438.apply(Unknown Source:-1)
      at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
      at java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720)
      at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
      at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
      at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:960)
      at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:934)
      at java.util.stream.AbstractTask.compute(AbstractTask.java:327)
      at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:754)
      at java.util.concurrent.ForkJoinTask.doExec$$$capture(ForkJoinTask.java:373)
      at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:-1)
      at java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
      at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
      at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
      at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

Comment From: cbezmen

This is not related to open feign or spring open feign. Just change your Feign client URL to @FeignClient(name="demo", url = "https://canbaba.free.beeceptor.com/") (I don't know how long this url is valid) or use a different client instead of WireMockServer.

Please investigate more before creating an issue... https://stackoverflow.com/questions/76158078/parallel-calls-to-wiremock-not-returning-the-stubbed-value

Comment From: OlgaMaciaszek

Hello, @pcimcioch, does @cbezmen's suggestion solve your issue?

Comment From: pcimcioch

Hi!

Thanks for the response, but no, it does not solve the problem. It is not problem with wiremock. Wiremock is added only for local reproducibility - It is reproducible in production environment, where I do not use wiremock - Thread dump I attached shows the location when deadlock appears

Comment From: pcimcioch

But I can see this response https://github.com/spring-cloud/spring-cloud-openfeign/issues/953#issuecomment-1856810849 In #953

Based on it, can I just assume that using Feign in Spring Inicialization Phase is discouraged and may not work properly in some cases?

Comment From: OlgaMaciaszek

Yes, it's not supported during initialisation - it might be to early for all the necessary components to be ready.

Comment From: spring-cloud-issues

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

Comment From: spring-cloud-issues

Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.