6
6
import socket
7
7
8
8
import pytest
9
+ import time
9
10
10
11
from kafka .conn import BrokerConnection , ConnectionStates , collect_hosts
11
12
from kafka .protocol .api import RequestHeader
@@ -61,28 +62,99 @@ def test_connect_timeout(_socket, conn):
61
62
# Initial connect returns EINPROGRESS
62
63
# immediate inline connect returns EALREADY
63
64
# second explicit connect returns EALREADY
64
- # third explicit connect returns EALREADY and times out via last_attempt
65
+ # third explicit connect returns EALREADY and times out via last_activity
65
66
_socket .connect_ex .side_effect = [EINPROGRESS , EALREADY , EALREADY , EALREADY ]
66
67
conn .connect ()
67
68
assert conn .state is ConnectionStates .CONNECTING
68
69
conn .connect ()
69
70
assert conn .state is ConnectionStates .CONNECTING
71
+ conn .last_activity = 0
70
72
conn .last_attempt = 0
71
73
conn .connect ()
72
74
assert conn .state is ConnectionStates .DISCONNECTED
73
75
76
+ def test_connect_timeout_slowconn (_socket , conn , mocker ):
77
+ # Same as test_connect_timeout,
78
+ # but we make the connection run longer than the timeout in order to test that
79
+ # BrokerConnection resets the timer whenever things happen during the connection
80
+ # See https://github.com/dpkp/kafka-python/issues/2386
81
+ _socket .connect_ex .side_effect = [EINPROGRESS , EISCONN ]
82
+
83
+ # 0.8 = we guarantee that when testing with three intervals of this we are past the timeout
84
+ time_between_connect = (conn .config ['connection_timeout_ms' ]/ 1000 ) * 0.8
85
+ start = time .time ()
86
+
87
+ # Use plaintext auth for simplicity
88
+ last_activity = conn .last_activity
89
+ last_attempt = conn .last_attempt
90
+ conn .config ['security_protocol' ] = 'SASL_PLAINTEXT'
91
+ conn .connect ()
92
+ assert conn .state is ConnectionStates .CONNECTING
93
+ # Ensure the last_activity counter was updated
94
+ # Last_attempt should also be updated
95
+ assert conn .last_activity > last_activity
96
+ assert conn .last_attempt > last_attempt
97
+ last_attempt = conn .last_attempt
98
+ last_activity = conn .last_activity
99
+
100
+ # Simulate time being passed
101
+ # This shouldn't be enough time to time out the connection
102
+ conn ._try_authenticate = mocker .Mock (side_effect = [False , False , True ])
103
+ with mock .patch ("time.time" , return_value = start + time_between_connect ):
104
+ # This should trigger authentication
105
+ # Note that an authentication attempt isn't actually made until now.
106
+ # We simulate that authentication does not succeed at this point
107
+ # This is technically incorrect, but it lets us see what happens
108
+ # to the state machine when the state doesn't change for two function calls
109
+ conn .connect ()
110
+ assert conn .last_activity > last_activity
111
+ # Last attempt is kept as a legacy variable, should not update
112
+ assert conn .last_attempt == last_attempt
113
+ last_activity = conn .last_activity
114
+
115
+ assert conn .state is ConnectionStates .AUTHENTICATING
116
+
117
+
118
+ # This time around we should be way past timeout.
119
+ # Now we care about connect() not terminating the attempt,
120
+ # because connection state was progressed in the meantime.
121
+ with mock .patch ("time.time" , return_value = start + time_between_connect * 2 ):
122
+ # Simulate this one not succeeding as well. This is so we can ensure things don't time out
123
+ conn .connect ()
124
+
125
+ # No state change = no activity change
126
+ assert conn .last_activity == last_activity
127
+ assert conn .last_attempt == last_attempt
128
+
129
+ # If last_activity was not reset when the state transitioned to AUTHENTICATING,
130
+ # the connection state would be timed out now.
131
+ assert conn .state is ConnectionStates .AUTHENTICATING
132
+
133
+
134
+ # This time around, the connection should succeed.
135
+ with mock .patch ("time.time" , return_value = start + time_between_connect * 3 ):
136
+ # This should finalize the connection
137
+ conn .connect ()
138
+
139
+ assert conn .last_activity > last_activity
140
+ assert conn .last_attempt == last_attempt
141
+ last_activity = conn .last_activity
142
+
143
+ assert conn .state is ConnectionStates .CONNECTED
144
+
145
+
74
146
75
147
def test_blacked_out (conn ):
76
148
with mock .patch ("time.time" , return_value = 1000 ):
77
- conn .last_attempt = 0
149
+ conn .last_activity = 0
78
150
assert conn .blacked_out () is False
79
- conn .last_attempt = 1000
151
+ conn .last_activity = 1000
80
152
assert conn .blacked_out () is True
81
153
82
154
83
155
def test_connection_delay (conn ):
84
156
with mock .patch ("time.time" , return_value = 1000 ):
85
- conn .last_attempt = 1000
157
+ conn .last_activity = 1000
86
158
assert conn .connection_delay () == conn .config ['reconnect_backoff_ms' ]
87
159
conn .state = ConnectionStates .CONNECTING
88
160
assert conn .connection_delay () == float ('inf' )
@@ -286,7 +358,7 @@ def test_lookup_on_connect():
286
358
]
287
359
288
360
with mock .patch ("socket.getaddrinfo" , return_value = mock_return2 ) as m :
289
- conn .last_attempt = 0
361
+ conn .last_activity = 0
290
362
conn .connect ()
291
363
m .assert_called_once_with (hostname , port , 0 , socket .SOCK_STREAM )
292
364
assert conn ._sock_afi == afi2
@@ -301,11 +373,10 @@ def test_relookup_on_failure():
301
373
assert conn .host == hostname
302
374
mock_return1 = []
303
375
with mock .patch ("socket.getaddrinfo" , return_value = mock_return1 ) as m :
304
- last_attempt = conn .last_attempt
376
+ last_activity = conn .last_activity
305
377
conn .connect ()
306
378
m .assert_called_once_with (hostname , port , 0 , socket .SOCK_STREAM )
307
379
assert conn .disconnected ()
308
- assert conn .last_attempt > last_attempt
309
380
310
381
afi2 = socket .AF_INET
311
382
sockaddr2 = ('127.0.0.2' , 9092 )
@@ -314,12 +385,13 @@ def test_relookup_on_failure():
314
385
]
315
386
316
387
with mock .patch ("socket.getaddrinfo" , return_value = mock_return2 ) as m :
317
- conn .last_attempt = 0
388
+ conn .last_activity = 0
318
389
conn .connect ()
319
390
m .assert_called_once_with (hostname , port , 0 , socket .SOCK_STREAM )
320
391
assert conn ._sock_afi == afi2
321
392
assert conn ._sock_addr == sockaddr2
322
393
conn .close ()
394
+ assert conn .last_activity > last_activity
323
395
324
396
325
397
def test_requests_timed_out (conn ):
0 commit comments