1414
1515"""Test built in connection-pooling."""
1616
17+ import gc
1718import os
1819import random
1920import sys
@@ -236,6 +237,35 @@ def test_max_pool_size_validation(self):
236237 c = Connection (host = host , port = port , max_pool_size = 100 )
237238 self .assertEqual (c .max_pool_size , 100 )
238239
240+ def force_reclaim_sockets (self , cx_pool , n_expected ):
241+ # When a thread dies without ending its request, the SocketInfo it was
242+ # using is deleted, and in its __del__ it returns the socket to the
243+ # pool. However, when exactly that happens is unpredictable. Try
244+ # various ways of forcing the issue.
245+
246+ if sys .platform .startswith ('java' ):
247+ raise SkipTest ("Jython can't reclaim sockets" )
248+
249+ # Bizarre behavior in CPython 2.4, and possibly other CPython versions
250+ # less than 2.7: the last dead thread's locals aren't cleaned up until
251+ # the local attribute with the same name is accessed from a different
252+ # thread. This assert checks that the thread-local is indeed local, and
253+ # also triggers the cleanup so the socket is reclaimed.
254+ self .assertEqual (None , cx_pool .local .sock_info )
255+
256+ # In PyPy, we need to try for a while to make garbage-collection call
257+ # SocketInfo.__del__
258+ start = time .time ()
259+ while len (cx_pool .sockets ) < n_expected and time .time () - start < 5 :
260+ import gc
261+ try :
262+ gc .collect (2 )
263+ except TypeError :
264+ # collect() didn't support 'generation' arg until 2.5
265+ gc .collect ()
266+
267+ time .sleep (0.5 )
268+
239269 def test_no_disconnect (self ):
240270 run_cases (self , [NoRequest , NonUnique , Unique , SaveAndFind ])
241271
@@ -500,7 +530,7 @@ def run_in_request():
500530
501531 def test_socket_reclamation (self ):
502532 # Check that if a thread starts a request and dies without ending
503- # the request, that the socket is reclaimed
533+ # the request, that the socket is reclaimed into the pool.
504534 cx_pool = Pool (
505535 pair = (host ,port ),
506536 max_size = 10 ,
@@ -517,21 +547,24 @@ def test_socket_reclamation(self):
517547 the_sock = [None ]
518548
519549 def leak_request ():
550+ self .assertEqual (NO_REQUEST , cx_pool .local .sock_info )
520551 cx_pool .start_request ()
552+ self .assertEqual (NO_SOCKET_YET , cx_pool .local .sock_info )
521553 sock_info = cx_pool .get_socket ()
522- lock .release ()
523- cx_pool ._reset ()
554+ self .assertEqual (sock_info , cx_pool .local .sock_info )
524555 the_sock [0 ] = id (sock_info .sock )
556+ lock .release ()
525557
526558 # Start a thread WITHOUT a threading.Thread - important to test that
527559 # Pool can deal with primitive threads.
528560 thread .start_new_thread (leak_request , ())
529561
530562 # Join thread
531- time .sleep (0.5 )
532- acquired = lock .acquire (0 )
563+ acquired = lock .acquire (1 )
533564 self .assertTrue (acquired , "Thread is hung" )
534565
566+ self .force_reclaim_sockets (cx_pool , 1 )
567+
535568 # Pool reclaimed the socket
536569 self .assertEqual (1 , len (cx_pool .sockets ))
537570 self .assertEqual (the_sock [0 ], id ((iter (cx_pool .sockets ).next ()).sock ))
@@ -550,17 +583,14 @@ def _test_max_pool_size(self, c, start_request, end_request):
550583 self .assert_ (t .passed )
551584
552585 # Critical: release refs to threads, so SocketInfo.__del__() executes
553- import sys ; print >> sys .stderr , "Deleting threads"
554586 del threads
555587 t = None
556- import sys ; print >> sys .stderr , "Deleted"
557588
558- # Let all the SocketInfo destructors execute and return sockets to
559- # the pool
560- time .sleep (1 )
589+ cx_pool = c ._Connection__pool
590+ self .force_reclaim_sockets (cx_pool , 4 )
561591
562592 # There's a race condition, so be lenient
563- nsock = len (c . _Connection__pool .sockets )
593+ nsock = len (cx_pool .sockets )
564594 self .assert_ (
565595 abs (4 - nsock ) < 4 ,
566596 "Expected about 4 sockets in the pool, got %d" % nsock
0 commit comments