如何制作一个处理电机和龙卷风故障转移的装饰器?

时间:2014-04-19 20:38:55

标签: python-3.x tornado tornado-motor

我正在尝试编写一个装饰器,它接受一个与mongodb交互的函数,如果发生异常,它会重试交互。 我有以下代码:

def handle_failover(f):
    def wrapper(*args):
        for i in range(40):
            try:
                yield f(*args)
                break
            except pymongo.errors.AutoReconnect:
                loop = IOLoop.instance()
                yield gen.Task(loop.add_timeout, time.time() + 0.25)
    return wrapper


class CreateHandler(DatabaseHandler):
    @handle_failover
    def create_counter(self, collection):
        object_id = yield self.db[collection].insert({'n': 0})
        return object_id

    @gen.coroutine
    def post(self, collection):
        object_id = yield self.create_counter(collection)
        self.finish({'id': object_id})

但这不起作用。它给出了create_counter产生生成器的错误。我已经尝试过制作@ gen.coroutines的所有函数,但它没有帮助。

如何让handle_failover装饰工作?

编辑: 现在没有装饰器。这应该可靠地创建一个计数器并将object_id返回给用户。如果出现异常,则会显示500页。

class CreateHandler(DatabaseHandler):
    @gen.coroutine
    def create_counter(self, collection, data):
        for i in range(FAILOVER_TRIES):
            try:
                yield self.db[collection].insert(data)
                break
            except pymongo.errors.AutoReconnect:
                loop = IOLoop.instance()
                yield gen.Task(loop.add_timeout, time.time() + FAILOVER_SLEEP)
            except pymongo.errors.DuplicateKeyError:
                break
        else:
            raise Exception("Can't create new counter.")

    @gen.coroutine
    def post(self, collection):
        object_id = bson.objectid.ObjectId()
        data = {
            '_id': object_id,
            'n': 0
        }
        yield self.create_counter(collection, data)
        self.set_status(201)
        self.set_header('Location', '/%s/%s' % (collection, str(object_id)))
        self.finish({})

虽然我仍然不知道如何增加计数器幂等,因为DuplicateKeyError的技巧在这里不适用:

class CounterHandler(CounterIDHandler):
    def increment(self, collection, object_id, n):
        result = yield self.db[collection].update({'_id': object_id}, {'$inc': {'n': int(n)}})
        return result

    @gen.coroutine
    def post(self, collection, counter_id, n):
        object_id = self.get_object_id(counter_id)
        if not n or not int(n):
            n = 1
        result = yield self.increment(collection, object_id, n)
        self.finish({'resp': result['updatedExisting']})

1 个答案:

答案 0 :(得分:3)

你很可能不想这样做。最好向用户显示错误,而不是重试操作。

盲目地重试任何引发AutoReconnect的插件是一个坏主意,因为你不知道MongoDB是否在丢失连接之前执行了插入。在这种情况下,您不知道是否最终会有一个或两个{'n': 0}的记录。因此,您应确保以此方式重试的任何操作都是幂等的。有关详细信息,请参阅我的"save the monkey" article

如果你肯定想要制作这样的包装器,你需要确保fwrapper都是协同程序。此外,如果f抛出错误40次,则必须重新提出最终错误。如果f成功,则必须返回其返回值:

def handle_failover(f):
    @gen.coroutine
    def wrapper(*args):
        retries = 40
        i = 0
        while True:
            try:
                ret = yield gen.coroutine(f)(*args)
                raise gen.Return(ret)
            except pymongo.errors.AutoReconnect:
                if i < retries:
                    i += 1
                    loop = IOLoop.instance()
                    yield gen.Task(loop.add_timeout, time.time() + 0.25)
                else:
                    raise
    return wrapper

但只能做幂等操作!