Skip to content

Commit 5e2f249

Browse files
author
A. Jesse Jiryu Davis
committed
Don't close socket after operation failure PYTHON-395
A socket must be closed after a network error, but not after an error response to getLastError. Sockets were wrongly closed in this case if auto_start_request=False.
1 parent 6153ba2 commit 5e2f249

File tree

4 files changed

+84
-0
lines changed

4 files changed

+84
-0
lines changed

pymongo/connection.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,9 @@ def _send_message(self, message, with_last_error=False):
832832

833833
self.__pool.maybe_return_socket(sock_info)
834834
return rv
835+
except OperationFailure:
836+
self.__pool.maybe_return_socket(sock_info)
837+
raise
835838
except (ConnectionFailure, socket.error), e:
836839
self.disconnect()
837840
raise AutoReconnect(str(e))

pymongo/replica_set_connection.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,9 @@ def _send_message(self, msg, safe=False, _connection_to_use=None):
10021002
rv = self.__check_response_to_last_error(response)
10031003
member.pool.maybe_return_socket(sock_info)
10041004
return rv
1005+
except OperationFailure:
1006+
member.pool.maybe_return_socket(sock_info)
1007+
raise
10051008
except(ConnectionFailure, socket.error), why:
10061009
member.pool.discard_socket(sock_info)
10071010
if _connection_to_use in (None, -1):

test/test_connection.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,5 +623,42 @@ def interrupter():
623623
db.foo.find().next()
624624
)
625625

626+
def test_operation_failure_without_request(self):
627+
# Ensure Connection doesn't close socket after it gets an error
628+
# response to getLastError. PYTHON-395.
629+
c = get_connection(auto_start_request=False)
630+
pool = c._Connection__pool
631+
self.assertEqual(1, len(pool.sockets))
632+
old_sock_info = iter(pool.sockets).next()
633+
c.pymongo_test.test.drop()
634+
c.pymongo_test.test.insert({'_id': 'foo'}, safe=True)
635+
self.assertRaises(
636+
OperationFailure,
637+
c.pymongo_test.test.insert, {'_id': 'foo'}, safe=True)
638+
639+
self.assertEqual(1, len(pool.sockets))
640+
new_sock_info = iter(pool.sockets).next()
641+
self.assertEqual(old_sock_info, new_sock_info)
642+
643+
def test_operation_failure_with_request(self):
644+
# Ensure Connection doesn't close socket after it gets an error
645+
# response to getLastError. PYTHON-395.
646+
c = get_connection(auto_start_request=True)
647+
pool = c._Connection__pool
648+
649+
# Connection has reserved a socket for this thread
650+
self.assertTrue(isinstance(pool._get_request_state(), SocketInfo))
651+
652+
old_sock_info = pool._get_request_state()
653+
c.pymongo_test.test.drop()
654+
c.pymongo_test.test.insert({'_id': 'foo'}, safe=True)
655+
self.assertRaises(
656+
OperationFailure,
657+
c.pymongo_test.test.insert, {'_id': 'foo'}, safe=True)
658+
659+
# OperationFailure doesn't affect the request socket
660+
self.assertEqual(old_sock_info, pool._get_request_state())
661+
662+
626663
if __name__ == "__main__":
627664
unittest.main()

test/test_replica_set_connection.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from pymongo.replica_set_connection import ReplicaSetConnection
3737
from pymongo.replica_set_connection import _partition_node
3838
from pymongo.database import Database
39+
from pymongo.pool import SocketInfo
3940
from pymongo.errors import (AutoReconnect,
4041
ConfigurationError,
4142
ConnectionFailure,
@@ -648,6 +649,46 @@ def sigalarm(num, frame):
648649
if old_signal_handler:
649650
signal.signal(signal.SIGALRM, old_signal_handler)
650651

652+
def test_operation_failure_without_request(self):
653+
# Ensure ReplicaSetConnection doesn't close socket after it gets an
654+
# error response to getLastError. PYTHON-395.
655+
c = self._get_connection(auto_start_request=False)
656+
pool = c._ReplicaSetConnection__members[self.primary].pool
657+
self.assertEqual(1, len(pool.sockets))
658+
old_sock_info = iter(pool.sockets).next()
659+
c.pymongo_test.test.drop()
660+
c.pymongo_test.test.insert({'_id': 'foo'}, safe=True)
661+
self.assertRaises(
662+
OperationFailure,
663+
c.pymongo_test.test.insert, {'_id': 'foo'}, safe=True)
664+
665+
self.assertEqual(1, len(pool.sockets))
666+
new_sock_info = iter(pool.sockets).next()
667+
668+
self.assertEqual(old_sock_info, new_sock_info)
669+
c.close()
670+
671+
def test_operation_failure_with_request(self):
672+
# Ensure ReplicaSetConnection doesn't close socket after it gets an
673+
# error response to getLastError. PYTHON-395.
674+
c = self._get_connection(auto_start_request=True)
675+
c.pymongo_test.test.find_one()
676+
pool = c._ReplicaSetConnection__members[self.primary].pool
677+
678+
# Connection has reserved a socket for this thread
679+
self.assertTrue(isinstance(pool._get_request_state(), SocketInfo))
680+
681+
old_sock_info = pool._get_request_state()
682+
c.pymongo_test.test.drop()
683+
c.pymongo_test.test.insert({'_id': 'foo'}, safe=True)
684+
self.assertRaises(
685+
OperationFailure,
686+
c.pymongo_test.test.insert, {'_id': 'foo'}, safe=True)
687+
688+
# OperationFailure doesn't affect the request socket
689+
self.assertEqual(old_sock_info, pool._get_request_state())
690+
c.close()
691+
651692
def test_auto_start_request(self):
652693
for bad_horrible_value in (None, 5, 'hi!'):
653694
self.assertRaises(

0 commit comments

Comments
 (0)