1818import socket
1919import threading
2020import time
21+ import six
22+
23+ from collections import defaultdict
24+ from itertools import chain
2125
2226import kazoo .python2atexit as python2atexit
2327
3337
3438log = logging .getLogger (__name__ )
3539
40+ _HAS_EPOLL = hasattr (select , "epoll" )
41+
42+
43+ def _to_fileno (obj ):
44+ if isinstance (obj , six .integer_types ):
45+ fd = int (obj )
46+ elif hasattr (obj , "fileno" ):
47+ fd = obj .fileno ()
48+ if not isinstance (fd , six .integer_types ):
49+ raise TypeError ("fileno() returned a non-integer" )
50+ fd = int (fd )
51+ else :
52+ raise TypeError ("argument must be an int, or have a fileno() method." )
53+
54+ if fd < 0 :
55+ raise ValueError (
56+ "file descriptor cannot be a negative integer (%d)" % (fd ,)
57+ )
58+
59+ return fd
60+
3661
3762class KazooTimeoutError (Exception ):
3863 pass
@@ -143,8 +168,17 @@ def stop(self):
143168 python2atexit .unregister (self .stop )
144169
145170 def select (self , * args , ** kwargs ):
146- # select() takes no kwargs, so it will be in args
147- timeout = args [3 ] if len (args ) == 4 else None
171+ # if we have epoll, and select is not expected to work
172+ # use an epoll-based "select". Otherwise don't touch
173+ # anything to minimize changes
174+ if _HAS_EPOLL :
175+ # if the highest fd we've seen is > 1023
176+ if max (map (_to_fileno , chain (* args [:3 ]))) > 1023 :
177+ return self ._epoll_select (* args , ** kwargs )
178+ return self ._select (* args , ** kwargs )
179+
180+ def _select (self , * args , ** kwargs ):
181+ timeout = kwargs .pop ('timeout' , None )
148182 # either the time to give up, or None
149183 end = (time .time () + timeout ) if timeout else None
150184 while end is None or time .time () < end :
@@ -167,6 +201,50 @@ def select(self, *args, **kwargs):
167201 # if we hit our timeout, lets return as a timeout
168202 return ([], [], [])
169203
204+ def _epoll_select (self , rlist , wlist , xlist , timeout = None ):
205+ """epoll-based drop-in replacement for select to overcome select
206+ limitation on a maximum filehandle value
207+ """
208+ if timeout is None :
209+ timeout = - 1
210+ eventmasks = defaultdict (int )
211+ rfd2obj = defaultdict (list )
212+ wfd2obj = defaultdict (list )
213+ xfd2obj = defaultdict (list )
214+ read_evmask = select .EPOLLIN | select .EPOLLPRI # Just in case
215+
216+ def store_evmasks (obj_list , evmask , fd2obj ):
217+ for obj in obj_list :
218+ fileno = _to_fileno (obj )
219+ eventmasks [fileno ] |= evmask
220+ fd2obj [fileno ].append (obj )
221+
222+ store_evmasks (rlist , read_evmask , rfd2obj )
223+ store_evmasks (wlist , select .EPOLLOUT , wfd2obj )
224+ store_evmasks (xlist , select .EPOLLERR , xfd2obj )
225+
226+ poller = select .epoll ()
227+
228+ for fileno in eventmasks :
229+ poller .register (fileno , eventmasks [fileno ])
230+
231+ try :
232+ events = poller .poll (timeout )
233+ revents = []
234+ wevents = []
235+ xevents = []
236+ for fileno , event in events :
237+ if event & read_evmask :
238+ revents += rfd2obj .get (fileno , [])
239+ if event & select .EPOLLOUT :
240+ wevents += wfd2obj .get (fileno , [])
241+ if event & select .EPOLLERR :
242+ xevents += xfd2obj .get (fileno , [])
243+ finally :
244+ poller .close ()
245+
246+ return revents , wevents , xevents
247+
170248 def socket (self ):
171249 return utils .create_tcp_socket (socket )
172250
0 commit comments