Skip to main content

nadisc_runtime/
registration.rs

1//! 注册写入串行化与本地资格。远端结果不明时停止相反写入后的重新发布。
2
3use crate::{
4    changed, closed, config::RegistrationPolicy, lock, millis, operation, timeout, Connection,
5    Core, Gate, OwnerPhase,
6};
7use nadisc::managed::{
8    DiscoveryError, ErrorCode, RegistrationEvidence, RegistrationPlan, Result, WriteAdmission,
9};
10use std::{
11    sync::{
12        atomic::{AtomicBool, AtomicU64, Ordering},
13        Arc, Mutex,
14    },
15    time::Duration,
16};
17use tokio::{sync::watch, time::Instant};
18
19/// 宿主提供的本地业务资格,不应由包含远端注册结果的整体 readiness 反向驱动。
20#[derive(Clone)]
21pub struct Eligibility {
22    inner: Arc<Mutex<bool>>,
23    changes: watch::Sender<bool>,
24    check: Option<Arc<dyn Fn() -> bool + Send + Sync>>,
25    valid_until: Arc<Mutex<Option<Instant>>>,
26    source: Option<std::sync::Weak<()>>,
27}
28
29/// 手动宿主的唯一资格发送者;丢弃后读句柄立即失效。
30pub struct EligibilityController {
31    signal: Eligibility,
32    _lifetime: Arc<()>,
33}
34impl EligibilityController {
35    /// 业务作用:发布有期限的本地资格,不让接收者保留永久有效的最后成功值。
36    /// 参数说明:`ready` 为本地事实,`valid_for` 必须在一毫秒到三十秒之间。
37    /// 返回:有效输入同步改变资格;非法期限拒绝且不开放接流。
38    pub fn update(&self, ready: bool, valid_for: Duration) -> Result<()> {
39        if valid_for < Duration::from_millis(1) || valid_for > Duration::from_secs(30) {
40            self.signal.set(false);
41            return Err(DiscoveryError::new(
42                ErrorCode::InvalidConfig,
43                "eligibility validity is out of bounds",
44            ));
45        }
46        let mut value = lock(&self.signal.inner);
47        *value = ready;
48        *lock(&self.signal.valid_until) = ready.then(|| Instant::now() + valid_for);
49        self.signal.changes.send_replace(ready);
50        Ok(())
51    }
52}
53impl Drop for EligibilityController {
54    /// 业务作用:发送者退出时立即撤销发布许可并唤醒注册监督。
55    /// 参数说明:无。
56    /// 返回:保留的读句柄不能延续最后一次 true。
57    fn drop(&mut self) {
58        self.signal.set(false);
59    }
60}
61
62impl Eligibility {
63    /// 业务作用:创建默认不接流的资格源,监听器准备后再显式开放。
64    /// 参数说明:无。
65    /// 返回:可由宿主保留的资格控制句柄。
66    pub fn new() -> Self {
67        Self {
68            inner: Arc::new(Mutex::new(false)),
69            changes: watch::channel(false).0,
70            check: None,
71            valid_until: Arc::new(Mutex::new(None)),
72            source: None,
73        }
74    }
75
76    /// 业务作用:为手动宿主分离资格更新责任与传入 owner 的只读信号。
77    /// 参数说明:无。
78    /// 返回:唯一发送者和默认不接流的信号,发送者退出或证据过期均撤权。
79    pub fn channel() -> (EligibilityController, Self) {
80        let lifetime = Arc::new(());
81        let mut signal = Self::new();
82        signal.source = Some(Arc::downgrade(&lifetime));
83        (
84            EligibilityController {
85                signal: signal.clone(),
86                _lifetime: lifetime,
87            },
88            signal,
89        )
90    }
91
92    /// 业务作用:同步改变本地发布资格,与注册提交前复验使用同一把锁。
93    /// 参数说明:`ready` 表示该注册对应的本地 listener 和业务依赖是否可接流。
94    /// 返回:后续发布立即受约束;手动 true 最多有效三十秒,需要宿主持续更新。
95    pub fn set(&self, ready: bool) {
96        let mut value = lock(&self.inner);
97        *value = ready;
98        *lock(&self.valid_until) = ready.then(|| Instant::now() + Duration::from_secs(30));
99        self.changes.send_replace(ready);
100    }
101
102    /// 业务作用:读取本地资格供独立健康端点返回真实状态。
103    /// 参数说明:无。
104    /// 返回:本地事实,不包含注册中心回查结果。
105    pub fn is_ready(&self) -> bool {
106        self.current(*lock(&self.inner))
107    }
108
109    /// 业务作用:绑定宿主可同步读取的本地资格,在实际发布点重新读取而不依赖轮询延迟。
110    /// 参数说明:`check` 必须无网络、有限时间返回,且不得重入 discovery 或改变资格对象。
111    /// 返回:只依赖宿主当前事实的资格源;闭包必须自行包含停机和证据期限门禁。
112    pub fn from_check(check: impl Fn() -> bool + Send + Sync + 'static) -> Self {
113        Self {
114            inner: Arc::new(Mutex::new(true)),
115            changes: watch::channel(true).0,
116            check: Some(Arc::new(check)),
117            valid_until: Arc::new(Mutex::new(None)),
118            source: None,
119        }
120    }
121
122    /// 业务作用:在已持有本地许可锁时复验证据期限或宿主实时状态。
123    /// 参数说明:`ready` 是保护范围内的显式许可。
124    /// 返回:停止或证据到期均为 false。
125    fn current(&self, ready: bool) -> bool {
126        ready
127            && self
128                .source
129                .as_ref()
130                .is_none_or(|source| source.strong_count() > 0)
131            && self.check.as_ref().map_or_else(
132                || lock(&self.valid_until).is_some_and(|until| Instant::now() < until),
133                |check| check(),
134            )
135    }
136}
137
138impl Default for Eligibility {
139    /// 业务作用:默认关闭发布许可,避免构造组件即对外注册。
140    /// 参数说明:无。
141    /// 返回:未就绪的本地资格源。
142    fn default() -> Self {
143        Self::new()
144    }
145}
146
147/// 冻结前的注册计划、目标连接与本地发布资格。
148pub struct RegistrationSpec {
149    /// 目标连接的 qualifier,必须引用 owner 中已配置的连接。
150    pub connection_ref: String,
151    /// 冻结后提交给后端的完整注册计划。
152    pub plan: RegistrationPlan,
153    /// 目标连接或注册的冻结策略。
154    pub policy: RegistrationPolicy,
155    /// 发布前复验的本地业务资格,不依赖远端注册健康回查。
156    pub eligibility: Eligibility,
157}
158
159/// 单个注册目标的发布、健康确认与注销状态。
160#[derive(Clone, Copy, Debug, Eq, PartialEq)]
161pub enum RegistrationState {
162    /// 计划已冻结,尚未写入后端。
163    Planned,
164    /// 正在提交注册写入。
165    Registering,
166    /// 远端写入结果不明,需要协调后才能继续发布。
167    OutcomeUnknown,
168    /// 已提交注册,尚未获得有效健康证据。
169    PendingHealth,
170    /// 已获得健康证据,仍需按 valid_until 判断时效。
171    Active,
172    /// 本地资格或后端证据不再允许继续接流。
173    Suspended,
174    /// 已停止发布,正在注销远端实例。
175    Withdrawing,
176    /// 注销未能确认完成,可能存在远端残留。
177    Residual,
178    /// 已确认目标注销完成。
179    Removed,
180}
181
182/// 注册目标的只读证据摘要;Active 仍需检查证据有效期。
183#[derive(Clone, Debug)]
184pub struct RegistrationStatus {
185    /// 注册目标的本地唯一标识。
186    pub id: String,
187    /// 承载该注册的连接 qualifier。
188    pub connection: String,
189    /// 当前注册阶段;远端写入结果不明时不能当作成功。
190    pub state: RegistrationState,
191    /// 是否曾完成启动所要求的首次注册确认。
192    pub startup_confirmed: bool,
193    /// 远端注册失去有效健康证据时是否影响宿主整体就绪。
194    pub readiness_critical: bool,
195    /// 最近一次有效健康证据的本地单调时刻。
196    pub observed_at: Option<Instant>,
197    /// 当前健康证据的截止时刻;缺失或过期均不代表就绪。
198    pub valid_until: Option<Instant>,
199    /// 最近的注册或回查错误,不包含凭据原文。
200    pub error: Option<DiscoveryError>,
201}
202
203impl RegistrationStatus {
204    /// 业务作用:读取无需网络访问的当前发布证据。
205    /// 参数说明:无。
206    /// 返回:已确认为 Active 且证据尚未到期才算远端就绪。
207    pub fn is_ready(&self) -> bool {
208        self.state == RegistrationState::Active
209            && self
210                .valid_until
211                .is_some_and(|deadline| Instant::now() < deadline)
212    }
213}
214
215pub(crate) struct Record {
216    pub spec: RegistrationSpec,
217    pub connection: Arc<Connection>,
218    pub status: Mutex<RegistrationStatus>,
219    pub started: AtomicBool,
220    pub confirmed: AtomicBool,
221    health_revocation: AtomicU64,
222    attempted: AtomicBool,
223    uncertain: AtomicBool,
224    unsettled_renewal: AtomicBool,
225}
226
227impl Record {
228    /// 业务作用:在第一项外部写入前持有完整计划和状态记录。
229    /// 参数说明:`spec` 已完成预检,`connection` 提供真实后端身份。
230    /// 返回:尚未发布的注册记录。
231    pub(crate) fn new(spec: RegistrationSpec, connection: Arc<Connection>) -> Self {
232        let health_revocation = connection.revocation.load(Ordering::Acquire);
233        Self {
234            status: Mutex::new(RegistrationStatus {
235                id: spec.plan.id.clone(),
236                connection: spec.connection_ref.clone(),
237                state: RegistrationState::Planned,
238                startup_confirmed: false,
239                readiness_critical: spec.policy.readiness_critical,
240                observed_at: None,
241                valid_until: None,
242                error: None,
243            }),
244            spec,
245            connection,
246            started: AtomicBool::new(false),
247            confirmed: AtomicBool::new(false),
248            health_revocation: AtomicU64::new(health_revocation),
249            attempted: AtomicBool::new(false),
250            uncertain: AtomicBool::new(false),
251            unsettled_renewal: AtomicBool::new(false),
252        }
253    }
254
255    /// 业务作用:统一裁决启动确认、宿主就绪与诊断读取的注册健康资格。
256    /// 参数说明:`gate` 是 owner 已持有的状态门禁,与连接撤权及健康证据发布串行。
257    /// 返回:旧身份代次、本地失权或停机的证据不能继续用于就绪;保留注销残留等独立生命周期事实。
258    pub(crate) fn current_status(&self, gate: &Gate) -> RegistrationStatus {
259        let mut status = lock(&self.status).clone();
260        // 身份代次只由有效的新回查推进;复制状态不能使被撤销的旧证据重新有效。
261        if self.health_revocation.load(Ordering::Acquire)
262            != self.connection.revocation.load(Ordering::Acquire)
263        {
264            status.valid_until = None;
265            if matches!(
266                status.state,
267                RegistrationState::Active | RegistrationState::PendingHealth
268            ) {
269                status.state = RegistrationState::Suspended;
270                status.error = Some(DiscoveryError::new(
271                    ErrorCode::Unauthenticated,
272                    "registration evidence belongs to a revoked identity",
273                ));
274            }
275        }
276        if !self.spec.eligibility.is_ready() || gate.failed || gate.close_started {
277            status.valid_until = None;
278        }
279        status
280    }
281}
282
283struct Admission<'a> {
284    core: &'a Core,
285    record: &'a Record,
286}
287impl WriteAdmission for Admission<'_> {
288    /// 业务作用:在 adapter 的实际传输提交点检查发布权并保存未知结果事实。
289    /// 参数说明:`submit` 是已经完成鉴权准备的同步提交动作。
290    /// 返回:许可有效时启动写入,失权时拒绝且不执行动作。
291    fn submit(&self, submit: &mut dyn FnMut()) -> Result<()> {
292        let (core, record) = (self.core, self.record);
293        let gate = lock(&core.gate);
294        let eligible = lock(&record.spec.eligibility.inner);
295        if gate.failed
296            || gate.close_started
297            || !matches!(gate.phase, OwnerPhase::Starting | OwnerPhase::Serving)
298            || !record.spec.eligibility.current(*eligible)
299            || core.registrations_stop.is_cancelled()
300        {
301            return Err(closed());
302        }
303        // 写前保留结果不明事实;等待 future 被取消时清理仍知道可能已经写入。
304        record.attempted.store(true, Ordering::Release);
305        record.uncertain.store(true, Ordering::Release);
306        submit();
307        Ok(())
308    }
309}
310
311/// 业务作用:每个注册目标只运行一个写入与健康监督序列。
312/// 参数说明:`core` 管理停止信号,`record` 保存不可变外部身份。
313/// 返回:关闭后退出;已确认身份的幂等续约可协调重试,管理端禁用、缺失及未解决的相反写入不授予重新注册权限。
314pub(crate) async fn run(core: Arc<Core>, record: Arc<Record>) {
315    let mut eligibility = record.spec.eligibility.changes.subscribe();
316    let mut prepared = record.connection.prepared.subscribe();
317    let admission = Admission {
318        core: &core,
319        record: &record,
320    };
321    let monitor = millis(record.spec.policy.monitor_interval_ms);
322    let interval = record
323        .connection
324        .provider
325        .renewal_interval(&record.spec.plan)
326        .unwrap_or(monitor)
327        .min(monitor);
328    let idempotent_renewal = record
329        .connection
330        .provider
331        .supports_idempotent_renewal(&record.spec.plan);
332    let mut registered = false;
333    let mut renewal_allowed = false;
334    let mut registration_blocked = false;
335    let mut backoff = record.connection.policy.initial_backoff_ms;
336    let mut attempt = 0u64;
337    loop {
338        let mut renewal_failed = false;
339        if core.registrations_stop.is_cancelled() {
340            return;
341        }
342        if !record.spec.eligibility.is_ready() || !*prepared.borrow() {
343            if registered {
344                // 本地失权先停止健康发布;只有本 owner 明确完成的删除才允许资格恢复后重建。
345                set_state(&core, &record, RegistrationState::Suspended, None);
346                withdraw(
347                    record.clone(),
348                    Instant::now() + millis(record.spec.policy.deregister_timeout_ms),
349                    millis(record.spec.policy.deregister_timeout_ms),
350                )
351                .await;
352                registered = false;
353                registration_blocked |= lock(&record.status).state != RegistrationState::Removed;
354                changed(&core);
355            }
356            tokio::select! {
357                _ = core.registrations_stop.cancelled() => return,
358                _ = eligibility.changed() => {},
359                _ = prepared.changed() => {},
360                _ = tokio::time::sleep(interval) => {},
361            }
362            continue;
363        }
364        if !registered && !registration_blocked {
365            set_state(&core, &record, RegistrationState::Registering, None);
366            let result = operation(
367                &core,
368                Instant::now() + millis(record.connection.policy.request_timeout_ms),
369                record
370                    .connection
371                    .provider
372                    .register(&record.spec.plan, &admission),
373            )
374            .await;
375            match result {
376                Ok(()) => {
377                    record.uncertain.store(false, Ordering::Release);
378                    registered = true;
379                    renewal_allowed = true;
380                    set_state(&core, &record, RegistrationState::PendingHealth, None);
381                }
382                Err(error) => {
383                    {
384                        let gate = lock(&core.gate);
385                        revoke_identity(&core, &gate, &record.connection, &error);
386                    }
387                    if definite_rejection(&error) {
388                        record.uncertain.store(false, Ordering::Release);
389                    }
390                    registration_blocked = record.uncertain.load(Ordering::Acquire)
391                        || matches!(
392                            error.code,
393                            ErrorCode::InvalidConfig | ErrorCode::Unsupported
394                        );
395                    // 首次占位冲突可能来自旧 owner 的延迟删除;只允许重新核对,不授予覆盖既有记录的权限。
396                    set_state(
397                        &core,
398                        &record,
399                        if record.uncertain.load(Ordering::Acquire) {
400                            RegistrationState::OutcomeUnknown
401                        } else {
402                            RegistrationState::Suspended
403                        },
404                        Some(error),
405                    );
406                }
407            }
408        }
409        if registered
410            && renewal_allowed
411            // 只有已确认身份且 provider 承诺相同 payload 幂等时,才可协调未知续约;不重放首次注册。
412            && (!record.uncertain.load(Ordering::Acquire) || idempotent_renewal)
413            && record
414                .connection
415                .provider
416                .renewal_interval(&record.spec.plan)
417                .is_some()
418        {
419            let result = operation(
420                &core,
421                Instant::now() + millis(record.connection.policy.request_timeout_ms),
422                record
423                    .connection
424                    .provider
425                    .renew(&record.spec.plan, true, &admission),
426            )
427            .await;
428            match result {
429                // 本次续约确认后才恢复健康回查;更早未决续约的清理限制由独立事实保留。
430                Ok(()) => record.uncertain.store(false, Ordering::Release),
431                Err(error) => {
432                    renewal_failed = true;
433                    {
434                        let gate = lock(&core.gate);
435                        revoke_identity(&core, &gate, &record.connection, &error);
436                    }
437                    if definite_rejection(&error) {
438                        record.uncertain.store(false, Ordering::Release);
439                    }
440                    if record.uncertain.load(Ordering::Acquire) {
441                        // 新续约成功只能证明当前同向状态;更早请求仍可能迟到,注销及后续重建必须保留此事实。
442                        record.unsettled_renewal.store(true, Ordering::Release);
443                        renewal_allowed = idempotent_renewal && !error.revokes_evidence();
444                        set_state(
445                            &core,
446                            &record,
447                            RegistrationState::OutcomeUnknown,
448                            Some(error),
449                        );
450                    } else {
451                        renewal_allowed = false;
452                        let mut status = lock(&record.status);
453                        if error.revokes_evidence() {
454                            status.valid_until = None;
455                            status.state = RegistrationState::Suspended;
456                        }
457                        status.error = Some(error);
458                    }
459                }
460            }
461        }
462        if registered && !record.uncertain.load(Ordering::Acquire) {
463            let revocation = record.connection.revocation.load(Ordering::Acquire);
464            let result = operation(
465                &core,
466                Instant::now() + millis(record.connection.policy.request_timeout_ms),
467                record.connection.provider.inspect(&record.spec.plan),
468            )
469            .await;
470            let gate = lock(&core.gate);
471            if core.registrations_stop.is_cancelled() || gate.close_started {
472                return;
473            }
474            if let Err(error) = &result {
475                if error.revokes_evidence() {
476                    renewal_allowed = false;
477                }
478                revoke_identity(&core, &gate, &record.connection, error);
479            }
480            // 回查期间发生撤权时,旧请求的 Healthy 或其他结果都不能发布;等待原身份下的新一轮证据。
481            if revocation == record.connection.revocation.load(Ordering::Acquire) {
482                let mut status = lock(&record.status);
483                match result {
484                    Ok(RegistrationEvidence::Healthy) if record.spec.eligibility.is_ready() => {
485                        registration_blocked = false;
486                        renewal_allowed = true;
487                        let now = Instant::now();
488                        status.state = RegistrationState::Active;
489                        status.observed_at = Some(now);
490                        status.valid_until =
491                            Some(now + millis(record.spec.policy.observation_grace_ms));
492                        status.error = None;
493                        status.startup_confirmed = true;
494                        record.confirmed.store(true, Ordering::Release);
495                        record
496                            .health_revocation
497                            .store(revocation, Ordering::Release);
498                    }
499                    Ok(RegistrationEvidence::Disabled | RegistrationEvidence::Missing) => {
500                        // 管理端移除和禁用只允许继续观察,不能据此取得重新注册的权限。
501                        renewal_allowed = false;
502                        registration_blocked = true;
503                        status.state = RegistrationState::Suspended;
504                        status.valid_until = None;
505                        status.error = Some(DiscoveryError::new(
506                            ErrorCode::NotReady,
507                            "registration was disabled or removed",
508                        ));
509                    }
510                    Ok(_) => {
511                        renewal_allowed = true;
512                        status.state = RegistrationState::PendingHealth;
513                        status.valid_until = None;
514                        status.error = None;
515                    }
516                    Err(error) => {
517                        // 不完整回查不产生新的健康事实,保留最后一次 Healthy 的固定期限;明确撤权仍立即关闭注册资格。
518                        if error.revokes_evidence() {
519                            status.state = RegistrationState::Suspended;
520                            status.valid_until = None;
521                            renewal_allowed = false;
522                        }
523                        status.error = Some(error);
524                    }
525                }
526            }
527        }
528        changed(&core);
529        let delay = if registered && !renewal_failed {
530            backoff = record.connection.policy.initial_backoff_ms;
531            attempt = 0;
532            interval
533        } else {
534            let delay = crate::retry_delay(&core, &record.connection, backoff, attempt);
535            attempt = attempt.wrapping_add(1);
536            backoff = backoff
537                .saturating_mul(2)
538                .min(record.connection.policy.max_backoff_ms);
539            // 续约响应持续失败也要退避,且不能比正常健康上报周期更频繁地重发。
540            delay.max(interval)
541        };
542        let next_attempt = Instant::now() + delay;
543        loop {
544            tokio::select! {
545                _ = core.registrations_stop.cancelled() => return,
546                // 持续刷新 true 不能绕过退避;实际失权立即唤醒,以便停止续约并执行摘流。
547                _ = eligibility.changed() => if !record.spec.eligibility.is_ready() { break; },
548                _ = tokio::time::sleep_until(next_attempt) => break,
549            }
550        }
551    }
552}
553
554/// 业务作用:注册或健康回查的认证撤销必须同时关闭该身份下的全部发现路由。
555/// 参数说明:`core` 提供通知,`gate` 是已持有的状态门禁,`connection` 是明确连接,`error` 是后端稳定错误类别。
556/// 返回:只有身份或权限失败会撤销连接证据,单注册冲突不影响其他服务。
557fn revoke_identity(core: &Core, gate: &Gate, connection: &Connection, error: &DiscoveryError) {
558    if matches!(
559        error.code,
560        ErrorCode::Unauthenticated | ErrorCode::PermissionDenied | ErrorCode::PermissionFiltered
561    ) {
562        crate::revoke_connection(core, gate, connection);
563    }
564}
565
566/// 业务作用:区分明确拒绝与请求可能已在远端执行的故障。
567/// 参数说明:`error` 由 adapter 将服务端拒绝映射为稳定类别。
568/// 返回:仅明确未执行的拒绝允许清除本次写入的不确定状态。
569fn definite_rejection(error: &DiscoveryError) -> bool {
570    matches!(
571        error.code,
572        ErrorCode::InvalidConfig
573            | ErrorCode::Unauthenticated
574            | ErrorCode::PermissionDenied
575            | ErrorCode::Conflict
576            | ErrorCode::Unsupported
577    )
578}
579
580/// 业务作用:发布注册状态变化,不在状态锁内执行后端调用。
581/// 参数说明:`core` 提供通知,`record` 是目标,`state`、`error` 是当前裁决。
582/// 返回:非 Active 状态同步清除就绪有效期。
583fn set_state(
584    core: &Core,
585    record: &Record,
586    state: RegistrationState,
587    error: Option<DiscoveryError>,
588) {
589    let mut status = lock(&record.status);
590    status.state = state;
591    status.error = error;
592    if state != RegistrationState::Active {
593        status.valid_until = None;
594    }
595    drop(status);
596    changed(core);
597}
598
599/// 业务作用:尝试删除本 owner 曾提交的注册,结果不明时保留残留事实。
600/// 参数说明:`record` 是精确计划,`deadline` 是全局期限,`share` 防止单目标耗尽全部预算。
601/// 返回:没有写过或明确删除且无旧未知写时标记 Removed,其余标记 Residual。
602pub(crate) async fn withdraw(record: Arc<Record>, deadline: Instant, share: Duration) {
603    if !record.attempted.load(Ordering::Acquire) {
604        lock(&record.status).state = RegistrationState::Removed;
605        return;
606    }
607    if lock(&record.status).state == RegistrationState::Removed {
608        return;
609    }
610    // 后续同向续约或普通回查不能证明旧请求已终止,清理 ACK 不得清除其迟到风险或开放重新注册。
611    let uncertain = record.uncertain.load(Ordering::Acquire)
612        || record.unsettled_renewal.load(Ordering::Acquire);
613    lock(&record.status).state = RegistrationState::Withdrawing;
614    let until =
615        deadline.min(Instant::now() + share.min(millis(record.spec.policy.deregister_timeout_ms)));
616    let result = tokio::time::timeout_at(
617        until,
618        record.connection.provider.deregister(&record.spec.plan),
619    )
620    .await
621    .unwrap_or_else(|_| Err(timeout()));
622    let mut status = lock(&record.status);
623    status.valid_until = None;
624    match result {
625        Ok(()) if !uncertain => {
626            status.state = RegistrationState::Removed;
627            status.error = None;
628            record.attempted.store(false, Ordering::Release);
629        }
630        Ok(()) => {
631            status.state = RegistrationState::Residual;
632            status.error = Some(DiscoveryError::new(
633                ErrorCode::OutcomeUnknown,
634                "an earlier write may still complete after cleanup",
635            ));
636        }
637        Err(error) => {
638            record.uncertain.store(true, Ordering::Release);
639            status.state = RegistrationState::Residual;
640            status.error = Some(error);
641        }
642    }
643}
644
645/// 业务作用:摘流终止后保留不能证明已删除的外部记录。
646/// 参数说明:`record` 的写入和摘流任务均已结束。
647/// 返回:未写入的目标为 Removed,其余未完成目标为 Residual。
648pub(crate) fn finish_withdrawal(record: &Record) {
649    let mut status = lock(&record.status);
650    status.valid_until = None;
651    if !record.attempted.load(Ordering::Acquire) {
652        status.state = RegistrationState::Removed;
653    } else if !matches!(
654        status.state,
655        RegistrationState::Removed | RegistrationState::Residual
656    ) {
657        status.state = RegistrationState::Residual;
658        status.error = Some(DiscoveryError::new(
659            ErrorCode::OutcomeUnknown,
660            "registration cleanup did not complete",
661        ));
662    }
663}