30
30
from bson import DEFAULT_CODEC_OPTIONS
31
31
from bson .py3compat import imap , itervalues , _unicode , PY3
32
32
from bson .son import SON
33
- from pymongo import auth , helpers , thread_util , __version__
33
+ from pymongo import auth , helpers , __version__
34
34
from pymongo .client_session import _validate_session_write_concern
35
35
from pymongo .common import (MAX_BSON_SIZE ,
36
36
MAX_CONNECTING ,
46
46
CertificateError ,
47
47
ConnectionFailure ,
48
48
ConfigurationError ,
49
+ ExceededMaxWaiters ,
49
50
InvalidOperation ,
50
51
DocumentTooLarge ,
51
52
NetworkTimeout ,
@@ -309,7 +310,8 @@ class PoolOptions(object):
309
310
'__wait_queue_timeout' , '__wait_queue_multiple' ,
310
311
'__ssl_context' , '__ssl_match_hostname' , '__socket_keepalive' ,
311
312
'__event_listeners' , '__appname' , '__driver' , '__metadata' ,
312
- '__compression_settings' , '__max_connecting' )
313
+ '__compression_settings' , '__max_connecting' ,
314
+ '__pause_enabled' )
313
315
314
316
def __init__ (self , max_pool_size = MAX_POOL_SIZE ,
315
317
min_pool_size = MIN_POOL_SIZE ,
@@ -318,7 +320,8 @@ def __init__(self, max_pool_size=MAX_POOL_SIZE,
318
320
wait_queue_multiple = None , ssl_context = None ,
319
321
ssl_match_hostname = True , socket_keepalive = True ,
320
322
event_listeners = None , appname = None , driver = None ,
321
- compression_settings = None , max_connecting = MAX_CONNECTING ):
323
+ compression_settings = None , max_connecting = MAX_CONNECTING ,
324
+ pause_enabled = True ):
322
325
323
326
self .__max_pool_size = max_pool_size
324
327
self .__min_pool_size = min_pool_size
@@ -335,6 +338,7 @@ def __init__(self, max_pool_size=MAX_POOL_SIZE,
335
338
self .__driver = driver
336
339
self .__compression_settings = compression_settings
337
340
self .__max_connecting = max_connecting
341
+ self .__pause_enabled = pause_enabled
338
342
self .__metadata = copy .deepcopy (_METADATA )
339
343
if appname :
340
344
self .__metadata ['application' ] = {'name' : appname }
@@ -406,6 +410,10 @@ def max_connecting(self):
406
410
"""
407
411
return self .__max_connecting
408
412
413
+ @property
414
+ def pause_enabled (self ):
415
+ return self .__pause_enabled
416
+
409
417
@property
410
418
def max_idle_time_seconds (self ):
411
419
"""The maximum number of seconds that a connection can remain
@@ -1058,6 +1066,12 @@ class _PoolClosedError(PyMongoError):
1058
1066
pass
1059
1067
1060
1068
1069
+ class PoolState (object ):
1070
+ PAUSED = 1
1071
+ READY = 2
1072
+ CLOSED = 3
1073
+
1074
+
1061
1075
# Do *not* explicitly inherit from object or Jython won't call __del__
1062
1076
# http://bugs.jython.org/issue1057
1063
1077
class Pool :
@@ -1068,6 +1082,10 @@ def __init__(self, address, options, handshake=True):
1068
1082
- `options`: a PoolOptions instance
1069
1083
- `handshake`: whether to call ismaster for each new SocketInfo
1070
1084
"""
1085
+ if options .pause_enabled :
1086
+ self .state = PoolState .PAUSED
1087
+ else :
1088
+ self .state = PoolState .READY
1071
1089
# Check a socket's health with socket_closed() every once in a while.
1072
1090
# Can override for testing: 0 to always check, None to never check.
1073
1091
self ._check_interval_seconds = 1
@@ -1079,7 +1097,6 @@ def __init__(self, address, options, handshake=True):
1079
1097
self .active_sockets = 0
1080
1098
# Monotonically increasing connection ID required for CMAP Events.
1081
1099
self .next_connection_id = 1
1082
- self .closed = False
1083
1100
# Track whether the sockets in this pool are writeable or not.
1084
1101
self .is_writable = None
1085
1102
@@ -1098,13 +1115,23 @@ def __init__(self, address, options, handshake=True):
1098
1115
1099
1116
if (self .opts .wait_queue_multiple is None or
1100
1117
self .opts .max_pool_size is None ):
1101
- max_waiters = None
1118
+ max_waiters = float ( 'inf' )
1102
1119
else :
1103
1120
max_waiters = (
1104
1121
self .opts .max_pool_size * self .opts .wait_queue_multiple )
1105
-
1106
- self ._socket_semaphore = thread_util .create_semaphore (
1107
- self .opts .max_pool_size , max_waiters )
1122
+ # The first portion of the wait queue.
1123
+ # Enforces: maxPoolSize and waitQueueMultiple
1124
+ # Also used for: clearing the wait queue
1125
+ self .size_cond = threading .Condition (self .lock )
1126
+ self .requests = 0
1127
+ self .max_pool_size = self .opts .max_pool_size
1128
+ if self .max_pool_size is None :
1129
+ self .max_pool_size = float ('inf' )
1130
+ self .waiters = 0
1131
+ self .max_waiters = max_waiters
1132
+ # The second portion of the wait queue.
1133
+ # Enforces: maxConnecting
1134
+ # Also used for: clearing the wait queue
1108
1135
self ._max_connecting_cond = threading .Condition (self .lock )
1109
1136
self ._max_connecting = self .opts .max_connecting
1110
1137
self ._pending = 0
@@ -1114,10 +1141,23 @@ def __init__(self, address, options, handshake=True):
1114
1141
# Similar to active_sockets but includes threads in the wait queue.
1115
1142
self .operation_count = 0
1116
1143
1117
- def _reset (self , close ):
1118
- with self .lock :
1144
+ def ready (self ):
1145
+ old_state , self .state = self .state , PoolState .READY
1146
+ if old_state != PoolState .READY :
1147
+ if self .enabled_for_cmap :
1148
+ self .opts .event_listeners .publish_pool_ready (self .address )
1149
+
1150
+ @property
1151
+ def closed (self ):
1152
+ return self .state == PoolState .CLOSED
1153
+
1154
+ def _reset (self , close , pause = True ):
1155
+ old_state = self .state
1156
+ with self .size_cond :
1119
1157
if self .closed :
1120
1158
return
1159
+ if self .opts .pause_enabled and pause :
1160
+ old_state , self .state = self .state , PoolState .PAUSED
1121
1161
self .generation += 1
1122
1162
newpid = os .getpid ()
1123
1163
if self .pid != newpid :
@@ -1126,7 +1166,10 @@ def _reset(self, close):
1126
1166
self .operation_count = 0
1127
1167
sockets , self .sockets = self .sockets , collections .deque ()
1128
1168
if close :
1129
- self .closed = True
1169
+ self .state = PoolState .CLOSED
1170
+ # Clear the wait queue
1171
+ self ._max_connecting_cond .notify_all ()
1172
+ self .size_cond .notify_all ()
1130
1173
1131
1174
listeners = self .opts .event_listeners
1132
1175
# CMAP spec says that close() MUST close sockets before publishing the
@@ -1138,7 +1181,7 @@ def _reset(self, close):
1138
1181
if self .enabled_for_cmap :
1139
1182
listeners .publish_pool_closed (self .address )
1140
1183
else :
1141
- if self .enabled_for_cmap :
1184
+ if old_state != PoolState . PAUSED and self .enabled_for_cmap :
1142
1185
listeners .publish_pool_cleared (self .address )
1143
1186
for sock_info in sockets :
1144
1187
sock_info .close_socket (ConnectionClosedReason .STALE )
@@ -1155,6 +1198,9 @@ def update_is_writable(self, is_writable):
1155
1198
def reset (self ):
1156
1199
self ._reset (close = False )
1157
1200
1201
+ def reset_without_pause (self ):
1202
+ self ._reset (close = False , pause = False )
1203
+
1158
1204
def close (self ):
1159
1205
self ._reset (close = True )
1160
1206
@@ -1164,6 +1210,9 @@ def remove_stale_sockets(self, reference_generation, all_credentials):
1164
1210
`generation` at the point in time this operation was requested on the
1165
1211
pool.
1166
1212
"""
1213
+ if self .state != PoolState .READY :
1214
+ return
1215
+
1167
1216
if self .opts .max_idle_time_seconds is not None :
1168
1217
with self .lock :
1169
1218
while (self .sockets and
@@ -1172,15 +1221,14 @@ def remove_stale_sockets(self, reference_generation, all_credentials):
1172
1221
sock_info .close_socket (ConnectionClosedReason .IDLE )
1173
1222
1174
1223
while True :
1175
- with self .lock :
1224
+ with self .size_cond :
1225
+ # There are enough sockets in the pool.
1176
1226
if (len (self .sockets ) + self .active_sockets >=
1177
1227
self .opts .min_pool_size ):
1178
- # There are enough sockets in the pool.
1179
1228
return
1180
-
1181
- # We must acquire the semaphore to respect max_pool_size.
1182
- if not self ._socket_semaphore .acquire (False ):
1183
- return
1229
+ if self .requests >= self .opts .min_pool_size :
1230
+ return
1231
+ self .requests += 1
1184
1232
incremented = False
1185
1233
try :
1186
1234
with self ._max_connecting_cond :
@@ -1204,7 +1252,10 @@ def remove_stale_sockets(self, reference_generation, all_credentials):
1204
1252
with self ._max_connecting_cond :
1205
1253
self ._pending -= 1
1206
1254
self ._max_connecting_cond .notify ()
1207
- self ._socket_semaphore .release ()
1255
+
1256
+ with self .size_cond :
1257
+ self .requests -= 1
1258
+ self .size_cond .notify ()
1208
1259
1209
1260
def connect (self , all_credentials = None ):
1210
1261
"""Connect to Mongo and return a new SocketInfo.
@@ -1289,6 +1340,14 @@ def get_socket(self, all_credentials, checkout=False):
1289
1340
if not checkout :
1290
1341
self .return_socket (sock_info )
1291
1342
1343
+ def _raise_if_not_ready (self , emit_event ):
1344
+ if self .state != PoolState .READY :
1345
+ if self .enabled_for_cmap and emit_event :
1346
+ self .opts .event_listeners .publish_connection_check_out_failed (
1347
+ self .address , ConnectionCheckOutFailedReason .CONN_ERROR )
1348
+ _raise_connection_failure (
1349
+ self .address , AutoReconnect ('connection pool paused' ))
1350
+
1292
1351
def _get_socket (self , all_credentials ):
1293
1352
"""Get or create a SocketInfo. Can raise ConnectionFailure."""
1294
1353
# We use the pid here to avoid issues with fork / multiprocessing.
@@ -1313,9 +1372,26 @@ def _get_socket(self, all_credentials):
1313
1372
deadline = _time () + self .opts .wait_queue_timeout
1314
1373
else :
1315
1374
deadline = None
1316
- if not self ._socket_semaphore .acquire (
1317
- True , self .opts .wait_queue_timeout ):
1318
- self ._raise_wait_queue_timeout ()
1375
+
1376
+ with self .size_cond :
1377
+ self ._raise_if_not_ready (emit_event = True )
1378
+ if self .waiters >= self .max_waiters :
1379
+ raise ExceededMaxWaiters (
1380
+ 'exceeded max waiters: %s threads already waiting' % (
1381
+ self .waiters ))
1382
+ self .waiters += 1
1383
+ try :
1384
+ while not (self .requests < self .max_pool_size ):
1385
+ if not _cond_wait (self .size_cond , deadline ):
1386
+ # Timed out, notify the next thread to ensure a
1387
+ # timeout doesn't consume the condition.
1388
+ if self .requests < self .max_pool_size :
1389
+ self .size_cond .notify ()
1390
+ self ._raise_wait_queue_timeout ()
1391
+ self ._raise_if_not_ready (emit_event = True )
1392
+ finally :
1393
+ self .waiters -= 1
1394
+ self .requests += 1
1319
1395
1320
1396
# We've now acquired the semaphore and must release it on error.
1321
1397
sock_info = None
@@ -1330,6 +1406,7 @@ def _get_socket(self, all_credentials):
1330
1406
# CMAP: we MUST wait for either maxConnecting OR for a socket
1331
1407
# to be checked back into the pool.
1332
1408
with self ._max_connecting_cond :
1409
+ self ._raise_if_not_ready (emit_event = False )
1333
1410
while not (self .sockets or
1334
1411
self ._pending < self ._max_connecting ):
1335
1412
if not _cond_wait (self ._max_connecting_cond , deadline ):
@@ -1340,6 +1417,7 @@ def _get_socket(self, all_credentials):
1340
1417
self ._max_connecting_cond .notify ()
1341
1418
emitted_event = True
1342
1419
self ._raise_wait_queue_timeout ()
1420
+ self ._raise_if_not_ready (emit_event = False )
1343
1421
1344
1422
try :
1345
1423
sock_info = self .sockets .popleft ()
@@ -1361,11 +1439,11 @@ def _get_socket(self, all_credentials):
1361
1439
if sock_info :
1362
1440
# We checked out a socket but authentication failed.
1363
1441
sock_info .close_socket (ConnectionClosedReason .ERROR )
1364
- self ._socket_semaphore .release ()
1365
-
1366
- if incremented :
1367
- with self .lock :
1442
+ with self .size_cond :
1443
+ self .requests -= 1
1444
+ if incremented :
1368
1445
self .active_sockets -= 1
1446
+ self .size_cond .notify ()
1369
1447
1370
1448
if self .enabled_for_cmap and not emitted_event :
1371
1449
self .opts .event_listeners .publish_connection_check_out_failed (
@@ -1401,10 +1479,11 @@ def return_socket(self, sock_info):
1401
1479
# Notify any threads waiting to create a connection.
1402
1480
self ._max_connecting_cond .notify ()
1403
1481
1404
- self ._socket_semaphore . release ()
1405
- with self .lock :
1482
+ with self .size_cond :
1483
+ self .requests -= 1
1406
1484
self .active_sockets -= 1
1407
1485
self .operation_count -= 1
1486
+ self .size_cond .notify ()
1408
1487
1409
1488
def _perished (self , sock_info ):
1410
1489
"""Return True and close the connection if it is "perished".
0 commit comments