@@ -244,36 +244,51 @@ def setUpClass(cls):
244244 def tearDownClass (cls ):
245245 cls .origin .shutdown ()
246246
247- def _get (self , relay , path = "/hi" , retries = 4 ):
248- # Loopback connections under CI load can occasionally reset; retry the
249- # transient ones so the rotation/throughput tests stay deterministic.
247+ @staticmethod
248+ def _retry (fn , retries = 6 ):
249+ # Loopback connections under CI load (especially Windows) can reset;
250+ # retry transient failures so the integration tests stay deterministic.
250251 last = None
251252 for attempt in range (retries ):
252253 try :
253- opener = urllib .request .build_opener (
254- urllib .request .ProxyHandler ({"http" : relay .url }))
255- return opener .open (
256- f"http://127.0.0.1:{ self .origin_port } { path } " , timeout = 10
257- ).read ().decode ()
254+ return fn ()
258255 except Exception as e : # noqa: BLE001 - retry transient socket errors
259256 last = e
260- time .sleep (0.2 * (attempt + 1 ))
257+ time .sleep (0.15 * (attempt + 1 ))
261258 raise last
262259
263- def _connect_get (self , relay , path = "/tunnel" ):
264- s = socket .create_connection ((relay .listen_host , relay .port ), timeout = 10 )
265- s .sendall (f"CONNECT 127.0.0.1:{ self .origin_port } HTTP/1.1\r \n "
266- f"Host: 127.0.0.1:{ self .origin_port } \r \n \r \n " .encode ())
267- self .assertIn (b"200" , s .recv (4096 ).split (b"\r \n " , 1 )[0 ])
268- s .sendall (f"GET { path } HTTP/1.1\r \n Host: 127.0.0.1\r \n Connection: close\r \n \r \n " .encode ())
269- data = b""
270- while True :
271- chunk = s .recv (4096 )
272- if not chunk :
273- break
274- data += chunk
275- s .close ()
276- return data
260+ def _get (self , relay , path = "/hi" , retries = 6 ):
261+ def once ():
262+ opener = urllib .request .build_opener (
263+ urllib .request .ProxyHandler ({"http" : relay .url }))
264+ return opener .open (
265+ f"http://127.0.0.1:{ self .origin_port } { path } " , timeout = 15
266+ ).read ().decode ()
267+ return self ._retry (once , retries = retries )
268+
269+ def _connect_get (self , relay , path = "/tunnel" , retries = 6 ):
270+ def once ():
271+ s = socket .create_connection ((relay .listen_host , relay .port ), timeout = 15 )
272+ try :
273+ s .sendall (f"CONNECT 127.0.0.1:{ self .origin_port } HTTP/1.1\r \n "
274+ f"Host: 127.0.0.1:{ self .origin_port } \r \n \r \n " .encode ())
275+ status = s .recv (4096 ).split (b"\r \n " , 1 )[0 ]
276+ if b"200" not in status :
277+ raise ConnectionError (f"CONNECT not established: { status !r} " )
278+ s .sendall (f"GET { path } HTTP/1.1\r \n Host: 127.0.0.1\r \n "
279+ f"Connection: close\r \n \r \n " .encode ())
280+ data = b""
281+ while True :
282+ chunk = s .recv (4096 )
283+ if not chunk :
284+ break
285+ data += chunk
286+ if b"OK " not in data :
287+ raise ConnectionError ("no response body" )
288+ return data
289+ finally :
290+ s .close ()
291+ return self ._retry (once , retries = retries )
277292
278293
279294class TestSocks5 (_Base ):
@@ -359,6 +374,7 @@ def test_roundrobin_uses_both(self):
359374 with ProxyRelay (pool , rotation = "roundrobin" ) as relay :
360375 for _ in range (4 ):
361376 self .assertEqual (self ._get (relay , "/x" ), "OK /x" )
377+ time .sleep (0.05 )
362378 # Round-robin must have used BOTH upstreams (exact split can shift by one
363379 # if a transient loopback reset triggered a retry).
364380 self .assertGreaterEqual (self .a .conns , 1 )
0 commit comments