Skip to content

Commit e1a7bc5

Browse files
committed
PYTHON-732 Handle network errors when adding existing credentials to sockets.
1 parent a1f7a54 commit e1a7bc5

File tree

3 files changed

+81
-4
lines changed

3 files changed

+81
-4
lines changed

pymongo/mongo_client.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -673,13 +673,16 @@ def max_write_batch_size(self):
673673
'max_write_batch_size', common.MAX_WRITE_BATCH_SIZE)
674674

675675
def __simple_command(self, sock_info, dbname, spec):
676-
"""Send a command to the server.
676+
"""Send a command to the server. May raise AutoReconnect.
677677
"""
678678
rqst_id, msg, _ = message.query(0, dbname + '.$cmd', 0, -1, spec)
679679
start = time.time()
680680
try:
681681
sock_info.sock.sendall(msg)
682682
response = self.__receive_message_on_socket(1, rqst_id, sock_info)
683+
except socket.error, e:
684+
sock_info.close()
685+
raise AutoReconnect(e)
683686
except:
684687
sock_info.close()
685688
raise
@@ -916,7 +919,7 @@ def __socket(self, member):
916919
"%s %s" % (host_details, str(why)))
917920
try:
918921
self.__check_auth(sock_info)
919-
except OperationFailure:
922+
except:
920923
connection_pool.maybe_return_socket(sock_info)
921924
raise
922925
return sock_info

test/test_client.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from pymongo.mongo_client import MongoClient
3535
from pymongo.database import Database
3636
from pymongo.pool import SocketInfo
37-
from pymongo import thread_util, common
37+
from pymongo import auth, thread_util, common
3838
from pymongo.errors import (AutoReconnect,
3939
ConfigurationError,
4040
ConnectionFailure,
@@ -54,7 +54,8 @@
5454
_TestLazyConnectMixin,
5555
lazy_client_trial,
5656
NTHREADS,
57-
get_pool)
57+
get_pool,
58+
one)
5859

5960

6061
def get_client(*args, **kwargs):
@@ -999,6 +1000,42 @@ def test_lazy_connect_w0(self):
9991000
client = get_client(_connect=False)
10001001
client.pymongo_test.test.remove(w=0)
10011002

1003+
def test_auth_network_error(self):
1004+
# Make sure there's no semaphore leak if we get a network error
1005+
# when authenticating a new socket with cached credentials.
1006+
auth_client = get_client()
1007+
if not server_started_with_auth(auth_client):
1008+
raise SkipTest('Authentication is not enabled on server')
1009+
1010+
auth_client.admin.add_user('admin', 'password')
1011+
auth_client.admin.authenticate('admin', 'password')
1012+
try:
1013+
# Get a client with one socket so we detect if it's leaked.
1014+
c = get_client(max_pool_size=1, waitQueueTimeoutMS=1)
1015+
1016+
# Simulate an authenticate() call on a different socket.
1017+
credentials = auth._build_credentials_tuple(
1018+
'MONGODB-CR', 'admin',
1019+
unicode('admin'), unicode('password'),
1020+
{})
1021+
1022+
c._cache_credentials('test', credentials, connect=False)
1023+
1024+
# Cause a network error on the actual socket.
1025+
pool = get_pool(c)
1026+
socket_info = one(pool.sockets)
1027+
socket_info.sock.close()
1028+
1029+
# In __check_auth, the client authenticates its socket with the
1030+
# new credential, but gets a socket.error. Should be reraised as
1031+
# AutoReconnect.
1032+
self.assertRaises(AutoReconnect, c.test.collection.find_one)
1033+
1034+
# No semaphore leak, the pool is allowed to make a new socket.
1035+
c.test.collection.find_one()
1036+
finally:
1037+
remove_all_users(auth_client.admin)
1038+
10021039

10031040
class TestClientLazyConnect(unittest.TestCase, _TestLazyConnectMixin):
10041041
def _get_client(self, **kwargs):

test/test_replica_set_client.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import threading
2727
import traceback
2828
import unittest
29+
from pymongo import auth
2930

3031
sys.path[0:0] = [""]
3132

@@ -1126,6 +1127,42 @@ def test_alive(self):
11261127

11271128
self.assertFalse(client.alive())
11281129

1130+
def test_auth_network_error(self):
1131+
# Make sure there's no semaphore leak if we get a network error
1132+
# when authenticating a new socket with cached credentials.
1133+
auth_client = self._get_client()
1134+
if not server_started_with_auth(auth_client):
1135+
raise SkipTest('Authentication is not enabled on server')
1136+
1137+
auth_client.admin.add_user('admin', 'password')
1138+
auth_client.admin.authenticate('admin', 'password')
1139+
try:
1140+
# Get a client with one socket so we detect if it's leaked.
1141+
c = self._get_client(max_pool_size=1, waitQueueTimeoutMS=1)
1142+
1143+
# Simulate an authenticate() call on a different socket.
1144+
credentials = auth._build_credentials_tuple(
1145+
'MONGODB-CR', 'admin',
1146+
unicode('admin'), unicode('password'),
1147+
{})
1148+
1149+
c._cache_credentials('test', credentials, connect=False)
1150+
1151+
# Cause a network error on the actual socket.
1152+
pool = get_pool(c)
1153+
socket_info = one(pool.sockets)
1154+
socket_info.sock.close()
1155+
1156+
# In __check_auth, the client authenticates its socket with the
1157+
# new credential, but gets a socket.error. Should be reraised as
1158+
# AutoReconnect.
1159+
self.assertRaises(AutoReconnect, c.test.collection.find_one)
1160+
1161+
# No semaphore leak, the pool is allowed to make a new socket.
1162+
c.test.collection.find_one()
1163+
finally:
1164+
remove_all_users(auth_client.admin)
1165+
11291166

11301167
class TestReplicaSetWireVersion(unittest.TestCase):
11311168
def test_wire_version(self):

0 commit comments

Comments
 (0)