11from __future__ import annotations
22
33import os
4+ import uuid
45
56import pytest
7+ from amqp .exceptions import NotFound
68
79import kombu
10+ from kombu .connection import ConnectionPool
811
912from .common import (BaseExchangeTypes , BaseFailover , BaseMessage ,
1013 BasePriority , BaseTimeToLive , BasicFunctionality )
@@ -20,6 +23,12 @@ def get_failover_connection(hostname, port, vhost):
2023 )
2124
2225
26+ def get_confirm_connection (hostname , port ):
27+ return kombu .Connection (
28+ f"pyamqp://{ hostname } :{ port } " , transport_options = {"confirm_publish" : True }
29+ )
30+
31+
2332@pytest .fixture ()
2433def invalid_connection ():
2534 return kombu .Connection ('pyamqp://localhost:12345' )
@@ -47,6 +56,14 @@ def failover_connection(request):
4756 )
4857
4958
59+ @pytest .fixture ()
60+ def confirm_publish_connection ():
61+ return get_confirm_connection (
62+ hostname = os .environ .get ("RABBITMQ_HOST" , "localhost" ),
63+ port = os .environ .get ("RABBITMQ_5672_TCP" , "5672" ),
64+ )
65+
66+
5067@pytest .mark .env ('py-amqp' )
5168@pytest .mark .flaky (reruns = 5 , reruns_delay = 2 )
5269class test_PyAMQPBasicFunctionality (BasicFunctionality ):
@@ -81,3 +98,49 @@ class test_PyAMQPFailover(BaseFailover):
8198@pytest .mark .flaky (reruns = 5 , reruns_delay = 2 )
8299class test_PyAMQPMessage (BaseMessage ):
83100 pass
101+
102+
103+ @pytest .mark .env ("py-amqp" )
104+ @pytest .mark .flaky (reruns = 5 , reruns_delay = 2 )
105+ class test_PyAMQPConnectionPool :
106+ def test_publish_confirm_does_not_block (self , confirm_publish_connection ):
107+ """Tests that the connection pool closes connections in case of an exception.
108+
109+ In case an exception occurs while the connection is in use, the pool should
110+ close the exception. In case the connection is not closed before releasing it
111+ back to the pool, the connection would remain in an unusable state, causing
112+ causing the next publish call to time out or block forever in case no
113+ timeout is specified.
114+ """
115+ pool = ConnectionPool (connection = confirm_publish_connection , limit = 1 )
116+
117+ try :
118+ with pool .acquire (block = True ) as connection :
119+ producer = kombu .Producer (connection )
120+ queue = kombu .Queue (
121+ f"test-queue-{ uuid .uuid4 ()} " , channel = connection
122+ )
123+ queue .declare ()
124+ producer .publish (
125+ {"foo" : "bar" }, routing_key = str (uuid .uuid4 ()), retry = False
126+ )
127+ assert connection .connected
128+ queue .delete ()
129+ try :
130+ queue .get ()
131+ except NotFound :
132+ raise
133+ except NotFound :
134+ pass
135+
136+ with pool .acquire (block = True ) as connection :
137+ assert not connection .connected
138+ producer = kombu .Producer (connection )
139+ queue = kombu .Queue (
140+ f"test-queue-{ uuid .uuid4 ()} " , channel = connection
141+ )
142+ queue .declare ()
143+ # In case the connection is broken, we should get a Timeout here
144+ producer .publish (
145+ {"foo" : "bar" }, routing_key = str (uuid .uuid4 ()), retry = False , timeout = 3
146+ )
0 commit comments