restartmanager-重启管理子系统
1. 子系统定位restartmanager是 MobyDocker daemon中负责容器自动重启策略的最小子系统。它本身不负责启动容器只回答一个问题给定当前退出码、是否被手动停止、本次运行了多久 —— 这个容器该不该重启什么时候重启真正的重启动作由daemon/monitor.go的handleContainerExit在收到 containerd 的 exit 事件后发起。RestartManager只给出决策和退避等待。这种决策与执行分离的设计让重启策略可独立单测也方便 daemon 在 shutdown 时统一Cancel()所有重启等待。2. 上游数据结构RestartPolicy定义在api/types/container/hostconfig.go:275type RestartPolicy struct { Name RestartPolicyMode MaximumRetryCount int } type RestartPolicyMode string const ( RestartPolicyDisabled RestartPolicyMode no RestartPolicyAlways RestartPolicyMode always RestartPolicyOnFailure RestartPolicyMode on-failure RestartPolicyUnlessStopped RestartPolicyMode unless-stopped )四种模式语义由IsNone/IsAlways/IsOnFailure/IsUnlessStopped方法封装Name何时重启MaximumRetryCount 是否生效no/永不默认否必须为 0always总是无论退出码、无论是否手动停止否必须为 0unless-stopped总是除非容器被用户手动停止否必须为 0on-failure退出码非 0 时重启受最大重试次数限制是ValidateRestartPolicyhostconfig.go:320做上述约束校验always/unless-stopped/no模式下MaximumRetryCount必须为 0on-failure下不能为负。兼容性细节v25.0.0 之前默认创建的容器Name为空字符串IsNone()把也当作no处理hostconfig.go:291-293。3. RestartManager 结构restartmanager.go:22type RestartManager struct { sync.Mutex sync.Once policy container.RestartPolicy restartCount int timeout time.Duration active bool cancel chan struct{} canceled bool }字段逐项字段含义sync.Mutex保护policy/restartCount/timeout/active/canceled。sync.Once嵌入以便Cancel()用Do(...)保证幂等多次调用只关闭一次 channel。policy当前生效的重启策略可通过SetPolicy在容器docker update时动态修改。restartCount已重启次数。New()时从容器持久化字段初始化每次决策要重启时自增。timeout当前次重启前要等待的退避时长指数增长。active是否正处于一次等待退避超时的过程中。防止并发重入ShouldRestart。cancel一个chan struct{}Cancel()时关闭让正在等待的 goroutine 立刻返回ErrRestartCanceled。canceled已取消标志使后续ShouldRestart调用直接返回ErrRestartCanceled。常量const ( backoffMultiplier 2 defaultTimeout 100 * time.Millisecond maxRestartTimeout 1 * time.Minute )这三者定义了指数退避曲线100ms → 200ms → 400ms → … → 1min封顶。4. 构造与生命周期Newrestartmanager.go:34func New(policy container.RestartPolicy, restartCount int) *RestartManager { return RestartManager{policy: policy, restartCount: restartCount, cancel: make(chan struct{})} }注意timeout初始为 0首次决策时才被置为defaultTimeout。restartCount由调用方传入对应容器持久化的RestartCount字段——daemon 重启后也能恢复计数。SetPolicyrestartmanager.go:39func (rm *RestartManager) SetPolicy(policy container.RestartPolicy) { rm.Lock() rm.policy policy rm.Unlock() }被container.UpdateMonitorcontainer.go:707调用支撑docker update --restart在线变更策略。只换策略不重置timeout/restartCount——这是后续要讨论的一个细节。Cancelrestartmanager.go:125func (rm *RestartManager) Cancel() { rm.Do(func() { rm.Lock() rm.canceled true close(rm.cancel) rm.Unlock() }) }借助嵌入的sync.Once无论调用多少次都只会关闭一次cancelchannel关闭已关闭的 channel 会 panic这是关键保护。调用方container.ResetRestartManager容器被删除/重建时daemon/shutdown路径container.go:458daemon 关闭时遍历所有容器逐个Cancel()避免重启风暴daemon 启动恢复阶段重建 manager 前先 Cancel 旧的5. 核心方法ShouldRestart签名restartmanager.go:46func (rm *RestartManager) ShouldRestart( exitCode uint32, hasBeenManuallyStopped bool, executionDuration time.Duration, ) (bool, chan error, error)返回值三段bool shouldRestart—— 决策结果chan error——退避等待通道调用方读它会阻塞到退避时间到或被 Cancel收到nil表示可以重启收到ErrRestartCanceled表示取消error—— 同步错误如 manager 已 Cancel / 重入5.1 算法流程按代码顺序拆解① 快速否决restartmanager.go:47-49if rm.policy.IsNone() { return false, nil, nil }策略是no时不加锁直接返回。高频路径的优化。② 加锁 防重入 防 Cancelrm.Lock() ... if rm.canceled { return false, nil, ErrRestartCanceled } if rm.active { return false, nil, errors.New(invalid call on an active restart manager) }activetrue表示上一次的退避 goroutine 还没结束还没到 timeout此时再调ShouldRestart是逻辑错误。③ 退避时长更新关键部分restartmanager.go:67-78if executionDuration.Seconds() 10 { rm.timeout 0 } switch { case rm.timeout 0: rm.timeout defaultTimeout // 100ms case rm.timeout maxRestartTimeout: rm.timeout * backoffMultiplier // ×2 } if rm.timeout maxRestartTimeout { rm.timeout maxRestartTimeout // 封顶 1min }两条核心规则规则 A容器存活 ≥ 10 秒就重置退避。即这次跑得够久说明不是 init 崩溃循环下次重启从 100ms 重新开始数。规则 B退避指数翻倍封顶 1 分钟。100ms → 200ms → 400ms → 800ms → 1.6s → 3.2s → 6.4s → 12.8s → 25.6s → 51.2s → 60s封顶。注意rm.timeout是实例状态而非本次决策的临时变量——下次调用会基于上一次的值继续翻倍。这是实现连续失败累计退避的关键。④ 策略判定restartmanager.go:80-91var restart bool switch { case rm.policy.IsAlways(): restart true case rm.policy.IsUnlessStopped() !hasBeenManuallyStopped: restart true case rm.policy.IsOnFailure(): if maxRetryCount : rm.policy.MaximumRetryCount; maxRetryCount 0 || rm.restartCount maxRetryCount { restart exitCode ! 0 } }要点always模式无视退出码、无视手动停止标志一律重启。unless-stopped模式只看hasBeenManuallyStopped——HasBeenManuallyStopped由docker stop/docker kill等显式停止设置持久化到容器 JSON。on-failure模式三重判断退出码非 0且未设上限或当前重启计数 上限。maxRetryCount 0是不设上限的哨兵值。no模式在 ① 已提前返回这里不会进来。⑤ 不重启清理 active 并返回restartmanager.go:93-96if !restart { rm.active false return false, nil, nil }⑥ 决定重启计数 1、解锁、启动退避 goroutinerestartmanager.go:98-121rm.restartCount unlockOnExit false rm.active true rm.Unlock() ch : make(chan error) go func() { timeout : time.NewTimer(rm.timeout) defer timeout.Stop() select { case -rm.cancel: ch - ErrRestartCanceled close(ch) case -timeout.C: rm.Lock() close(ch) rm.active false rm.Unlock() } }() return true, ch, nil这段是子系统的并发心脏几个值得注意的细节unlockOnExit false在 goroutine 启动前已手动Unlock()避免defer二次解锁 panic。goroutine 在select上等两个事件Cancelrm.cancel被 close或退避超时timeout.C。Cancel 路径往ch发送ErrRestartCanceled再 closeactive没被改manager 已废无所谓。超时路径直接close(ch)让 receiver 收到零值nil表示等待结束可以重启然后加锁把active置回false。两种路径都close(ch)确保 channel 不会被泄漏调用方一旦读到值就说明等待结束。调用方读 channel 的语义ch nil→ 退避到时可以重启ch收到ErrRestartCanceled→ 被取消放弃重启channel close 后读取零值 → 等同 nil可重启6. 调用方与 daemon 的集成6.1 持有container.RestartManager()container.go:720func (container *Container) RestartManager() *restartmanager.RestartManager { if container.restartManager nil { container.restartManager restartmanager.New( container.HostConfig.RestartPolicy, container.RestartCount, ) } return container.restartManager }懒初始化第一次访问时按当前HostConfig.RestartPolicy和持久化的RestartCount创建实例。这是 daemon 重启后恢复重启计数的关键——RestartCount是持久化在容器 JSON 里的。6.2 决策消费daemon/monitor.go:99execDuration : time.Since(c.State.StartedAt) restart, wait, err : c.RestartManager().ShouldRestart( uint32(ctrExitStatus.ExitCode), daemonShutdown || c.HasBeenManuallyStopped, execDuration, )handleContainerExit的关键拼装exitCode来自 containerd 的 exit 事件hasBeenManuallyStoppeddaemon 正在关闭或容器被手动停止二者任一为真就当作手动停止影响unless-stopped判定execDuration从State.StartedAt到 now6.3 执行重启daemon/monitor.go:148-177if restart { go func() { waitErr : -wait // 阻塞等退避 if waitErr nil { daemon.waitForStartupDone() if err : daemon.containerStart(...); err ! nil { waitErr err ... } } if waitErr ! nil { // 重启失败回退到 stopped 状态、可能 auto-remove ... } }() }异步启动新 goroutine等待退避结束再触发containerStart。这是为什么ShouldRestart要返回 channel 而不是同步阻塞——daemon 主循环不能为退避卡住。6.4 在线更新策略container.UpdateMonitorfunc (container *Container) UpdateMonitor(restartPolicy containertypes.RestartPolicy) { container.RestartManager().SetPolicy(restartPolicy) }docker update --restart...时调用。注意只换policy不重置timeout/restartCount——如果容器正在崩溃循环退避中更新策略后仍按原退避节奏继续。7. 状态机与并发模型7.1 RestartManager 状态New() │ ▼ ┌─────────── activefalse ───────────┐ │ │ │ ShouldRestart() 判定要重启 │ │ │ ▼ │ activetrue │ 启动 goroutine │ 等待 cancel 或 timeout │ │ │ ┌──────┴───────┐ │ │ │ │ timeout 到 Cancel() │ │ │ │ ▼ ▼ │ activefalse canceledtrue │ (永不再重启)7.2 并发安全要点policy/restartCount/timeout/active/canceled全程在锁保护下访问。退避 goroutine在超时分支里才取锁设activefalseCancel 分支不取锁manager 已废弃。ShouldRestart的锁释放路径成功路径手动Unlock()后启动 goroutine失败路径由defer释放——靠unlockOnExit标志二选一。Cancel()靠sync.Once防止重复 close channel 的 panic。active防重入避免同一容器多次 exit 事件并发触发两次退避 goroutine。8. 边界条件与陷阱理解这个子系统时下面几条最容易踩坑8.1 存活 ≥ 10 秒重置退避是单边决策executionDuration 10s时把rm.timeout 0然后switch 里又把它设为defaultTimeout。所以重置的真实含义是下次退避从 100ms 重新开始而不是不退避立即重启。一个跑了 10s 后崩溃的容器第一次重启仍要等 100ms。8.2on-failure的退出码 0 也会消耗一次等待如果exitCode 0且策略是on-failurerestart保持false——但前面的退避 timeout 已经被更新过了。也就是说成功结束也会改rm.timeout。不过因为接下来activefalse直接返回没有 goroutine 真正等这个 timeout所以实践中无影响。但下次容器再启动并失败时新一次ShouldRestart会基于一个已经被重置或翻倍过的timeout。这通常无害因为同时也会走 10 秒重置逻辑但属于隐式状态。8.3always模式下hasBeenManuallyStopped也重启case rm.policy.IsAlways(): restart true // 不检查 hasBeenManuallyStopped所以docker stop一个--restartalways的容器后daemon 仍会尝试重启它。这就是为什么 CLI 要先Cancel()容器的 RestartManager或把它标 stopped再 stop。对比之下unless-stopped才真正记住用户主动停止过。8.4MaximumRetryCount 0的双重含义if maxRetryCount : rm.policy.MaximumRetryCount; maxRetryCount 0 || rm.restartCount maxRetryCount { restart exitCode ! 0 }0在这里是哨兵值 不限次。所以--restarton-failure:0等价于无限重试而非不重试。CLI 默认on-failure不带数字时填 0。8.5 daemon shutdown 路径的 Cancelcontainer.go:458在 daemon 关闭时遍历所有容器Cancel()。这会让正在退避 goroutine 里的-rm.cancel立刻触发channel 收到ErrRestartCanceled。monitor.go:172显式判断errors.Is(waitErr, restartmanager.ErrRestartCanceled)后不打错误日志——这是预期行为。8.6restartCount与MaximumRetryCount的比较时机rm.restartCount maxRetryCount在自增之前比较。所以on-failure:3实际允许 3 次重启count: 0→1, 1→2, 2→3 都满足 3count: 3 不满足停止。命名上MaximumRetryCount 3语义就是最多重试 3 次一致。8.7docker update --restart不重置状态SetPolicy只换策略字段。如果容器当前restartCount已经是 5、timeout已经退避到 30s把策略从on-failure:10改成always下一次崩溃仍按 30s 退避仍带 count6。这是有意为之避免用户通过 update 重置来绕过崩溃循环保护。9. 测试视角restartmanager_test.go只有两个用例系统足够小TestRestartManagerTimeout策略 always executionDuration1s → 应该重启且rm.timeout是defaultTimeout100ms。TestRestartManagerTimeoutReset手动把rm.timeout设为 5s调用ShouldRestart(_, _, 10s)→ 由于 executionDuration ≥ 10srm.timeout应回到defaultTimeout。未覆盖但值得补测的场景on-failure模式下MaximumRetryCount边界count max-1/count maxunless-stopped模式下hasBeenManuallyStoppedtrueCancel()后再调ShouldRestart返回ErrRestartCanceledactivetrue时重入返回同步 error退避翻倍曲线多次连续失败 timeout 演化Cancel goroutine 让-ch收到ErrRestartCanceled10. 学习路径建议先读hostconfig.go:275-344理解RestartPolicy四种模式与校验。再读restartmanager.go全文仅 ~130 行一气呵成聚焦ShouldRestart的三段——退避更新、策略判定、goroutine 启动。接着读daemon/container/container.go:720-740看 manager 如何被懒初始化、持久化字段如何注入。最后读daemon/monitor.go:34-180看决策如何被消费、退避 channel 如何驱动containerStart。动手实验docker run --restarton-failure:3 ...一个会exit 1的脚本docker inspect观察RestartCount与State对照退避时间表100ms→200ms→…验证。关键调用关系图container.RestartManager() ── 懒初始化 ── restartmanager.New() │ ├── policy ← HostConfig.RestartPolicy └── restartCount ← Container.RestartCount (持久化) container.UpdateMonitor(newPolicy) ── SetPolicy() daemon.handleContainerExit(c, e) [daemon/monitor.go:34] │ ├── c.RestartManager().ShouldRestart(exitCode, manual, dur) │ │ │ ├── 退避更新指数 2 / 封顶 1min / 10s 重置 │ ├── 策略判定always / unless-stopped / on-failure │ └── 启动 goroutine 等 cancel 或 timeout │ ├── if restart: c.State.SetRestarting(...) 启动新 goroutine 等 wait │ └── -wait nil 时调 daemon.containerStart(...) │ └── else: c.State.SetStopped(...) 可能 autoRemove daemon shutdown / container 删除 ── RestartManager.Cancel() └── Do(close(rm.cancel)) ← goroutine 收到 ErrRestartCanceled

相关新闻

AI Agent 搜索 Skill 全品类梳理(2026):国内方案+海外 API+开源自建选型参考

AI Agent 搜索 Skill 全品类梳理(2026):国内方案+海外 API+开源自建选型参考

开篇AI Agent 搜索 Skill 是智能体调用的联网检索能力,可帮助智能体实时获取信息、核查事实、搜集资料,是智能体连接外部世界的核心组件。当前行业内的搜索 Skill 可分为海外云 API、国内可用方案、开源自建、一体化搜索与抓取四大类,不同技术…

2026/8/4 2:24:36 阅读更多 →
AI 搜索工具推荐(2026):国内工具+海外专业产品+面向开发者的搜索基础设施

AI 搜索工具推荐(2026):国内工具+海外专业产品+面向开发者的搜索基础设施

2026 年,AI 搜索工具已进入规模化应用阶段。国内有秘塔 AI 搜索、豆包、文心一言、Kimi 等产品;海外有 Perplexity AI、New Bing、Phind 等产品,覆盖日常资讯、专业调研、学术检索、编程辅助等多类场景。对于开发者群体而言,若为 …

2026/8/4 2:24:36 阅读更多 →
项目邮箱迁移实战:从@mail.ru到企业邮箱的完整配置与避坑指南

项目邮箱迁移实战:从@mail.ru到企业邮箱的完整配置与避坑指南

大家好,最近在开发一个需要集成邮件服务的项目时,遇到了一个不大不小的“坑”:项目初期为了方便,使用了 mail.ru 这类俄罗斯邮箱服务作为测试邮箱。但随着项目推进,需要更换为更通用、更稳定的企业邮箱(如…

2026/8/4 2:24:36 阅读更多 →

最新新闻

嵌入式学习第十三天报告:从void指针深入理解C语言内存与指针

嵌入式学习第十三天报告:从void指针深入理解C语言内存与指针

1. void指针:通用内存操作工具 1.1 void指针的基本概念 void指针(void *)是C语言中一种特殊的指针类型,它不指向任何具体的数据类型。void指针的主要特点是: 通用性:可以指向任意类型的数据灵活性&#xff…

2026/8/4 6:06:26 阅读更多 →
ToastFish:让摸鱼时间变成英语提升的黄金时刻

ToastFish:让摸鱼时间变成英语提升的黄金时刻

ToastFish:让摸鱼时间变成英语提升的黄金时刻 【免费下载链接】ToastFish 一个利用摸鱼时间背单词的软件。 项目地址: https://gitcode.com/GitHub_Trending/to/ToastFish 你是否曾有过这样的体验?工作间隙刷手机、开会发呆、等待文件传输时&…

2026/8/4 6:06:26 阅读更多 →
AI代码审计实战:从静态扫描到智能安全护航的研发流程变革

AI代码审计实战:从静态扫描到智能安全护航的研发流程变革

1. 项目概述:当AI成为代码的“安检员”最近在团队内部搞了个有意思的实践,我们称之为“AI驱动的代码安全卫士”。核心就是拿华为云CodeArts的“代码智能审计助手”这个工具,深度折腾了一番,把它从一个“静态规则扫描器”用成了我们…

2026/8/4 6:06:26 阅读更多 →
SpringBoot3.x启动流程与优化实践

SpringBoot3.x启动流程与优化实践

1. SpringBoot3.x启动流程全景解析作为Java生态中最主流的应用框架,SpringBoot的启动机制一直是开发者深入理解框架的核心切入点。最近在将一个老项目迁移到SpringBoot3.x时,我系统梳理了新版启动流程的变化点,这里结合源码层级的调试分析&am…

2026/8/4 6:06:26 阅读更多 →
C++字符串数字提取:从基础到实战的完整指南

C++字符串数字提取:从基础到实战的完整指南

1. 项目概述:从字符串中精准提取数字的艺术在C的日常开发中,处理字符串数据是家常便饭。其中,一个高频且看似简单的需求就是从一段混杂着字母、符号和空格的字符串里,把那些代表数字的字符“抠”出来,并转换成可以直接…

2026/8/4 6:06:26 阅读更多 →
OpenClaw:从AI安全工具到攻防新战场的范式转变

OpenClaw:从AI安全工具到攻防新战场的范式转变

1. 从“工具”到“战场”:OpenClaw的范式转变如果你最近关注AI安全领域,可能会发现一个有趣的现象:过去几个月,围绕“OpenClaw”的讨论热度急剧攀升。它不再仅仅是一个开源项目或工具的名字,而是频繁地与“攻击面”、“…

2026/8/4 6:05:26 阅读更多 →

日新闻

AI Agent白手起家26: 使用标准事件驱动大模型实践

AI Agent白手起家26: 使用标准事件驱动大模型实践

纲要 练习目标:掌握大模型标准事件的调用回顾 LangChain 中的核心标准事件 invokestreambatchastream_eventswith_structured_output 环境准备实战代码:多种事件调用对比 同步调用与流式输出批量处理异步事件流监听结构化输出 运行说明与预期结果总结与扩…

2026/8/4 0:00:40 阅读更多 →
dealsea是什么?跨境卖家必知的美国deal站入门指南

dealsea是什么?跨境卖家必知的美国deal站入门指南

说实话,第一次听说美国这个老牌折扣网站的跨境卖家,十个有八个会问同一个问题:这个平台到底是干嘛的?我见过一个做家居出口的朋友,他在亚马逊上月销二十万美金,却从来没用过它。我给他看了首页——一屏一屏…

2026/8/4 0:01:40 阅读更多 →
清华大学重磅EST:植物自导电闪蒸焦耳热600°C/2600°C两步法!稀土超积累植物秒级转化为CeO₂-石墨烯电催化剂!

清华大学重磅EST:植物自导电闪蒸焦耳热600°C/2600°C两步法!稀土超积累植物秒级转化为CeO₂-石墨烯电催化剂!

通讯作者:邓兵、刘建国通讯单位:清华大学DOI:https://doi.org/10.1021/acs.est.6c00603研究背景稀土元素(REEs)是清洁能源技术与电子器件不可或缺的核心原料,然而传统提取方式依赖能耗高、排放大的采矿与强…

2026/8/4 0:01:40 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/3 4:58:13 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/3 1:53:31 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/4 5:26:40 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/3 13:07:03 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/3 5:19:38 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/3 8:27:36 阅读更多 →