Skip to content

fix(arknights): 连接失败改为指数退避并只提示一次 - #457

Open
qiyinxi wants to merge 1 commit into
devfrom
fix/arknights-connect-backoff
Open

fix(arknights): 连接失败改为指数退避并只提示一次#457
qiyinxi wants to merge 1 commit into
devfrom
fix/arknights-connect-backoff

Conversation

@qiyinxi

@qiyinxi qiyinxi commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

摘要

  • Timer.minute_task 每秒调用一次 scheduled_taskapp/core/timer.py:139 + asyncio.sleep(1)),其中「未连接且窗口句柄存在」这一分支没有任何退避、次数上限或放弃条件,只要窗口在但连不上(未进主界面、controller 拒绝连接等)就会以秒级频率无限重试。
  • 每次失败都会 logger.error 并向前端 Publisher.send 一条错误通知,用户界面同样每秒被刷屏一次
  • 新增 connect_retry_delay:连续失败时 2/4/8… 秒指数退避、封顶 60 秒;窗口句柄变化视为新的连接机会并清空退避;前端通知只在一轮失败的首次发出。
  • 线上表现:Sentry AUTO-MAS-BACKEND-2N,单台机器 24 小时内 68,558 条,占全组织事件量的 80.5%,频率 1.25 秒一条。

设计取舍

封顶 60 秒而非彻底放弃:用户可能在任意时刻进入游戏主界面,永久放弃会导致再也连不上,需要手动开关工具才能恢复。60 秒上限把最坏情况从 86,400 次/天压到 1,440 次/天,同时保留自动恢复能力。

检查

  • 新增 tests/task/test_arknights_connect_backoff.py:4 个用例通过(含封顶与异常入参)
  • python -m pytest tests --collect-only -q:收集 168 个,退出码 0
  • ruff format 无残留改动
  • 未做人工验证:需要在明日方舟窗口存在但无法连接的状态下观察日志频率与前端通知次数,并确认进入游戏后能在 60 秒内自动接上

Sourcery 摘要

通过添加带上限的指数重试退避机制和一次性用户通知,在保留自动恢复功能的同时,减少 Arknights 连接失败产生的噪音。

错误修复:

  • 防止 Arknights 连接失败后每秒重试并导致日志和前端通知泛滥。
  • 保留自动重新连接功能,同时通过带上限的指数退避降低失败尝试的频率。

增强功能:

  • 在游戏窗口发生变化或连接成功时重置连接重试状态,并且每个失败周期仅通知前端一次。

测试:

  • 增加对指数重试延迟、最大延迟上限和无效失败次数的测试覆盖。
Original summary in English

Summary by Sourcery

Reduce Arknights connection failure noise by adding capped exponential retry backoff and one-time user notifications while retaining automatic recovery.

Bug Fixes:

  • Prevent repeated Arknights connection failures from retrying every second and flooding logs and frontend notifications.
  • Preserve automatic reconnection while reducing failed-attempt frequency through capped exponential backoff.

Enhancements:

  • Reset connection retry state when the game window changes or a connection succeeds, and notify the frontend only once per failure cycle.

Tests:

  • Add coverage for exponential retry delays, the maximum delay cap, and invalid failure counts.

`Timer.minute_task` 每秒调用一次 `scheduled_task`,其中「未连接且窗口句柄
存在」这一分支没有任何退避、次数上限或放弃条件,只要窗口在但连不上(未进
主界面、controller 拒绝连接等)就会以秒级频率无限重试。每次失败都会
`logger.error` 并向前端推送一条错误通知,用户界面也随之被刷屏。

线上单台机器 24 小时内因此产生 68,558 条上报,占全组织事件量的 80.5%。

- 新增 `connect_retry_delay`:连续失败时 2/4/8… 秒指数退避,封顶 60 秒,
  保证用户随时进入游戏后仍能在一分钟内自动接上
- 窗口句柄变化视为新的连接机会,清空退避
- 前端通知只在一轮失败的首次发出,重试期间不再重复弹窗

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Aug 29, 2026

Copy link
Copy Markdown

审查者指南

将明日方舟连接失败处理从每秒无限重试改为 2 秒起步、60 秒封顶的指数退避,并在一轮失败中仅提示用户一次;窗口变化或连接成功会重置退避状态,同时新增退避计算边界测试。

明日方舟连接重试退避时序图

