Skip to content

Commit 4152622

Browse files
A. Jesse Jiryu Davisbehackett
authored andcommitted
Discard a socket after an exception to ensure we don't have inconsistent state PYTHON-294
1 parent cc4e6c8 commit 4152622

File tree

2 files changed

+74
-2
lines changed

2 files changed

+74
-2
lines changed

pymongo/connection.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ def get_socket(self, host, port):
165165
self.sock = (pid, self.connect(host, port))
166166
return (self.sock[1], False)
167167

168+
def discard_socket(self):
169+
"""Close and discard the active socket.
170+
"""
171+
if self.sock:
172+
self.sock[1].close()
173+
self.sock = None
168174

169175
def return_socket(self):
170176
if self.sock is not None and self.sock[0] == os.getpid():
@@ -827,7 +833,18 @@ def __receive_data_on_socket(self, length, sock):
827833
"""
828834
message = ""
829835
while len(message) < length:
830-
chunk = sock.recv(length - len(message))
836+
try:
837+
chunk = sock.recv(length - len(message))
838+
except BaseException, e:
839+
# PYTHON-294: must close socket here, because if it remains open
840+
# after this then the next time we read from it we may get stale
841+
# data from a previous operation.
842+
try:
843+
self.__pool.discard_socket()
844+
except:
845+
pass
846+
raise e
847+
831848
if chunk == "":
832849
raise ConnectionFailure("connection closed")
833850
message += chunk
@@ -877,7 +894,11 @@ def _send_message_with_response(self, message,
877894
raise AutoReconnect(str(e))
878895
finally:
879896
if "network_timeout" in kwargs:
880-
sock.settimeout(self.__net_timeout)
897+
try:
898+
sock.settimeout(self.__net_timeout)
899+
except socket.error:
900+
# There was an exception and we've closed the socket
901+
pass
881902

882903
def start_request(self):
883904
"""DEPRECATED all operations will start a request.

test/test_connection.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,57 @@ def test_contextlib(self):
476476
self.assertEqual(None, connection._Connection__pool.sock)
477477
self.assertEqual(0, len(connection._Connection__pool.sockets))
478478

479+
def test_interrupt_signal(self):
480+
"""
481+
Test fix for PYTHON-294 -- make sure Connection closes its socket if it
482+
gets an interrupt while waiting to recv() from it.
483+
"""
484+
import pymongo
485+
import signal
486+
487+
c = get_connection()
488+
db = c.pymongo_test
489+
490+
# A $where clause which takes 1 sec to execute
491+
where = '''function() {
492+
var d = new Date((new Date()).getTime() + 1*1000);
493+
while (d > (new Date())) { }; return true;
494+
}'''
495+
496+
# Need exactly 1 document so find() will execute its $where clause once
497+
db.drop_collection('foo')
498+
db.foo.insert({'_id': 1}, safe=True)
499+
500+
# Convert SIGALRM to SIGINT -- it's hard to schedule a SIGINT for 1/2 a
501+
# second in the future, but easy to schedule SIGALRM.
502+
def sigalarm(num, frame):
503+
import sys
504+
raise KeyboardInterrupt
505+
506+
old_signal_handler = signal.signal(signal.SIGALRM, sigalarm)
507+
signal.setitimer(signal.ITIMER_REAL, 0.5, 0)
508+
raised = False
509+
try:
510+
# Need list() to actually iterate the cursor and create the
511+
# cursor on the server; find is lazily evaluated. The find() will
512+
# be interrupted by sigalarm() raising a KeyboardInterrupt.
513+
list(db.foo.find({'$where': where}))
514+
except KeyboardInterrupt:
515+
raised = True
516+
finally:
517+
signal.signal(signal.SIGALRM, old_signal_handler)
518+
519+
# Can't use self.assertRaises() because it doesn't catch system
520+
# exceptions
521+
self.assert_(raised, "Didn't raise expected KeyboardInterrupt")
522+
523+
# Raises AssertionError due to PYTHON-294 -- Mongo's response to the
524+
# previous find() is still waiting to be read on the socket, so the
525+
# request id's don't match.
526+
self.assertEqual(
527+
[{'_id': 1}],
528+
list(db.foo.find())
529+
)
479530

480531
if __name__ == "__main__":
481532
unittest.main()

0 commit comments

Comments
 (0)