> For the complete documentation index, see [llms.txt](https://summerain-1.gitbook.io/summerain/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://summerain-1.gitbook.io/summerain/zh-cn/fa-bu-shuo-ming/v2.0.4.md).

# V2.0.4

> 开发预发布版本。此版本从 `dev` 分支发布，后续修改可能较为频繁， 并且不会被默认的 `latest` 镜像选中。

## 概述

v2.0.4 为后端 worker manager 管理的所有 worker 提供统一监督器和协调、有界的停机流程。 当 worker panic 或意外返回时，监督器会按指数退避重新启动；当系统进入预期的 drain 时， worker 会停止领取新任务、完成有时间上限的清理，并且不会再启动替代任务。

停机现在会先排空产生任务的 HTTP 流量，再停止消费任务的 worker。进程首先转为未就绪， 随后等待活跃 HTTP 请求完成，再排空 worker；必要时执行强制取消，最后关闭 Redis 和 MySQL 资源。监听器故障也会进入与 `SIGINT`、`SIGTERM` 相同的生命周期，不再通过 `log.Fatalf` 直接终止进程。

此版本不新增数据库结构版本，不改变 HTTP 或上传 API，也不改变图片处理配方。

## 变更

### 统一的 worker 监督

* 将心跳、outbox、V2 发布、V2 清理、浏览计数刷新、通用清理和用户删除 worker 全部放到一个带名称的监督器下运行。
* 捕获 worker panic，并在 worker 意外返回后按 1、2、4、8、16、30 秒的有界指数延迟 重新启动；后续失败的等待时间保持在 30 秒上限。
* 记录发生故障的 worker 名称和原始 panic 值，便于定位重复故障，同时避免整个进程退出。
* `Manager.Start(ctx)` 会持续阻塞，直到所有受监督 worker 停止；同时提供幂等且并发安全的 `BeginDrain()` 信号。
* drain 或强制取消开始时会立即中断重启等待；收到任一停止信号后都不会重新启动 worker。

### 先生产者、后消费者的停机顺序

* 停机开始后，`/ready` 立即返回 HTTP 503 和 `status: draining`；`/health` 行为保持不变。
* 先排空 HTTP 服务器，再要求 worker 停止领取任务。这样，正在执行的上传请求可以先完成 持久化发布任务和 outbox 记录的创建，随后消费者再执行最终排空。
* 先在软排空阶段等待 worker；必要时再取消 worker 根 context，并进入独立的强制停止等待阶段。
* 仅在 worker 已完成或 worker 停止预算到期后，才关闭 Redis 和 SQL 连接池。
* 意外的监听器退出会进入同一个停机协调器；生命周期路径会报告停机错误，但不使用 fatal logger。

### 有界的生命周期预算

* 强制执行 30 秒应用停机预算：HTTP 排空最多 18 秒，worker 软排空 5 秒，worker 强制停止 5 秒，运行时资源关闭 2 秒。
* 将 Compose 后端的 `stop_grace_period` 增加到 40 秒，为 30 秒应用预算和容器运行时调度 留出余量。
* 最终浏览计数刷新和租约清理使用新的有界 context，因此即使普通 worker context 已取消， 清理仍可继续执行。

### 持久化发布与 outbox 租约

* drain 开始后，V2 发布与 outbox worker 不再领取新记录，但当前已领取的操作可以在 软排空阶段完成。
* 使用新的 3 秒清理 context 释放中断的发布和 outbox 任务。
* 每次释放都同时校验 lease owner 与 lease token。旧 worker 的清理不能清除已经完成的记录， 也不能清除替代 worker 后来获取的租约。
* 正常取消时退还本次领取所增加的尝试次数，使单纯的停机不会消耗重试预算。
* 发布过程 panic 时保留本次尝试次数，再把 panic 重新抛给监督器，使确定性故障最终仍能达到 尝试次数上限。
* outbox 投递 panic 时，只把当前活动事件标记为失败，退还尚未处理的已领取事件， 并把原始 panic 重新抛给监督器。

### 最终浏览计数刷新

* 无论软排空还是强制取消，都会使用新的 context 执行一次最终浏览计数刷新，最长 3 秒。
* Redis `GETDEL` 后若 MySQL 明确返回更新错误，会通过新的 1 秒 Redis `INCRBY` context 恢复已取出的计数，不再静默丢失。
* 恢复操作保持在 5 秒 worker 强制停止预算以内。

### 持续集成

* 为固定版本的 MySQL 8.4.10 CI 服务增加专用生命周期测试数据库。
* 新增强制执行的真实 MySQL 测试，覆盖发布租约 owner/token 隔离、发布 panic 后的租约释放与 panic 传播，以及 outbox 租约 owner/token 隔离。
* 新增监督器、重启退避、排空顺序、生命周期截止时间、readiness gate、浏览计数刷新和 Compose 停机宽限期的单元测试。

## 验证

* 真实 MySQL 8.4 生命周期数据库引导和 3 个租约清理集成场景：通过。
* 后端 `go test ./... -count=1`：通过。
* 服务端生命周期、worker 和发布服务的后端 race 测试：通过。
* 后端 `go vet ./...`：通过。
* 后端 `go build ./...`：通过。
* 前端 ESLint、Vitest、TypeScript 项目和 Vite 生产构建：通过。
* Python requirements 锁文件与发布镜像策略测试：通过。
* 刷新本版本的翻译源哈希后，GitBook 文档与翻译校验：通过。
* 现有 wasm-vips 依赖的 direct-eval 警告保持不变，不会导致构建失败。

## 安装

开发镜像必须显式拉取：

```bash
docker pull jaykserks/summerain:dev-v2.0.4
```

等效的精确开发标签：

```
jaykserks/summerain:dev-2.0.4
```

滚动更新的 `dev` 标签也会指向最新一次成功的开发构建。 此版本不会修改 `latest`、`main` 以及稳定的语义化版本别名。

从 v2.0.3 升级时无需执行数据库迁移或 API 迁移，可以进行滚动升级。不过，生产环境应固定 使用精确镜像标签或 digest，而不应使用会移动的 `dev` 标签。

## 已知限制

* 浏览计数仍跨越 Redis 与 MySQL，且没有事务型或幂等账本。如果 MySQL 已提交更新， 但返回了结果不确定的错误，恢复 Redis 计数可能导致该计数在后续刷新时再次写入。 新恢复流程能避免明确失败时的数据丢失，但不能提供跨存储的 exactly-once 语义。
* 心跳和用户删除 worker 会在批次之间停止，但已经开始的批次中，并非每个数据库操作都已完整 感知 context。因此，正在执行的数据库调用可能持续到驱动操作返回，或进程停止期限生效。
* 严格配置解析与校验计划在 v2.0.5 中完成。
* 本次监督器只覆盖由 `Manager` 管理的后台 worker。在请求内执行的资源密集路径不受该监督器 管理，其中包括旧 V1 AVIF 转换；这些路径的资源准入和限制控制计划在 v2.0.6 中完成。
* 运行时依赖和 worker 健康状态的完整 readiness 检查计划在 v2.0.7 中完成。v2.0.4 只保证停机排空开始后 readiness 立即失败。
* 动图支持仍计划在后续版本中提供。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://summerain-1.gitbook.io/summerain/zh-cn/fa-bu-shuo-ming/v2.0.4.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