sequenceDiagram
    participant Timer
    participant Toolkit
    participant Arknights
    participant Publisher

    loop Every second
        Timer->>Toolkit: scheduled_task()
        Toolkit->>Toolkit: get_connect_status()
        alt Retry time reached
            Toolkit->>Arknights: connect_arknights()
            alt Connection succeeds
                Arknights-->>Toolkit: connected
                Toolkit->>Toolkit: reset_connect_backoff()
            else Connection fails
                Arknights-->>Toolkit: Exception
                Toolkit->>Toolkit: connect_retry_delay(failures)
                Toolkit->>Toolkit: Set next_connect_attempt
                Toolkit->>Publisher: Publisher.send(error notice)
            end
        else Backoff active
            Toolkit-->>Timer: Skip connection attempt
        end
    end
Loading

明日方舟连接退避生命周期状态图

stateDiagram-v2
    [*] --> Ready
    Ready --> Retrying: connect_arknights() fails
    Retrying --> Retrying: scheduled_task() before next_connect_attempt
    Retrying --> Retrying: connect_retry_delay() 2/4/8.../60 seconds
    Retrying --> Connected: connect_arknights() succeeds
    Retrying --> Ready: Window handle changes
    Connected --> Ready: Window handle changes
    Connected --> Connected: get_connect_status()
    Retrying --> [*]: Window disappears
Loading

明日方舟连接失败通知流程图

flowchart TD
    A["connect_arknights()"] --> B{"Connection succeeds?"}
    B -->|Yes| C["reset_connect_backoff()"]
    B -->|No| D[Increment connect_failures]
    D --> E["connect_retry_delay() and set next_connect_attempt"]
    E --> F{"First failure in this round?"}
    F -->|Yes| G["Publisher.send(error notice)"]
    F -->|No| H[Wait for next retry]
    G --> H
    H --> I{"Window changes or connection succeeds?"}
    I -->|Yes| C
    I -->|No| A
Loading

文件级变更

变更 详细信息 文件
为明日方舟连接失败增加基于单调时钟的指数退避,并在窗口变化或连接成功时重置状态。
  • 新增 2 秒起步、指数增长、60 秒封顶的退避计算。
  • 在每秒调度任务中仅于退避窗口到期后尝试连接。
  • 窗口句柄变化会开启新的连接机会并清空失败计数。
  • 连接成功后清空失败计数和下一次尝试时间。
app/MaaFW/ArknightWin32.py
抑制重复失败期间的用户通知,同时保留带重试上下文的错误日志。
  • 连续失败仅首次向前端发送连接错误通知。
  • 后续日志记录失败次数及预计重试延迟。
app/MaaFW/ArknightWin32.py
增加连接退避计算函数的边界行为测试。
  • 覆盖首次延迟、指数增长、60 秒封顶及非正失败次数输入。
tests/task/test_arknights_connect_backoff.py
更新资源版本信息并清理无关空行格式变更。
  • 调整版本元数据。
  • 移除多个类定义前的多余空行。
res/version.json
app/MaaFW/ArknightWin32.py

提示和命令

与 Sourcery 交互

  • 触发新的审查: 在拉取请求中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 根据审查评论生成 GitHub issue: 回复审查评论,请求 Sourcery 根据该评论创建 issue。你也可以使用 @sourcery-ai issue 回复审查评论,以根据该评论创建 issue。
  • 生成拉取请求标题: 在拉取请求标题的任意位置写入 @sourcery-ai,即可随时生成标题。你也可以在拉取请求中评论 @sourcery-ai title,以随时生成或重新生成标题。
  • 生成拉取请求摘要: 在拉取请求正文中任意位置写入 @sourcery-ai summary,即可在指定位置随时生成 PR 摘要。你也可以在拉取请求中评论 @sourcery-ai summary,以随时生成或重新生成摘要。
  • 生成审查者指南: 在拉取请求中评论 @sourcery-ai guide,即可随时生成或重新生成审查者指南。
  • 解决所有 Sourcery 评论: 在拉取请求中评论 @sourcery-ai resolve,即可解决所有 Sourcery 评论。如果你已经处理完所有评论且不想再看到它们,这一功能会很有用。
  • 忽略所有 Sourcery 审查: 在拉取请求中评论 @sourcery-ai dismiss,即可忽略所有现有的 Sourcery 审查。如果你想从头开始进行新的审查,这一功能尤其有用——别忘了评论 @sourcery-ai review 以触发新的审查!

自定义使用体验

访问你的控制面板以:

  • 启用或禁用审查功能,例如 Sourcery 生成的拉取请求摘要、审查者指南等。
  • 更改审查语言。
  • 添加、删除或编辑自定义审查说明。
  • 调整其他审查设置。

获取帮助

Original review guide in English

