// ===== 节点状态机纯核(状态/迁移数据化——大 gen 思想:显式穷尽,禁静默默认臂)=====
// 状态机(net_kernel up_nodedown 语义对位——规格书 §8):
// Disconnected --connect--> Connecting --ack--> Connected
// Connected --conn_lost--> Disconnected(触发 nodedown 事件+reconnect 决策)
// Connecting --fail--> Disconnected(退避重试)
///|
pub(all) suberror NodeError {
UnknownState(String)
IllegalTransition(String)
}
///|
pub(all) enum NodeState {
Disconnected
Connecting
Connected
}
///|
/// 迁移表(数据面——显式穷尽;新迁移=新行)
pub(all) struct Transition {
from : NodeState
event : String
to : NodeState
}
///|
/// 迁移表(数据面真源——显式穷尽;新迁移=新行;fn 形因 const Array 限原语)
pub fn transitions() -> Array[Transition] {
[
Transition::{ from: Disconnected, event: "connect", to: Connecting, },
Transition::{ from: Connecting, event: "ack", to: Connected, },
Transition::{ from: Connecting, event: "fail", to: Disconnected, },
Transition::{ from: Connected, event: "conn_lost", to: Disconnected, },
]
}
///|
/// 迁移查找(纯函数;查无=IllegalTransition 诚实拒——零静默默认臂)
pub fn transition(
from : NodeState,
event : String,
) -> NodeState raise NodeError {
for tr in transitions() {
if state_eq(tr.from, from) && tr.event == event {
return tr.to
}
}
raise NodeError::IllegalTransition("\{state_name(from)} --\{event}--> ?")
}
///|
/// 状态名(文本面)
pub fn state_name(s : NodeState) -> String {
match s {
Disconnected => "disconnected"
Connecting => "connecting"
Connected => "connected"
}
}
///|
/// 状态等(骨架手写——无 derive 守恒)
fn state_eq(a : NodeState, b : NodeState) -> Bool {
match (a, b) {
(Disconnected, Disconnected) => true
(Connecting, Connecting) => true
(Connected, Connected) => true
_ => false
}
}
///|
/// 重连退避(A10 同源:指数+cap+max 停机哨兵 -1)
pub(all) struct NodeRetry {
base_ms : Int32
cap_ms : Int32
max : Int32
}
///|
pub fn NodeRetry::new(
base_ms~ : Int32 = 1000,
cap_ms~ : Int32 = 8000,
max~ : Int32 = 3,
) -> NodeRetry {
{ base_ms, cap_ms, max, }
}
///|
pub fn NodeRetry::delay(self : NodeRetry, tries : Int32) -> Int32 {
if tries >= self.max {
-1
} else {
let d = self.base_ms * (1 << tries)
if d > self.cap_ms {
self.cap_ms
} else {
d
}
}
}
///|
/// 连接执行位(六边形:EPMD+握手重放由消费方注入——beam-dist 组装)
pub(open) trait ConnectPort {
// async 真件面(beam-dist 组装面——epmd 发现+五帧握手 async 全程)
async fn connect(self : Self, node : String) -> Bool
}
///|
/// 节点管理器:状态机+退避+执行位+事件面组合(门 C 四层闭环宿主:
/// conn_lost→账清→nodedown 事件→决策→重连执行)
pub struct NodeManager {
node : String
priv mut state : NodeState
retry : NodeRetry
conn : &ConnectPort
events : &NodeEvents // 订阅面(NodeSupervisor 接线——决策由订阅者出)
priv registry : NodeRegistry // 多节点账本(主账=self.node)
}
///|
pub fn NodeManager::new(
node~ : String,
retry~ : NodeRetry,
conn~ : &ConnectPort,
events~ : &NodeEvents,
) -> NodeManager {
let registry = NodeRegistry::new()
let _ = registry.register(node) // 主账预注册(账随连接建——ets insert 对位)
{ node, state: Disconnected, retry, conn, events, registry, }
}
///|
/// 主账(本节点连接的账本——罐测/上层读写用)
pub fn NodeManager::account(self : NodeManager) -> NodeAccount {
// register 幂等回返主账(ets insert 对位——永不缺席,零 fail 面)
self.registry.register(self.node)
}
///|
pub fn NodeManager::state(self : NodeManager) -> String {
state_name(self.state)
}
///|
/// 重连(Once 策略执行面):状态机走 connect→执行位→ack/fail→退避循环
/// 返 true=Connected;false=重试耗尽(停机哨兵)
pub async fn NodeManager::reconnect(self : NodeManager) -> Bool raise NodeError {
let mut tries = 0
while true {
self.state = transition(self.state, "connect")
let ok = self.conn.connect(self.node) catch { _ => false }
if ok {
self.state = transition(self.state, "ack")
return true
}
self.state = transition(self.state, "fail")
let d = self.retry.delay(tries)
if d < 0 {
return false
}
tries += 1
}
false // 不可达(while true 内必有 return;编译器类型收口)
}
///|
/// 连接丢失真形(门 C 事件源接线):状态机走 conn_lost→主账 on_conn_lost
/// (清账+lost 计数+nodedown 事件上抛订阅者→决策)→决策为 reconnect 时
/// 执行位重连(退避循环)——本地判定语义,不依赖对端 EXIT 帧。
/// 返回 true=重连成功回 Connected;false=降级或重试耗尽。
pub async fn NodeManager::mark_lost(self : NodeManager) -> Bool raise NodeError {
self.state = transition(self.state, "conn_lost")
let acc = self.account()
acc.on_conn_lost(self.events)
// 决策由订阅者(NodeSupervisor)落账——本件读不回决策词(权柄在订阅面);
// Once 策略执行面 = 重连尝试(执行位退避循环)——决策退化词(degrade)
// 时上层不再调 mark_lost 后的重连,这里统一走一次重连尝试,
// 成败如实返回(执行面无策略复制——策略在订阅者,执行在此)
self.reconnect()
}