gcli2api / internal /pool /degrade.go
a3216's picture
chore: 同步到上游 1.12.0-panel + 凭证同步/独立启动器/保活
6d60378 verified
Raw History Blame Contribute Delete
2.82 kB
// 连败降权(issue #114):NoteFailures 计数 + 达阈临时出池 + NoteSuccess 清零。
// 与冷却/熔断「并存取更长者不叠加」由 entry.healthy 的并列或门天然保证
// (三个截止是或门,任一未到期即不可选,生效的一定是最远者——见 entry.degradeUntil 注释)。
package pool
import "time"
// NoteFailures 记录一次「不罚号的失败」(issue #114 连败兜底的喂入口):
// consecutiveFails++,达到 degradeThreshold 触发降权(degradeUntil = now +
// 降权时长,计数清零供下一轮重新累计——同 recordBreakerFailureLocked 哲学)。
// **已在降权期内**(degradeUntil 未到期)再次达阈:不延长、不翻倍(防用户重试
// 把降权越堆越厚,与 CooldownSoftRate 兜底探测同哲学)——计数清零,到期放行后
// 需重新连败满阈值才再降。成功(NoteSuccess)清零计数并清降权截止。
//
// 谁该喂:ErrClient(未知 4xx,applyErrorPolicy default 只换号不罚)与传输层
// 失败(handler 网络抖动分支,同样只换号不罚)——两者都是「不知道原因的失败」,
// 正是连败兜底的目标形态。带权威分类的错误(429/WAF/402/5xx/12153/11140/11102)
// 各有「知道原因的惩罚」(冷却/熔断/禁用/负缓存),不喂本计数器——惩罚已存在,
// 再喂连败是重复计罚(任务书纪律:两机制并存取更长者,不叠加)。
func (p *Pool) NoteFailures(uid string) {
p.mu.Lock()
defer p.mu.Unlock()
e, ok := p.byUID[uid]
if !ok {
return
}
e.consecutiveFails++
if e.consecutiveFails < p.degradeThreshold {
p.dirty.Store(true)
return
}
e.consecutiveFails = 0
if !e.degradeUntil.IsZero() && time.Now().Before(e.degradeUntil) {
// 降权期内的连败达阈:不延长(取更长者语义由 healthy 或门保证)。计数
// 已清零——到期放行后需重新连败满阈值才再降。
p.dirty.Store(true)
return
}
e.degradeUntil = time.Now().Add(p.degradeDurationLocked())
p.dirty.Store(true)
}
// degradeDurationLocked 返回降权时长(当前实现为固定 degradeCooldown,钳到
// [defaultDegradeCooldown, degradeCooldownMax])。不做指数升级:触发计数在达阈时
// 清零、不持久化历史触发次数,档位不可推导;固定时长已满足「临时出池一段时间」
// 的语义(真持续坏号会在下一轮 5 连败里再降一次)。调用方必须已持有 p.mu。
func (p *Pool) degradeDurationLocked() time.Duration {
d := p.degradeCooldown
if d <= 0 {
d = defaultDegradeCooldown
}
max := p.degradeCooldownMax
if max <= 0 {
max = defaultDegradeCooldownMax
}
if d > max {
d = max
}
return d
}