Reviewer's Guide

将明日方舟连接失败处理从每秒无限重试改为 2 秒起步、60 秒封顶的指数退避,并在一轮失败中仅提示用户一次;窗口变化或连接成功会重置退避状态,同时新增退避计算边界测试。

Sequence diagram for Arknights connection retry backoff

sequenceDiagram
    participant Timer
    participant Toolkit
    participant Arknights
    participant Publisher

    loop Every second
        Timer->>Toolkit: scheduled_task()
        Toolkit->>Toolkit: get_connect_status()
        alt Retry time reached
            Toolkit->>Arknights: connect_arknights()
            alt Connection succeeds
                Arknights-->>Toolkit: connected
                Toolkit->>Toolkit: reset_connect_backoff()
            else Connection fails
                Arknights-->>Toolkit: Exception
                Toolkit->>Toolkit: connect_retry_delay(failures)
                Toolkit->>Toolkit: Set next_connect_attempt
                Toolkit->>Publisher: Publisher.send(error notice)
            end
        else Backoff active
            Toolkit-->>Timer: Skip connection attempt
        end
    end
Loading

State diagram for Arknights connection backoff lifecycle

stateDiagram-v2
    [*] --> Ready
    Ready --> Retrying: connect_arknights() fails
    Retrying --> Retrying: scheduled_task() before next_connect_attempt
    Retrying --> Retrying: connect_retry_delay() 2/4/8.../60 seconds
    Retrying --> Connected: connect_arknights() succeeds
    Retrying --> Ready: Window handle changes
    Connected --> Ready: Window handle changes
    Connected --> Connected: get_connect_status()
    Retrying --> [*]: Window disappears
Loading

Flow diagram for Arknights connection failure notification

flowchart TD
    A["connect_arknights()"] --> B{"Connection succeeds?"}
    B -->|Yes| C["reset_connect_backoff()"]
    B -->|No| D[Increment connect_failures]
    D --> E["connect_retry_delay() and set next_connect_attempt"]
    E --> F{"First failure in this round?"}
    F -->|Yes| G["Publisher.send(error notice)"]
    F -->|No| H[Wait for next retry]
    G --> H
    H --> I{"Window changes or connection succeeds?"}
    I -->|Yes| C
    I -->|No| A
Loading

File-Level Changes

Change Details Files
为明日方舟连接失败增加基于单调时钟的指数退避,并在窗口变化或连接成功时重置状态。
  • 新增 2 秒起步、指数增长、60 秒封顶的退避计算。
  • 在每秒调度任务中仅于退避窗口到期后尝试连接。
  • 窗口句柄变化会开启新的连接机会并清空失败计数。
  • 连接成功后清空失败计数和下一次尝试时间。
app/MaaFW/ArknightWin32.py
抑制重复失败期间的用户通知,同时保留带重试上下文的错误日志。
  • 连续失败仅首次向前端发送连接错误通知。
  • 后续日志记录失败次数及预计重试延迟。
app/MaaFW/ArknightWin32.py
增加连接退避计算函数的边界行为测试。
  • 覆盖首次延迟、指数增长、60 秒封顶及非正失败次数输入。
tests/task/test_arknights_connect_backoff.py
更新资源版本信息并清理无关空行格式变更。
  • 调整版本元数据。
  • 移除多个类定义前的多余空行。
res/version.json
app/MaaFW/ArknightWin32.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你好——我发现了 1 个问题

AI Agent 提示词
请处理本次代码审查中的评论:

## 单独评论

