Skip to content

Improve Documentation for PooledChannelConnectionFactory #1557

@m-ober

Description

@m-ober

In what version(s) of Spring AMQP are you seeing this issue?

Tested with 2.4.3 and 2.4.8

Describe the bug

We have a Spring Boot application that uses a PooledChannelConnectionFactory and a CachingConnectionFactory inside a SimpleRoutingConnectionFactory(the default connection used is the PooledChannelConnectionFactory, the other one is only used for few requests which where not used when experiencing the described problem). We noticed that after some time and loading the application with (HTTP) requests, the container is shut down because the /actuator/health endpoint stops responding.

We traced it down to the channel pool being exhausted, that is, this line (PooledChannelConnectionFactory.java:196) waits forever:

Channel channel = transactional ? this.txChannels.borrowObject() : this.channels.borrowObject();

We are using the default RabbitHealthIndicator provided by the org.springframework.boot.actuate.amqp package, which does the check by executing:

return this.rabbitTemplate
	.execute((channel) -> channel.getConnection().getServerProperties().get("version").toString());

The connection configuration is straight forward:

var pooledChannelConnectionFactory = new PooledChannelConnectionFactory(connectionFactory);
pooledChannelConnectionFactory.setRequestedHeartBeat(configProperties.getRabbitmq().getHeartbeatSeconds());
pooledChannelConnectionFactory.setConnectionNameStrategy(factory -> String.format(
    "%s-%s",
    configProperties.getName(),
    factory.getPublisherConnectionFactory()
));

We have a RabbitListener for incoming messages:

@RabbitListener(
    queues = "${...}",
    ackMode = "NONE",
    messageConverter = "...",
    errorHandler = "..."
)

And send outgoing messages using rabbitTemplate.convertSendAndReceive().

This is the state of the pool when it stops working:

GenericObjectPool [maxTotal=8, blockWhenExhausted=true, maxWaitDuration=PT-0.001S, lifo=true, fairness=false, testOnCreate=false, testOnBorrow=false, testOnReturn=false, testWhileIdle=false, durationBetweenEvictionRuns=PT-0.001S, numTestsPerEvictionRun=3, minEvictableIdleTimeDuration=PT30M, softMinEvictableIdleTimeDuration=PT-0.001S, evictionPolicy=org.apache.commons.pool2.impl.DefaultEvictionPolicy@2aa6311a, closeLock=java.lang.Object@61f39bb, closed=false, evictionLock=java.lang.Object@249e0271, evictor=null, evictionIterator=null, factoryClassLoader=java.lang.ref.WeakReference@4893b344, oname=org.apache.commons.pool2:type=GenericObjectPool,name=pool, creationStackTrace=java.lang.Exception
	at org.apache.commons.pool2.impl.BaseGenericObjectPool.<init>(BaseGenericObjectPool.java:407)
	at org.apache.commons.pool2.impl.GenericObjectPool.<init>(GenericObjectPool.java:147)
	at org.apache.commons.pool2.impl.GenericObjectPool.<init>(GenericObjectPool.java:130)
	at org.springframework.amqp.rabbit.connection.PooledChannelConnectionFactory$ConnectionWrapper.<init>(PooledChannelConnectionFactory.java:183)
	at org.springframework.amqp.rabbit.connection.PooledChannelConnectionFactory.createConnection(PooledChannelConnectionFactory.java:140)
	at org.springframework.amqp.rabbit.connection.AbstractRoutingConnectionFactory.createConnection(AbstractRoutingConnectionFactory.java:141)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.checkMismatchedQueues(AbstractMessageListenerContainer.java:1863)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.start(AbstractMessageListenerContainer.java:1407)
	at org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry.startIfNecessary(RabbitListenerEndpointRegistry.java:289)
	at org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry.start(RabbitListenerEndpointRegistry.java:239)
	at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178)
	at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54)
	at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356)
	at java.base/java.lang.Iterable.forEach(Iterable.java:75)
	at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155)
	at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123)
	at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:935)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586)
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731)
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:307)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292)
	at ...Application.main(Application.java:10)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:49)
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:108)
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:58)
	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:65)
, borrowedCount=157, returnedCount=149, createdCount=8, destroyedCount=0, destroyedByEvictorCount=0, destroyedByBorrowValidationCount=0, activeTimes=StatsStore [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], size=100, index=49], idleTimes=StatsStore [[15001, 1, 14996, 29999, 14998, 1, 14998, 29999, 14999, 14999, 14998, 14999, 14999, 0, 14998, 0, 6520, 44999, 0, 14996, 15000, 14998, 14998, 14999, 14999, 14999, 14999, 15000, 0, 15002, 30005, 14994, 14994, 15004, 15004, 791, 14993, 1, 14997, 1, 14993, 0, 14999, 0, 14998, 0, 14999, 1, 14997, 0, 14998, 0, 14998, 0, 14999, 0, 10112]], size=100, index=57], waitTimes=StatsStore [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], size=100, index=57], maxBorrowWaitDuration=PT0.082S, swallowedExceptionListener=null, factoryType=null, maxIdle=8, minIdle=0, factory=org.springframework.amqp.rabbit.connection.PooledChannelConnectionFactory$ConnectionWrapper$ChannelFactory@53a665ad, allObjects={IdentityWrapper [instance=AMQChannel(amqp://[email protected]:5672/,1)]=Object: AMQChannel(amqp://[email protected]:5672/,1), State: ALLOCATED, IdentityWrapper [instance=AMQChannel(amqp://[email protected]:5672/,6)]=Object: AMQChannel(amqp://[email protected]:5672/,6), State: ALLOCATED, IdentityWrapper [instance=AMQChannel(amqp://[email protected]:5672/,7)]=Object: AMQChannel(amqp://[email protected]:5672/,7), State: ALLOCATED, IdentityWrapper [instance=AMQChannel(amqp://[email protected]:5672/,4)]=Object: AMQChannel(amqp://[email protected]:5672/,4), State: ALLOCATED, IdentityWrapper [instance=AMQChannel(amqp://[email protected]:5672/,8)]=Object: AMQChannel(amqp://[email protected]:5672/,8), State: ALLOCATED, IdentityWrapper [instance=AMQChannel(amqp://[email protected]:5672/,2)]=Object: AMQChannel(amqp://[email protected]:5672/,2), State: ALLOCATED, IdentityWrapper [instance=AMQChannel(amqp://[email protected]:5672/,5)]=Object: AMQChannel(amqp://[email protected]:5672/,5), State: ALLOCATED, IdentityWrapper [instance=AMQChannel(amqp://[email protected]:5672/,3)]=Object: AMQChannel(amqp://[email protected]:5672/,3), State: ALLOCATED}, createCount=8, idleObjects=[], abandonedConfig=null]

To Reproduce

I am not yet able to provide a minimum working example to easily reproduce this behaviour. It requires an application that runs for some time and is loaded with requests. At some point, the RabbitHealthIndicator stops working, that is, the liveness/readiness endpoints time out.

Expected behavior

The channel pool should not get exhausted.

Sample

Not yet available.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions