55from django .db import connection
66from django .db .utils import OperationalError
77
8- from ansible_base .lib .utils .db import advisory_lock , migrations_are_complete
8+ from ansible_base .lib .utils .db import advisory_lock , migrations_are_complete , transaction
9+ from django .db import connection
910
1011
1112@pytest .mark .django_db
@@ -15,6 +16,8 @@ def test_migrations_are_complete():
1516
1617
1718class TestAdvisoryLock :
19+ THREAD_WAIT_TIME = 0.1
20+
1821 @pytest .fixture (autouse = True )
1922 def skip_if_sqlite (self ):
2023 if connection .vendor == 'sqlite' :
@@ -30,7 +33,7 @@ def background_task(django_db_blocker):
3033 # HACK: as a thread the pytest.mark.django_db will not work
3134 django_db_blocker .unblock ()
3235 with advisory_lock ('background_task_lock' ):
33- time .sleep (0.1 )
36+ time .sleep (TestAdvisoryLock . THREAD_WAIT_TIME )
3437
3538 @pytest .mark .django_db
3639 def test_determine_lock_is_held (self , django_db_blocker ):
@@ -40,7 +43,7 @@ def test_determine_lock_is_held(self, django_db_blocker):
4043 with advisory_lock ('background_task_lock' , wait = False ) as held :
4144 if held is False :
4245 break
43- time .sleep (0.01 )
46+ time .sleep (TestAdvisoryLock . THREAD_WAIT_TIME / 5.0 )
4447 else :
4548 raise RuntimeError ('Other thread never obtained lock' )
4649 thread .join ()
@@ -56,10 +59,52 @@ def test_invalid_tuple_name(self):
5659 with advisory_lock (['test_invalid_tuple_name' , 'foo' ]):
5760 pass
5861
59- @pytest .mark .django_db
60- def test_lock_session_timeout_milliseconds (self ):
62+
63+ # Special transaction=True parameter is used, because we do not want in normal test transactions
64+ # because dropping the connection would break the transaction context, erroring at end of test
65+ @pytest .mark .django_db (transaction = True )
66+ class TestAdvisoryLockPostgresErrors :
67+ """Tests related to connection management and the advisory_lock"""
68+
69+ def kick_connection (self ):
70+ """
71+ These (somewhat evil) tests are not gentle with the connection
72+
73+ At the start of one tests there is a good chance the connection is broken by the last test.
74+ """
75+ connection .ensure_connection ()
76+ # sanity checks
77+ assert connection .connection
78+ assert connection .connection .closed is False
79+
80+ def test_timeout_under_lock_by_sleep (self ):
81+ self .kick_connection ()
82+
6183 with pytest .raises (OperationalError ) as exc :
6284 # uses miliseconds units
63- with advisory_lock ('test_lock_session_timeout_milliseconds' , lock_session_timeout_milliseconds = 2 ):
64- time .sleep (3 )
65- assert 'the connection is lost' in str (exc )
85+ with advisory_lock ('test_lock_session_timeout_milliseconds' , lock_session_timeout_milliseconds = 5 ):
86+ time .sleep (0.1 )
87+
88+ # This exception comes from the __exit__, either releasing the lock or closing the cursor
89+ assert 'terminating connection due to idle-session timeout' in str (exc )
90+
91+ with connection .cursor () as cursor :
92+ cursor .execute ('SELECT 1' )
93+
94+ def test_idle_after_exception (self ):
95+ self .kick_connection ()
96+
97+ with pytest .raises (RuntimeError ) as exc :
98+ with advisory_lock ('test_timeout_under_lock_with_query' , lock_session_timeout_milliseconds = 5 ):
99+ with connection .cursor () as cursor :
100+ raise RuntimeError ('exception from test' )
101+
102+ assert 'exception from test' in str (exc )
103+
104+ time .sleep (0.1 )
105+
106+ # The fact that this works shows that the timeout was reset in the context manager __exit__
107+ # Prior bug was giving exception with
108+ # consuming input failed: terminating connection due to idle-session timeout server closed the connection unexpectedly
109+ with connection .cursor () as cursor :
110+ cursor .execute ('SELECT 1' )
0 commit comments