18
18
import errno
19
19
import select
20
20
import struct
21
+ import threading
21
22
22
23
_HAS_POLL = True
23
- _poller = None
24
24
_EVENT_MASK = 0
25
25
try :
26
26
from select import poll
27
- _poller = poll ()
28
27
_EVENT_MASK = (select .POLLIN | select .POLLPRI | select .POLLERR |
29
28
select .POLLHUP | select .POLLNVAL )
30
29
except ImportError :
31
30
_HAS_POLL = False
32
31
32
+ try :
33
+ from select import error as _SELECT_ERROR
34
+ except ImportError :
35
+ _SELECT_ERROR = OSError
36
+
33
37
from pymongo import helpers , message
34
38
from pymongo .common import MAX_MESSAGE_SIZE
35
39
from pymongo .errors import (AutoReconnect ,
@@ -159,12 +163,7 @@ def _receive_data_on_socket(sock, length):
159
163
try :
160
164
chunk = sock .recv (length )
161
165
except (IOError , OSError ) as exc :
162
- err = None
163
- if hasattr (exc , 'errno' ):
164
- err = exc .errno
165
- elif exc .args :
166
- err = exc .args [0 ]
167
- if err == errno .EINTR :
166
+ if _errno_from_exception (exc ) == errno .EINTR :
168
167
continue
169
168
raise
170
169
if chunk == b"" :
@@ -176,17 +175,56 @@ def _receive_data_on_socket(sock, length):
176
175
return msg
177
176
178
177
179
- def socket_closed (sock ):
180
- """Return True if we know socket has been closed, False otherwise.
181
- """
182
- try :
178
+ def _errno_from_exception (exc ):
179
+ if hasattr (exc , 'errno' ):
180
+ return exc .errno
181
+ elif exc .args :
182
+ return exc .args [0 ]
183
+ else :
184
+ return None
185
+
186
+
187
+ class SocketChecker (object ):
188
+
189
+ def __init__ (self ):
183
190
if _HAS_POLL :
184
- _poller .register (sock , _EVENT_MASK )
185
- rd = _poller .poll (0 )
186
- _poller .unregister (sock )
191
+ self ._lock = threading .Lock ()
192
+ self ._poller = poll ()
187
193
else :
188
- rd , _ , _ = select .select ([sock ], [], [], 0 )
189
- # Any exception here is equally bad (select.error, ValueError, etc.).
190
- except :
191
- return True
192
- return len (rd ) > 0
194
+ self ._lock = None
195
+ self ._poller = None
196
+
197
+ def socket_closed (self , sock ):
198
+ """Return True if we know socket has been closed, False otherwise.
199
+ """
200
+ while True :
201
+ try :
202
+ if self ._poller :
203
+ with self ._lock :
204
+ self ._poller .register (sock , _EVENT_MASK )
205
+ try :
206
+ rd = self ._poller .poll (0 )
207
+ finally :
208
+ self ._poller .unregister (sock )
209
+ else :
210
+ rd , _ , _ = select .select ([sock ], [], [], 0 )
211
+ except (RuntimeError , KeyError ):
212
+ # RuntimeError is raised during a concurrent poll. KeyError
213
+ # is raised by unregister if the socket is not in the poller.
214
+ # These errors should not be possible since we protect the
215
+ # poller with a mutex.
216
+ raise
217
+ except ValueError :
218
+ # ValueError is raised by register/unregister/select if the
219
+ # socket file descriptor is negative or outside the range for
220
+ # select (> 1023).
221
+ return True
222
+ except (_SELECT_ERROR , IOError ) as exc :
223
+ if _errno_from_exception (exc ) in (errno .EINTR , errno .EAGAIN ):
224
+ continue
225
+ return True
226
+ except :
227
+ # Any other exceptions should be attributed to a closed
228
+ # or invalid socket.
229
+ return True
230
+ return len (rd ) > 0
0 commit comments