Skip to content

Commit d320162

Browse files
committed
PYTHON-736 - Don't close sockets on OperationFailure
This also speeds up returning exhaust sockets to the pool when the server returns an error and fixes the tests to run against all MongoDB versions we test against.
1 parent 9ad421a commit d320162

File tree

2 files changed

+19
-22
lines changed

2 files changed

+19
-22
lines changed

pymongo/cursor.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
from pymongo import helpers, message, read_preferences
2323
from pymongo.read_preferences import ReadPreference, secondary_ok_commands
2424
from pymongo.errors import (AutoReconnect,
25-
CursorNotFound,
26-
InvalidOperation)
25+
InvalidOperation,
26+
OperationFailure)
2727

2828
_QUERY_OPTIONS = {
2929
"tailable_cursor": 2,
@@ -927,7 +927,7 @@ def __send_message(self, message):
927927
# Exhaust cursor - no getMore message.
928928
try:
929929
response = client._exhaust_next(self.__exhaust_mgr.sock)
930-
except:
930+
except AutoReconnect:
931931
self.__killed = True
932932
self.__exhaust_mgr.error()
933933
raise
@@ -938,8 +938,10 @@ def __send_message(self, message):
938938
self.__tz_aware,
939939
self.__uuid_subtype,
940940
self.__compile_re)
941-
except CursorNotFound:
941+
except OperationFailure:
942942
self.__killed = True
943+
# Make sure exhaust socket is returned immediately, if necessary.
944+
self.__die()
943945
# If this is a tailable cursor the error is likely
944946
# due to capped collection roll over. Setting
945947
# self.__killed to True ensures Cursor.alive will be
@@ -951,6 +953,8 @@ def __send_message(self, message):
951953
# Don't send kill cursors to another server after a "not master"
952954
# error. It's completely pointless.
953955
self.__killed = True
956+
# Make sure exhaust socket is returned immediately, if necessary.
957+
self.__die()
954958
client.disconnect()
955959
raise
956960

test/utils.py

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import time
2424

2525
from nose.plugins.skip import SkipTest
26+
27+
from bson.son import SON
2628
from pymongo import MongoClient, MongoReplicaSetClient
2729
from pymongo.errors import AutoReconnect, ConnectionFailure, OperationFailure
2830
from pymongo.pool import NO_REQUEST, NO_SOCKET_YET, SocketInfo
@@ -588,15 +590,6 @@ def test_max_bson_size(self):
588590
c.max_message_size)
589591

590592

591-
def collect_until(fn):
592-
start = time.time()
593-
while not fn():
594-
if (time.time() - start) > 5:
595-
raise AssertionError("timed out")
596-
597-
gc.collect()
598-
599-
600593
class _TestExhaustCursorMixin(object):
601594
"""Test that clients properly handle errors from exhaust cursors.
602595
@@ -609,15 +602,18 @@ def test_exhaust_query_server_error(self):
609602
client = self._get_client(max_pool_size=1)
610603
if is_mongos(client):
611604
raise SkipTest("Can't use exhaust cursors with mongos")
605+
if not version.at_least(client, (2, 2, 0)):
606+
raise SkipTest("mongod < 2.2.0 closes exhaust socket on error")
612607

613608
collection = client.pymongo_test.test
614609
pool = get_pool(client)
615610

616611
sock_info = one(pool.sockets)
617-
cursor = collection.find({'$bad_query_operator': 1}, exhaust=True)
612+
# This will cause OperationFailure in all mongo versions since
613+
# the value for $orderby must be a document.
614+
cursor = collection.find(
615+
SON([('$query', {}), ('$orderby', True)]), exhaust=True)
618616
self.assertRaises(OperationFailure, cursor.next)
619-
del cursor
620-
collect_until(lambda: sock_info in pool.sockets)
621617
self.assertFalse(sock_info.closed)
622618

623619
# The semaphore was decremented despite the error.
@@ -639,7 +635,7 @@ def test_exhaust_getmore_server_error(self):
639635

640636
# Enough data to ensure it streams down for a few milliseconds.
641637
long_str = 'a' * (256 * 1024)
642-
collection.insert([{'a': long_str} for _ in range(1000)])
638+
collection.insert([{'a': long_str} for _ in range(200)])
643639

644640
pool = get_pool(client)
645641
pool._check_interval_seconds = None # Never check.
@@ -653,12 +649,9 @@ def test_exhaust_getmore_server_error(self):
653649
# Cause a server error on getmore.
654650
client2.pymongo_test.test.drop()
655651
self.assertRaises(OperationFailure, list, cursor)
656-
del cursor
657-
collect_until(lambda: sock_info.closed)
658-
self.assertFalse(sock_info in pool.sockets)
659652

660-
# The semaphore was decremented despite the error.
661-
self.assertTrue(pool._socket_semaphore.acquire(blocking=False))
653+
# Make sure the socket is still valid
654+
self.assertEqual(0, collection.count())
662655

663656
def test_exhaust_query_network_error(self):
664657
# When doing an exhaust query, the socket stays checked out on success

0 commit comments

Comments
 (0)