### 评论 1
<location path="app/MaaFW/ArknightWin32.py" line_range="66-69" />
<code_context>
+        float: 等待秒数, 指数增长并封顶到 ``CONNECT_RETRY_MAX_SECONDS``
+    """
+
+    return min(
+        CONNECT_RETRY_BASE_SECONDS * 2 ** max(failures - 1, 0),
+        CONNECT_RETRY_MAX_SECONDS,
+    )
+
+
</code_context>
<issue_to_address>
**issue (bug_risk):** 当连续失败次数达到 1025 次时,`2 ** (failures - 1)` 在与浮点数相乘时转换为 `float`,直接抛出 `OverflowError`,因此异常处理本身失败,`second_task` 终止且后续不再自动重连。60 秒封顶并不能避免这个问题,因为指数表达式是在 `min` 执行前计算的。

**Triggers:** 当明日方舟窗口持续无法连接约 17 小时以上,累计失败次数达到 1025 次。

**Suggested fix:** 在指数运算前先按封顶阈值截断失败次数,或在 `failures` 达到对应阈值时直接返回 `CONNECT_RETRY_MAX_SECONDS````suggestion
    return min(
        CONNECT_RETRY_BASE_SECONDS * 2 ** min(max(failures - 1, 0), 5),
        CONNECT_RETRY_MAX_SECONDS,
    )
```
</issue_to_address>

Sourcery 评估

等待批准。 请先处理 1 个发现的问题。

阻塞性发现:app/MaaFW/ArknightWin32.py:69


Sourcery 对开源项目免费——如果您喜欢我们的审查,请考虑分享给他人 ✨
帮助我变得更有用!请在每条评论上点击 👍 或 👎,我会利用这些反馈来改进审查结果。
Original comment in English

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="app/MaaFW/ArknightWin32.py" line_range="66-69" />
<code_context>
+        float: 等待秒数, 指数增长并封顶到 ``CONNECT_RETRY_MAX_SECONDS``
+    """
+
+    return min(
+        CONNECT_RETRY_BASE_SECONDS * 2 ** max(failures - 1, 0),
+        CONNECT_RETRY_MAX_SECONDS,
+    )
+
+
</code_context>
<issue_to_address>
**issue (bug_risk):** 当连续失败次数达到 1025 次时,`2 ** (failures - 1)` 在与浮点数相乘时转换为 `float`,直接抛出 `OverflowError`,因此异常处理本身失败,`second_task` 终止且后续不再自动重连。60 秒封顶并不能避免这个问题,因为指数表达式是在 `min` 执行前计算的。

**Triggers:** 当明日方舟窗口持续无法连接约 17 小时以上,累计失败次数达到 1025 次。

**Suggested fix:** 在指数运算前先按封顶阈值截断失败次数,或在 `failures` 达到对应阈值时直接返回 `CONNECT_RETRY_MAX_SECONDS````suggestion
    return min(
        CONNECT_RETRY_BASE_SECONDS * 2 ** min(max(failures - 1, 0), 5),
        CONNECT_RETRY_MAX_SECONDS,
    )
```
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: app/MaaFW/ArknightWin32.py:69


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +66 to +69
return min(
CONNECT_RETRY_BASE_SECONDS * 2 ** max(failures - 1, 0),
CONNECT_RETRY_MAX_SECONDS,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): 当连续失败次数达到 1025 次时,2 ** (failures - 1) 在与浮点数相乘时转换为 float,直接抛出 OverflowError,因此异常处理本身失败,second_task 终止且后续不再自动重连。60 秒封顶并不能避免这个问题,因为指数表达式是在 min 执行前计算的。

Triggers: 当明日方舟窗口持续无法连接约 17 小时以上,累计失败次数达到 1025 次。

Suggested fix: 在指数运算前先按封顶阈值截断失败次数,或在 failures 达到对应阈值时直接返回 CONNECT_RETRY_MAX_SECONDS

Suggested change
return min(
CONNECT_RETRY_BASE_SECONDS * 2 ** max(failures - 1, 0),
CONNECT_RETRY_MAX_SECONDS,
)
return min(
CONNECT_RETRY_BASE_SECONDS * 2 ** min(max(failures - 1, 0), 5),
CONNECT_RETRY_MAX_SECONDS,
)
Original comment in English

issue (bug_risk): 当连续失败次数达到 1025 次时,2 ** (failures - 1) 在与浮点数相乘时转换为 float,直接抛出 OverflowError,因此异常处理本身失败,second_task 终止且后续不再自动重连。60 秒封顶并不能避免这个问题,因为指数表达式是在 min 执行前计算的。

Triggers: 当明日方舟窗口持续无法连接约 17 小时以上,累计失败次数达到 1025 次。

Suggested fix: 在指数运算前先按封顶阈值截断失败次数,或在 failures 达到对应阈值时直接返回 CONNECT_RETRY_MAX_SECONDS

Suggested change
return min(
CONNECT_RETRY_BASE_SECONDS * 2 ** max(failures - 1, 0),
CONNECT_RETRY_MAX_SECONDS,
)
return min(
CONNECT_RETRY_BASE_SECONDS * 2 ** min(max(failures - 1, 0), 5),
CONNECT_RETRY_MAX_SECONDS,
)

@HarcoChen HarcoChen added enhancement New feature or request AI AI generated content labels Aug 30, 2026
@qiyinxi qiyinxi assigned qiyinxi and unassigned qiyinxi Aug 30, 2026
@qiyinxi qiyinxi added the Sentry daily Sentry daily issues label Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI AI generated content enhancement New feature or request Sentry daily Sentry daily issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants