/// engine_circuit.mbt —— 熔断器三态状态机(R104,Nygard Release It! 2007 / Fowler / Azure / AWS 蒸馏落地)。
/// Closed(正常放行,窗口内失败计数)→ 达阈值 → Open(fail-fast 拒绝,微秒返回不耗资源)→
/// 恢复定时器到期 → Half-Open(放行探测请求)→ 探测成功 Closed / 探测失败回 Open。
/// 编排中间层定位:不真调外部服务,状态推进 + 决策(allow_call 指示调用方可否放行),持久化双后端。
///|
/// 上报一次失败并推进熔断状态机。窗口内失败达阈值 → Open(记 opened_at);Half-Open 探测失败 → 回 Open。
pub fn FistEngine::circuit_fail(
self : FistEngine,
ns? : String = "default",
circuit~ : String,
now_secs~ : Int,
window_secs? : Int = 60,
threshold? : Int = 3,
recovery_secs? : Int = 30,
) -> Json {
let row = self.store.cb_get(ns, circuit)
let (state, failures, w, thr, rec, opened_at, last_fail_ts) = match row {
None => ("closed", 0, window_secs, threshold, recovery_secs, 0, 0)
Some(r) => r
}
let (new_state, new_failures, new_opened) = if state == "open" {
// 已熔断:保持 Open(调用方按 allow_call=false 快速失败)
("open", failures, opened_at)
} else if state == "half_open" {
// Half-Open 探测失败 → 回 Open,重启恢复定时器
("open", thr, now_secs)
} else {
// Closed:窗口内计数
let nf = if now_secs - last_fail_ts > w { 1 } else { failures + 1 }
if nf >= thr {
("open", nf, now_secs)
} else {
("closed", nf, 0)
}
}
ignore(
self.store.cb_save(
ns,
circuit,
new_state,
new_failures,
w,
thr,
rec,
new_opened,
now_secs,
now_secs.to_string(),
),
)
cb_json(circuit, new_state, new_failures, w, thr, rec, new_opened, now_secs)
}
///|
/// 上报一次成功:Closed 复位计数;Half-Open 探测成功 → Closed;Open 保持。
pub fn FistEngine::circuit_succeed(
self : FistEngine,
ns? : String = "default",
circuit~ : String,
now_secs~ : Int,
) -> Json {
let row = self.store.cb_get(ns, circuit)
let (state, failures, w, thr, rec, opened_at, last_fail_ts) = match row {
None => ("closed", 0, 60, 3, 30, 0, 0)
Some(r) => r
}
let (new_state, new_failures, new_opened) = if state == "half_open" {
("closed", 0, 0)
} else if state == "open" {
("open", failures, opened_at)
} else {
("closed", 0, 0)
}
ignore(
self.store.cb_save(
ns,
circuit,
new_state,
new_failures,
w,
thr,
rec,
new_opened,
now_secs,
now_secs.to_string(),
),
)
cb_json(circuit, new_state, new_failures, w, thr, rec, new_opened, now_secs)
}
///|
/// 查询熔断状态(含 Half-Open 到期判定:Open 且 elapsed≥recovery → 转 Half-Open 放行探测)。
pub fn FistEngine::circuit_status(
self : FistEngine,
ns? : String = "default",
circuit~ : String,
now_secs~ : Int,
) -> Json {
let row = self.store.cb_get(ns, circuit)
let (state, failures, w, thr, rec, opened_at, last_fail_ts) = match row {
None => ("closed", 0, 60, 3, 30, 0, 0)
Some(r) => r
}
let (eff_state, eff_failures, eff_opened) = if state == "open" &&
opened_at > 0 &&
now_secs - opened_at >= rec {
// 恢复定时器到期 → Half-Open,放行探测
ignore(
self.store.cb_save(
ns,
circuit,
"half_open",
failures,
w,
thr,
rec,
opened_at,
now_secs,
now_secs.to_string(),
),
)
("half_open", failures, opened_at)
} else {
(state, failures, opened_at)
}
cb_json(circuit, eff_state, eff_failures, w, thr, rec, eff_opened, now_secs)
}
///|
fn cb_json(
circuit : String,
state : String,
failures : Int,
w : Int,
thr : Int,
rec : Int,
opened_at : Int,
now_secs : Int,
) -> Json {
let allow = state != "open"
let m = Map::new()
m.set("circuit", Json::string(circuit))
m.set("state", Json::string(state))
m.set("failures", Json::number(failures.to_double()))
m.set("window_secs", Json::number(w.to_double()))
m.set("threshold", Json::number(thr.to_double()))
m.set("recovery_secs", Json::number(rec.to_double()))
m.set("opened_at", Json::number(opened_at.to_double()))
m.set("allow_call", Json::boolean(allow))
m.set(
"note",
Json::string(
"熔断器三态(Nygard Release It! 2007 / Fowler / Azure 蒸馏):Closed 放行 / Open fail-fast 拒绝 / Half-Open 放行探测,探测成功回 Closed、失败回 Open。",
),
)
Json::object(m)
}