diff --git a/pymongo/mongo_client.py b/pymongo/mongo_client.py index 82a2ecb38..759ee7f0c 100644 --- a/pymongo/mongo_client.py +++ b/pymongo/mongo_client.py @@ -1096,7 +1096,7 @@ class MongoClient(common.BaseObject): return message def _send_message(self, message, - with_last_error=False, command=False, check_primary=True): + with_last_error=False, command=False): """Say something to Mongo. Raises ConnectionFailure if the message cannot be sent. Raises @@ -1109,11 +1109,9 @@ class MongoClient(common.BaseObject): - `message`: message to send - `with_last_error`: check getLastError status after sending the message - - `check_primary`: don't try to write to a non-primary; see - kill_cursors for an exception to this rule """ member = self.__ensure_member() - if check_primary and not with_last_error and not self.is_primary: + if not with_last_error and not self.is_primary: # The write won't succeed, bail as if we'd done a getLastError raise AutoReconnect("not master") @@ -1345,8 +1343,24 @@ class MongoClient(common.BaseObject): """ if not isinstance(cursor_ids, list): raise TypeError("cursor_ids must be a list") - return self._send_message( - message.kill_cursors(cursor_ids), check_primary=False) + + member = self.__member + + # We're disconnected, but can't risk taking the lock to reconnect + # if we're being called from Cursor.__del__, see PYTHON-799. + if not member: + raise AutoReconnect() + + _, kill_cursors_msg = message.kill_cursors(cursor_ids) + sock_info = self.__socket(member) + try: + try: + sock_info.sock.sendall(kill_cursors_msg) + except: + sock_info.close() + raise + finally: + member.maybe_return_socket(sock_info) def server_info(self): """Get information about the MongoDB server we're connected to. diff --git a/pymongo/pool.py b/pymongo/pool.py index c34ebda4e..b9ecd6865 100644 --- a/pymongo/pool.py +++ b/pymongo/pool.py @@ -213,13 +213,12 @@ class Pool: self.pool_id += 1 self.pid = os.getpid() - sockets = None + # Allocate outside the lock. Triggering a GC while holding the lock + # could run Cursor.__del__ and deadlock. See PYTHON-799. + new_sockets = set() + self.lock.acquire() try: - # Swapping variables is not atomic. We need to ensure no other - # thread is modifying self.sockets, or replacing it, in this - # critical section. - self.lock.acquire() - sockets, self.sockets = self.sockets, set() + sockets, self.sockets = self.sockets, new_sockets finally: self.lock.release()