Skip to content

Commit 6731c87

Browse files
author
A. Jesse Jiryu Davis
committed
Don't reuse socket after an exception PYTHON-398
Prevent sockets from being returned to the pool if they're in an inconsistent state due to an async exception from PyThreadState_SetAsyncExc(). Connection and ReplicaSetConnection always use sockets the same way: first sendall() with a request, then __receive_message_on_socket() to get the response. (Named __recv_msg in RSC.) A socket is vulnerable to async exceptions starting at sendall() and ending after __receive_message_on_socket(). If both are completed, then the socket can be returned to the pool. If an exception's raised during this period, the socket must be closed. I wrap each section comprising sendall() and __receive_message_on_socket() like: try: sock_info.sock.sendall(msg) response = self.__receive_message_on_socket(1, rqst_id, sock_info) except: sock_info.close() raise Since the socket's pool of origin isn't easily available in every function where the socket must be closed, I close it directly instead of calling pool.discard_socket(sock_info). This is safe, because Pool checks sock_info.closed everywhere that matters, so the socket won't be reused after it's closed. Calling close() instead of discard_socket() also keeps Connection and RSC from further divergence, which is good in case we ever refactor them.
1 parent 4d69ed3 commit 6731c87

File tree

2 files changed

+26
-14
lines changed

2 files changed

+26
-14
lines changed

pymongo/connection.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -511,8 +511,13 @@ def __simple_command(self, sock_info, dbname, spec):
511511
"""
512512
rqst_id, msg, _ = message.query(0, dbname + '.$cmd', 0, -1, spec)
513513
start = time.time()
514-
sock_info.sock.sendall(msg)
515-
response = self.__receive_message_on_socket(1, rqst_id, sock_info)
514+
try:
515+
sock_info.sock.sendall(msg)
516+
response = self.__receive_message_on_socket(1, rqst_id, sock_info)
517+
except:
518+
sock_info.close()
519+
raise
520+
516521
end = time.time()
517522
response = helpers._unpack_response(response)['data'][0]
518523
msg = "command %r failed: %%s" % spec
@@ -838,6 +843,9 @@ def _send_message(self, message, with_last_error=False):
838843
except (ConnectionFailure, socket.error), e:
839844
self.disconnect()
840845
raise AutoReconnect(str(e))
846+
except:
847+
sock_info.close()
848+
raise
841849

842850
def __receive_data_on_socket(self, length, sock_info):
843851
"""Lowest level receive operation.
@@ -847,12 +855,7 @@ def __receive_data_on_socket(self, length, sock_info):
847855
"""
848856
chunks = []
849857
while length:
850-
try:
851-
chunk = sock_info.sock.recv(length)
852-
except:
853-
# recv was interrupted
854-
self.__pool.discard_socket(sock_info)
855-
raise
858+
chunk = sock_info.sock.recv(length)
856859
if chunk == EMPTY:
857860
raise ConnectionFailure("connection closed")
858861
length -= len(chunk)
@@ -877,8 +880,12 @@ def __send_and_receive(self, message, sock_info):
877880
"""Send a message on the given socket and return the response data.
878881
"""
879882
(request_id, data) = self.__check_bson_size(message)
880-
sock_info.sock.sendall(data)
881-
return self.__receive_message_on_socket(1, request_id, sock_info)
883+
try:
884+
sock_info.sock.sendall(data)
885+
return self.__receive_message_on_socket(1, request_id, sock_info)
886+
except:
887+
sock_info.close()
888+
raise
882889

883890
# we just ignore _must_use_master here: it's only relevant for
884891
# MasterSlaveConnection instances.

pymongo/replica_set_connection.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -636,8 +636,13 @@ def __simple_command(self, sock_info, dbname, spec):
636636
"""
637637
rqst_id, msg, _ = message.query(0, dbname + '.$cmd', 0, -1, spec)
638638
start = time.time()
639-
sock_info.sock.sendall(msg)
640-
response = self.__recv_msg(1, rqst_id, sock_info)
639+
try:
640+
sock_info.sock.sendall(msg)
641+
response = self.__recv_msg(1, rqst_id, sock_info)
642+
except:
643+
sock_info.close()
644+
raise
645+
641646
end = time.time()
642647
response = helpers._unpack_response(response)['data'][0]
643648
msg = "command %r failed: %%s" % spec
@@ -1011,7 +1016,7 @@ def _send_message(self, msg, safe=False, _connection_to_use=None):
10111016
self.disconnect()
10121017
raise AutoReconnect(str(why))
10131018
except:
1014-
member.pool.discard_socket(sock_info)
1019+
sock_info.close()
10151020
raise
10161021

10171022
def __send_and_receive(self, member, msg, **kwargs):
@@ -1038,7 +1043,7 @@ def __send_and_receive(self, member, msg, **kwargs):
10381043
member.pool.discard_socket(sock_info)
10391044
raise AutoReconnect("%s:%d: %s" % (host, port, str(why)))
10401045
except:
1041-
member.pool.discard_socket(sock_info)
1046+
sock_info.close()
10421047
raise
10431048

10441049
def __try_read(self, member, msg, **kwargs):

0 commit comments

Comments
 (0)