예제 #1
0
    async def send_cluster_transaction(self, stack, raise_on_error=True):
        # the first time sending the commands we send all of the commands that were queued up.
        # if we have to run through it again, we only retry the commands that failed.
        attempt = sorted(stack, key=lambda x: x.position)
        node = {}

        # as we move through each command that still needs to be processed,
        # we figure out the slot number that command maps to, then from the slot determine the node.
        for c in attempt:
            # refer to our internal node -> slot table that tells us where a given
            # command should route to.
            slot = self._determine_slot(*c.args)
            hashed_node = self.connection_pool.get_node_by_slot(slot)

            # now that we know the name of the node ( it's just a string in the form of host:port )
            # we can build a list of commands for each node.
            if node.get('name') != hashed_node['name']:
                # raise error if commands in a transaction can not hash to same node
                if len(node) > 0:
                    raise ClusterTransactionError(
                        "Keys in request don't hash to the same node")
            node = hashed_node
        conn = self.connection_pool.get_connection_by_node(node)
        if self.watches:
            await self._watch(node, conn, self.watches)
        node_commands = NodeCommands(self.parse_response,
                                     conn,
                                     in_transaction=True)
        node_commands.append(PipelineCommand(('MULTI', )))
        node_commands.extend(attempt)
        node_commands.append(PipelineCommand(('EXEC', )))
        self.explicit_transaction = True
        await node_commands.write()
        # todo: make this place clear
        try:
            await node_commands.read()
        except ExecAbortError:
            if self.explicit_transaction:
                await conn.send_command('DISCARD')
                await conn.read_response()

        # If at least one watched key is modified before the EXEC command,
        # the whole transaction aborts,
        # and EXEC returns a Null reply to notify that the transaction failed.
        if node_commands.commands[-1].result is None:
            raise WatchError
        self.connection_pool.release(conn)
        if self.watching:
            self._unwatch(conn)
        if raise_on_error:
            self.raise_first_error(stack)
예제 #2
0
 async def _watch(self, node, conn, names):
     "Watches the values at keys ``names``"
     for name in names:
         slot = self._determine_slot('WATCH', name)
         dist_node = self.connection_pool.get_node_by_slot(slot)
         if node.get('name') != dist_node['name']:
             # raise error if commands in a transaction can not hash to same node
             if len(node) > 0:
                 raise ClusterTransactionError(
                     "Keys in request don't hash to the same node")
     if self.explicit_transaction:
         raise RedisError('Cannot issue a WATCH after a MULTI')
     await conn.send_command('WATCH', *names)
     return await conn.read_response()