3434 "partial" : 128 }
3535
3636
37+ # This has to be an old style class due to
38+ # http://bugs.jython.org/issue1057
39+ class _SocketManager :
40+ """Used with exhaust cursors to ensure the socket is returned.
41+ """
42+ def __init__ (self , sock , pool ):
43+ self .sock = sock
44+ self .pool = pool
45+ self .__closed = False
46+
47+ def __del__ (self ):
48+ self .close ()
49+
50+ def close (self ):
51+ """Return this instance's socket to the connection pool.
52+ """
53+ if not self .__closed :
54+ self .__closed = True
55+ self .pool .maybe_return_socket (self .sock )
56+ self .sock , self .pool = None , None
57+
58+
3759# TODO might be cool to be able to do find().include("foo") or
3860# find().exclude(["bar", "baz"]) or find().slice("a", 1, 2) as an
3961# alternative to the fields specifier.
@@ -46,7 +68,7 @@ def __init__(self, collection, spec=None, fields=None, skip=0, limit=0,
4668 max_scan = None , as_class = None , slave_okay = False ,
4769 await_data = False , partial = False , manipulate = True ,
4870 read_preference = ReadPreference .PRIMARY , tag_sets = [{}],
49- secondary_acceptable_latency_ms = None ,
71+ secondary_acceptable_latency_ms = None , exhaust = False ,
5072 _must_use_master = False , _uuid_subtype = None , ** kwargs ):
5173 """Create a new cursor.
5274
@@ -78,6 +100,8 @@ def __init__(self, collection, spec=None, fields=None, skip=0, limit=0,
78100 raise TypeError ("await_data must be an instance of bool" )
79101 if not isinstance (partial , bool ):
80102 raise TypeError ("partial must be an instance of bool" )
103+ if not isinstance (exhaust , bool ):
104+ raise TypeError ("exhaust must be an instance of bool" )
81105
82106 if fields is not None :
83107 if not fields :
@@ -95,6 +119,15 @@ def __init__(self, collection, spec=None, fields=None, skip=0, limit=0,
95119 self .__limit = limit
96120 self .__batch_size = 0
97121
122+ # Exhaust cursor support
123+ if self .__collection .database .connection .is_mongos and exhaust :
124+ raise InvalidOperation ('Exhaust cursors are '
125+ 'not supported by mongos' )
126+ if limit and exhaust :
127+ raise InvalidOperation ("Can't use limit and exhaust together." )
128+ self .__exhaust = exhaust
129+ self .__exhaust_mgr = None
130+
98131 # This is ugly. People want to be able to do cursor[5:5] and
99132 # get an empty result set (old behavior was an
100133 # exception). It's hard to do that right, though, because the
@@ -193,11 +226,19 @@ def __die(self):
193226 """Closes this cursor.
194227 """
195228 if self .__id and not self .__killed :
196- connection = self .__collection .database .connection
197- if self .__connection_id is not None :
198- connection .close_cursor (self .__id , self .__connection_id )
229+ if self .__exhaust and self .__exhaust_mgr :
230+ # If this is an exhaust cursor and we haven't completely
231+ # exhausted the result set we *must* close the socket
232+ # to stop the server from sending more data.
233+ self .__exhaust_mgr .sock .close ()
199234 else :
200- connection .close_cursor (self .__id )
235+ connection = self .__collection .database .connection
236+ if self .__connection_id is not None :
237+ connection .close_cursor (self .__id , self .__connection_id )
238+ else :
239+ connection .close_cursor (self .__id )
240+ if self .__exhaust and self .__exhaust_mgr :
241+ self .__exhaust_mgr .close ()
201242 self .__killed = True
202243
203244 def close (self ):
@@ -299,6 +340,8 @@ def __query_options(self):
299340 options |= _QUERY_OPTIONS ["no_timeout" ]
300341 if self .__await_data :
301342 options |= _QUERY_OPTIONS ["await_data" ]
343+ if self .__exhaust :
344+ options |= _QUERY_OPTIONS ["exhaust" ]
302345 if self .__partial :
303346 options |= _QUERY_OPTIONS ["partial" ]
304347 return options
@@ -319,6 +362,14 @@ def add_option(self, mask):
319362 raise TypeError ("mask must be an int" )
320363 self .__check_okay_to_chain ()
321364
365+ if mask & _QUERY_OPTIONS ["exhaust" ]:
366+ if self .__limit :
367+ raise InvalidOperation ("Can't use limit and exhaust together." )
368+ if self .__collection .database .connection .is_mongos :
369+ raise InvalidOperation ('Exhaust cursors are '
370+ 'not supported by mongos' )
371+ self .__exhaust = True
372+
322373 self .__query_flags |= mask
323374 return self
324375
@@ -332,6 +383,9 @@ def remove_option(self, mask):
332383 raise TypeError ("mask must be an int" )
333384 self .__check_okay_to_chain ()
334385
386+ if mask & _QUERY_OPTIONS ["exhaust" ]:
387+ self .__exhaust = False
388+
335389 self .__query_flags &= ~ mask
336390 return self
337391
@@ -350,6 +404,8 @@ def limit(self, limit):
350404 """
351405 if not isinstance (limit , int ):
352406 raise TypeError ("limit must be an int" )
407+ if self .__exhaust :
408+ raise InvalidOperation ("Can't use limit and exhaust together." )
353409 self .__check_okay_to_chain ()
354410
355411 self .__empty = False
@@ -689,34 +745,38 @@ def where(self, code):
689745
690746 def __send_message (self , message ):
691747 """Send a query or getmore message and handles the response.
692- """
693- db = self .__collection .database
694- kwargs = {"_must_use_master" : self .__must_use_master }
695- kwargs ["read_preference" ] = self .__read_preference
696- kwargs ["tag_sets" ] = self .__tag_sets
697- kwargs ["secondary_acceptable_latency_ms" ] = (
698- self .__secondary_acceptable_latency_ms )
699- if self .__connection_id is not None :
700- kwargs ["_connection_to_use" ] = self .__connection_id
701- kwargs .update (self .__kwargs )
702-
703- try :
704- response = db .connection ._send_message_with_response (message ,
705- ** kwargs )
706- except AutoReconnect :
707- # Don't try to send kill cursors on another socket
708- # or to another server. It can cause a _pinValue
709- # assertion on some server releases if we get here
710- # due to a socket timeout.
711- self .__killed = True
712- raise
713-
714- if isinstance (response , tuple ):
715- (connection_id , response ) = response
716- else :
717- connection_id = None
718748
719- self .__connection_id = connection_id
749+ If message is ``None`` this is an exhaust cursor, which reads
750+ the next result batch off the exhaust socket instead of
751+ sending getMore messages to the server.
752+ """
753+ client = self .__collection .database .connection
754+
755+ if message :
756+ kwargs = {"_must_use_master" : self .__must_use_master }
757+ kwargs ["read_preference" ] = self .__read_preference
758+ kwargs ["tag_sets" ] = self .__tag_sets
759+ kwargs ["secondary_acceptable_latency_ms" ] = (
760+ self .__secondary_acceptable_latency_ms )
761+ kwargs ['exhaust' ] = self .__exhaust
762+ if self .__connection_id is not None :
763+ kwargs ["_connection_to_use" ] = self .__connection_id
764+ kwargs .update (self .__kwargs )
765+
766+ try :
767+ res = client ._send_message_with_response (message , ** kwargs )
768+ self .__connection_id , (response , sock , pool ) = res
769+ if self .__exhaust :
770+ self .__exhaust_mgr = _SocketManager (sock , pool )
771+ except AutoReconnect :
772+ # Don't try to send kill cursors on another socket
773+ # or to another server. It can cause a _pinValue
774+ # assertion on some server releases if we get here
775+ # due to a socket timeout.
776+ self .__killed = True
777+ raise
778+ else : # exhaust cursor - no getMore message
779+ response = client ._exhaust_next (self .__exhaust_mgr .sock )
720780
721781 try :
722782 response = helpers ._unpack_response (response , self .__id ,
@@ -727,7 +787,7 @@ def __send_message(self, message):
727787 # Don't send kill cursors to another server after a "not master"
728788 # error. It's completely pointless.
729789 self .__killed = True
730- db . connection .disconnect ()
790+ client .disconnect ()
731791 raise
732792 self .__id = response ["cursor_id" ]
733793
@@ -743,6 +803,11 @@ def __send_message(self, message):
743803 if self .__limit and self .__id and self .__limit <= self .__retrieved :
744804 self .__die ()
745805
806+ # Don't wait for garbage collection to call __del__, return the
807+ # socket to the pool now.
808+ if self .__exhaust and self .__id == 0 :
809+ self .__exhaust_mgr .close ()
810+
746811 def _refresh (self ):
747812 """Refreshes the cursor with more data from Mongo.
748813
@@ -776,9 +841,14 @@ def _refresh(self):
776841 else :
777842 limit = self .__batch_size
778843
779- self .__send_message (
780- message .get_more (self .__collection .full_name ,
781- limit , self .__id ))
844+ # Exhaust cursors don't send getMore messages.
845+ if self .__exhaust :
846+ self .__send_message (None )
847+ else :
848+ self .__send_message (
849+ message .get_more (self .__collection .full_name ,
850+ limit , self .__id ))
851+
782852 else : # Cursor id is zero nothing else to return
783853 self .__killed = True
784854
0 commit comments