重试循环 重试
Retry Loop Retry

原始链接: https://matklad.github.io/2025/08/23/retry-loop-retry.html

这篇帖子详细介绍了作者在函数式编程环境中创建健壮重试循环的过程。最初的目标是创建一个明确限制重试次数、避免在最后一次尝试后不必要的延迟、保留原始错误并在失败时以及最大限度地减少代码重复的循环。 早期的尝试集中在循环固定次数,但这需要在运行时进行断言,以保证即使没有重试,`action()` 函数也*始终*被执行至少一次。然后,作者重新定义了问题:与其循环*直到*重试次数用尽,不如循环*当*操作失败时,将重试作为提前退出的条件。 这种方法优雅地处理了零重试的情况。然而,最初它缺乏语法上受限的重试次数。通过向循环添加上限(retry_count + 1)解决了这个问题,但作者承认,如果忘记了“+1”,可能会导致程序崩溃。最终,尽管存在一个小的注意事项,但这个解决方案在功能和清晰度之间取得了平衡。

黑客新闻 新 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 重试循环 重试 (matklad.github.io) 4 点赞 by todsacerdoti 1 小时前 | 隐藏 | 过去 | 收藏 | 讨论 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请YC | 联系 搜索:
相关文章

原文

Some time ago I lamented that I don’t know how to write a retry loop such that:

  • it is syntactically obvious that the amount of retries is bounded,
  • there’s no spurious extra sleep after the last attempt,
  • the original error is reported if retrying fails,
  • there’s no code duplication in the loop.

https://matklad.github.io/2023/12/21/retry-loop.html

To recap, we have

fn action() E!T { ... }
fn is_transient_error(err: E) bool { ... }

and we need to write

fn action_with_retries(retry_count: u32) E!T { ... }

I’ve received many suggestions, and the best one was from https://www.joachimschipper.nl, though it was somewhat specific to Python:

for tries_left in reverse(range(retry_count)):
    try:
        return action()
    except Exception as e:
        if tries_left == 0 or not is_transient_error(e):
            raise
        sleep()
else:
    assert False

A couple of days ago I learned to think better about the problem. You see, the first requirement, that the number of retries is bounded syntactically, was leading me down the wrong path. If we start with that requirement, we get code shape like:

const result: E!T = for (0..retry_count) {
    
    action()
    
}

The salient point here is that, no matter what we do, we need to get E or T out as a result, so we’ll have to call action() at least once. But retry_count could be zero. Looking at the static semantics, any non do while loop’s body can be skipped completely, so we’ll have to have some runtime asserts explaining to the compiler that we really did run action at least once. The part of the loop which is guaranteed to be executed at least once is a condition. So it’s more fruitful to flip this around: it’s not that we are looping until we are out of attempts, but, rather, we are looping while the underlying action returns an error, and then retries are an extra condition to exit the loop early:

var retries_left = retry_count;
const result = try while(true) {
    const err = if (action()) |ok| break ok else |err| err;
    if (!is_transient_error(err)) break err;

    if (retries_left == 0) break err;
    retries_left -= 1;
    sleep();
};

This shape of the loop also works if the condition for retries is not attempts based, but, say, time based. Sadly, this throws “loop is obviously bounded” requirement out of the window. But it can be restored by adding upper bound to the infinite loop:

var retries_left = retry_count;
const result = try for(0..retry_count + 1) {
    const err = if (action()) |ok| break ok else |err| err;
    if (!is_transient_error(err)) break err;

    if (retries_left == 0) break err;
    retries_left -= 1;
    sleep();
} else @panic("runaway loop");

I still don’t like it (if you forget that +1, you’ll get a panic!), but that’s where I am at!

联系我们 contact @ memedata.com