Skip to main content

nadisc_runtime/
owner.rs

1//! 资源关闭权与只读句柄分离。停机发起同步撤权,等待凭据不拥有清理任务。
2
3use crate::{
4    changed, closed,
5    config::{invalid, validate_name, ConnectionPolicy, Limits},
6    lock, millis, operation,
7    registration::{Record, RegistrationSpec, RegistrationStatus},
8    spawn_owned, timeout, Connection, Core, DiscoveryHandle, Gate, Quota,
9};
10use nadisc::managed::{DiscoveryError, ErrorCode, ManagedProvider, Result};
11use std::{
12    collections::{BTreeMap, BTreeSet},
13    sync::{atomic::AtomicU64, Arc, Mutex},
14};
15use tokio::{
16    sync::{watch, Semaphore},
17    time::Instant,
18};
19use tokio_util::sync::CancellationToken;
20
21/// 发现 owner 的生命周期阶段;关闭后不能重新开放路由。
22#[derive(Clone, Copy, Debug, Eq, PartialEq)]
23pub enum OwnerPhase {
24    /// 目录已构造,尚未启动后端连接。
25    Prepared,
26    /// 正在准备连接或确认首次注册。
27    Starting,
28    /// 已完成发布阶段,可按有效证据提供发现。
29    Serving,
30    /// 已撤销发布资格,正在摘流或等待业务排空。
31    Quiescing,
32    /// 资源关闭已开始或完成,不再接纳新发现工作。
33    Closed,
34}
35
36/// 由宿主提供的具名后端及其冻结策略。
37pub struct ConnectionSpec {
38    /// 连接的本地 qualifier,不是后端服务名称。
39    pub name: String,
40    /// 后端类型或实例,决定该连接支持的发现及注册能力。
41    pub provider: Arc<dyn ManagedProvider>,
42    /// 目标连接或注册的冻结策略。
43    pub policy: ConnectionPolicy,
44}
45
46/// 在网络活动之前完成的类型化连接目录。
47pub struct OwnerBuilder {
48    limits: Limits,
49    connections: BTreeMap<String, ConnectionSpec>,
50    default_connection: Option<String>,
51}
52
53impl Default for OwnerBuilder {
54    /// 业务作用:建立不连接任何后端的空目录。
55    ///
56    /// 参数说明:无。
57    ///
58    /// 返回:显式添加连接后才产生网络活动。
59    fn default() -> Self {
60        Self {
61            limits: Limits::default(),
62            connections: BTreeMap::new(),
63            default_connection: None,
64        }
65    }
66}
67
68impl OwnerBuilder {
69    /// 业务作用:设置所有连接共享的有限资源预算。
70    ///
71    /// 参数说明:
72    /// - `limits`:是实例、任务和并发容量。
73    ///
74    /// 返回:构造时统一校验的 builder。
75    pub fn limits(mut self, limits: Limits) -> Self {
76        self.limits = limits;
77        self
78    }
79
80    /// 业务作用:兼容使用显式默认引用的宿主;新目录可直接将默认连接命名为 primary。
81    ///
82    /// 参数说明:
83    /// - `name`:必须引用已登记的连接,不影响显式 qualifier 的匹配。
84    ///
85    /// 返回:构造时校验引用的 builder;省略此入口时只采用 primary。
86    pub fn default_connection(mut self, name: impl Into<String>) -> Self {
87        self.default_connection = Some(name.into());
88        self
89    }
90
91    /// 业务作用:登记一个独立 provider 实例并拒绝重复连接别名。
92    ///
93    /// 参数说明:
94    /// - `spec`:包含名称、后端和冻结策略。
95    ///
96    /// 返回:合法连接加入目录;错误不触发网络访问。
97    pub fn connection(mut self, spec: ConnectionSpec) -> Result<Self> {
98        validate_name(&spec.name)?;
99        spec.policy.validate()?;
100        if self.connections.contains_key(&spec.name) {
101            return Err(invalid("duplicate connection name"));
102        }
103        self.connections.insert(spec.name.clone(), spec);
104        Ok(self)
105    }
106
107    /// 业务作用:创建唯一控制 owner,使初始化取消后仍有资源清理权。
108    ///
109    /// 参数说明:无。
110    ///
111    /// 返回:无网络副作用的 owner;非法显式默认引用拒绝,无 primary 的目录只允许具名选择。
112    pub fn build(self) -> Result<DiscoveryOwner> {
113        self.limits.validate()?;
114        if self.connections.len() > self.limits.max_connections {
115            return Err(invalid("connection capacity exceeded"));
116        }
117        let default_connection = match self.default_connection {
118            Some(name) if self.connections.contains_key(&name) => Some(name),
119            Some(_) => return Err(invalid("unknown default connection")),
120            None => self
121                .connections
122                .contains_key("primary")
123                .then(|| "primary".into()),
124        };
125        let connections = self
126            .connections
127            .into_iter()
128            .map(|(name, spec)| {
129                let connection = Arc::new(Connection {
130                    name: name.clone(),
131                    provider: spec.provider,
132                    policy: spec.policy,
133                    slots: Mutex::new(BTreeMap::new()),
134                    prepared: watch::channel(false).0,
135                    error: Mutex::new(None),
136                    epoch: AtomicU64::new(1),
137                    revocation: AtomicU64::new(0),
138                    queries: AtomicU64::new(0),
139                    query_errors: AtomicU64::new(0),
140                    query_micros: AtomicU64::new(0),
141                });
142                (name, connection)
143            })
144            .collect();
145        let core = Arc::new(Core {
146            id: uuid::Uuid::new_v4().to_string(),
147            gate: Mutex::new(Gate {
148                phase: OwnerPhase::Prepared,
149                registrations: BTreeMap::new(),
150                frozen: false,
151                close_started: false,
152                failed: false,
153                shutdown_deadline: None,
154            }),
155            connections,
156            default_connection,
157            operations: Semaphore::new(self.limits.max_parallel_operations),
158            watches: Semaphore::new(self.limits.max_watch_streams),
159            quota: Arc::new(Mutex::new(Quota::default())),
160            bridges: Arc::new(Semaphore::new(
161                self.limits
162                    .max_service_watches_per_connection
163                    .saturating_mul(self.limits.max_connections),
164            )),
165            transports: Arc::new(Semaphore::new(self.limits.max_transport_tasks)),
166            limits: self.limits,
167            tasks: Mutex::new(Vec::new()),
168            registration_tasks: Mutex::new(Vec::new()),
169            withdrawal_task: Mutex::new(None),
170            close_task: Mutex::new(None),
171            stop: CancellationToken::new(),
172            registrations_stop: CancellationToken::new(),
173            changes: watch::channel(0).0,
174            close: watch::channel(None).0,
175            withdrawn: watch::channel(None).0,
176        });
177        Ok(DiscoveryOwner {
178            core,
179        })
180    }
181}
182
183/// 非 Clone 的控制对象;查询句柄和等待凭据不继承关闭权。
184pub struct DiscoveryOwner {
185    core: Arc<Core>,
186}
187
188impl DiscoveryOwner {
189    /// 业务作用:启动独立连接监督并等待必需连接达到所需作用域的访问条件。
190    ///
191    /// 参数说明:
192    /// - `deadline`:是宿主原启动截止点,取消等待不会丢失后台资源归属。
193    ///
194    /// 返回:必需连接准备成功;关闭或期限耗尽返回错误。
195    pub async fn prepare(&self, deadline: Instant) -> Result<()> {
196        if Instant::now() >= deadline {
197            return Err(timeout());
198        }
199        {
200            let mut gate = lock(&self.core.gate);
201            match gate.phase {
202                OwnerPhase::Prepared => {
203                    gate.phase = OwnerPhase::Starting;
204                    for connection in self.core.connections.values() {
205                        let (core, connection) = (self.core.clone(), connection.clone());
206                        spawn_owned(
207                            &self.core,
208                            async move { prepare_loop(core, connection).await },
209                        );
210                    }
211                }
212                OwnerPhase::Starting => {}
213                _ => return Err(invalid("owner cannot prepare in its current phase")),
214            }
215        }
216        let mut changes = self.core.changes.subscribe();
217        loop {
218            if self.core.stop.is_cancelled() {
219                return Err(closed());
220            }
221            if Instant::now() >= deadline {
222                return Err(timeout());
223            }
224            let ready = self
225                .core
226                .connections
227                .values()
228                .filter(|connection| connection.policy.startup_required)
229                .all(|connection| *connection.prepared.borrow());
230            if ready {
231                return Ok(());
232            }
233            tokio::time::timeout_at(deadline, changes.changed())
234                .await
235                .map_err(|_| timeout())?
236                .map_err(|_| closed())?;
237        }
238    }
239
240    /// 业务作用:取得精确连接或默认 primary,不跨环境回退。
241    ///
242    /// 参数说明:
243    /// - `qualifier`:为空时采用 primary;兼容宿主显式设置的默认引用优先。
244    ///
245    /// 返回:只有查询权的句柄;不存在或已关闭时失败。
246    pub fn client(&self, qualifier: Option<&str>) -> Result<DiscoveryHandle> {
247        let gate = lock(&self.core.gate);
248        if gate.close_started || gate.failed {
249            return Err(closed());
250        }
251        let name = qualifier
252            .or(self.core.default_connection.as_deref())
253            .ok_or_else(|| invalid("no default discovery connection"))?;
254        let connection = self
255            .core
256            .connections
257            .get(name)
258            .cloned()
259            .ok_or_else(|| invalid("unknown discovery connection"))?;
260        Ok(DiscoveryHandle {
261            core: Arc::downgrade(&self.core),
262            connection,
263        })
264    }
265
266    /// 业务作用:一次性校验并冻结全部最终注册计划,避免部分发布后才发现引用非法。
267    ///
268    /// 参数说明:
269    /// - `specs`:已解析实际 listener、对外端点和本地资格来源。
270    ///
271    /// 返回:全部计划合法时原子登记,任何错误均不发出注册请求。
272    pub fn freeze_registrations(&self, specs: Vec<RegistrationSpec>) -> Result<()> {
273        let mut gate = lock(&self.core.gate);
274        if gate.frozen || gate.phase != OwnerPhase::Starting {
275            return Err(invalid(
276                "registration plans are already frozen or owner is not starting",
277            ));
278        }
279        if specs.len() > self.core.limits.max_registrations {
280            return Err(invalid("registration capacity exceeded"));
281        }
282        let mut records = BTreeMap::new();
283        let mut keys = BTreeSet::new();
284        for mut spec in specs {
285            validate_name(&spec.plan.id)?;
286            let connection = self
287                .core
288                .connections
289                .get(&spec.connection_ref)
290                .cloned()
291                .ok_or_else(|| invalid("unknown registration connection"))?;
292            spec.policy.validate(&connection.policy)?;
293            if !connection.provider.capabilities().registration {
294                return Err(invalid("registration provider is read only"));
295            }
296            if spec
297                .plan
298                .instance
299                .metadata
300                .contains_key("nasa.discovery.owner")
301            {
302                return Err(invalid("registration ownership metadata is reserved"));
303            }
304            spec.plan
305                .instance
306                .metadata
307                .insert("nasa.discovery.owner".into(), self.core.id.clone());
308            let instance = &spec.plan.instance;
309            if instance.ip.trim() != instance.ip
310                || instance.ip.is_empty()
311                || instance.port == 0
312                || instance.ip == "0.0.0.0"
313                || instance.ip == "::"
314                || instance.ip == "[::]"
315                || !instance.weight.is_finite()
316                || instance.weight < 0.0
317                || spec.plan.service.is_empty()
318            {
319                return Err(invalid("registration endpoint or weight is invalid"));
320            }
321            connection.provider.validate_registration(&spec.plan)?;
322            if !keys.insert(connection.provider.registration_key(&spec.plan))
323                || records.contains_key(&spec.plan.id)
324            {
325                return Err(invalid(
326                    "duplicate external registration key or local registration name",
327                ));
328            }
329            records.insert(
330                spec.plan.id.clone(),
331                Arc::new(Record::new(spec, connection)),
332            );
333        }
334        gate.registrations = records;
335        gate.frozen = true;
336        Ok(())
337    }
338
339    /// 业务作用:在宿主监听器可接流后启动注册监督,等待启动必需目标的真实确认。
340    ///
341    /// 参数说明:
342    /// - `deadline`:延续原启动期限,不为注册重新计时。
343    ///
344    /// 返回:全部启动必需注册确认后进入 Serving;失败由宿主发起有界清理。
345    pub async fn publish(&self, deadline: Instant) -> Result<()> {
346        if Instant::now() >= deadline {
347            return Err(timeout());
348        }
349        {
350            let gate = lock(&self.core.gate);
351            if !gate.frozen
352                || gate.phase != OwnerPhase::Starting
353                || gate.failed
354                || gate.close_started
355            {
356                return Err(invalid("owner is not ready to publish frozen plans"));
357            }
358            for record in gate.registrations.values() {
359                if !record
360                    .started
361                    .swap(true, std::sync::atomic::Ordering::AcqRel)
362                {
363                    let (core, record) = (self.core.clone(), record.clone());
364                    crate::spawn_registration(&self.core, async move {
365                        crate::registration::run(core, record).await
366                    });
367                }
368            }
369        }
370        let mut changes = self.core.changes.subscribe();
371        loop {
372            {
373                let mut gate = lock(&self.core.gate);
374                if gate.phase != OwnerPhase::Starting || gate.failed || gate.close_started {
375                    return Err(closed());
376                }
377                // 全部证据同时有效也不能延长宿主原启动期限。
378                if Instant::now() >= deadline {
379                    return Err(timeout());
380                }
381                let ready = gate
382                    .registrations
383                    .values()
384                    .filter(|record| record.spec.policy.startup_required)
385                    .all(|record| record.current_status(&gate).is_ready());
386                if ready {
387                    gate.phase = OwnerPhase::Serving;
388                    return Ok(());
389                }
390            }
391            tokio::time::timeout_at(deadline, changes.changed())
392                .await
393                .map_err(|_| timeout())?
394                .map_err(|_| closed())?;
395        }
396    }
397
398    /// 业务作用:同步撤销所有新注册和健康上报许可,保留出站发现供业务排空。
399    ///
400    /// 参数说明:
401    /// - `deadline`:是摘流、业务排空与最终关闭共享的停机截止点。
402    ///
403    /// 返回:重复调用等待同一摘流结果;出站资源保留到 begin_close。
404    pub fn begin_quiesce(&self, deadline: Instant) -> QuiesceTicket {
405        begin_quiesce(self.core.clone(), deadline)
406    }
407
408    /// 业务作用:发起唯一有界清理并同步撤销后续出站发送,等待取消不终止清理。
409    ///
410    /// 参数说明:
411    /// - `deadline`:是宿主停机总截止点,首次调用后不能延长。
412    ///
413    /// 返回:可复制的只读关闭等待凭据。
414    pub fn begin_close(&self, deadline: Instant) -> CloseTicket {
415        begin_close(self.core.clone(), deadline)
416    }
417
418    /// 业务作用:读取当前 owner 阶段。
419    ///
420    /// 参数说明:无。
421    ///
422    /// 返回:单调推进的生命周期阶段。
423    pub fn phase(&self) -> OwnerPhase {
424        lock(&self.core.gate).phase
425    }
426
427    /// 业务作用:读取注册监督事实,供宿主映射就绪贡献和运维状态。
428    ///
429    /// 参数说明:无。
430    ///
431    /// 返回:按当前连接身份代次及本地资格裁决的状态;已撤权的注册不得继续贡献就绪,不输出凭据或 metadata。
432    pub fn registrations(&self) -> Vec<RegistrationStatus> {
433        let gate = lock(&self.core.gate);
434        gate.registrations
435            .values()
436            .map(|record| record.current_status(&gate))
437            .collect()
438    }
439
440    /// 业务作用:提供状态变化通知,允许宿主按同一事实监督而不复制后端轮询。
441    ///
442    /// 参数说明:无。
443    ///
444    /// 返回:只读通知流,收到通知后重新读取完整状态。
445    pub fn changes(&self) -> watch::Receiver<u64> {
446        self.core.changes.subscribe()
447    }
448
449    /// 业务作用:向监控与运维代码提供没有关闭权的有界状态入口。
450    ///
451    /// 参数说明:无。
452    ///
453    /// 返回:观察器不延长 owner 的生命周期,也不创建后端任务。
454    pub fn observer(&self) -> crate::RuntimeObserver {
455        crate::RuntimeObserver {
456            core: Arc::downgrade(&self.core),
457        }
458    }
459
460    /// 业务作用:在冻结发布计划前让宿主比较后端查询作用域,防止别名掩盖协议投影冲突。
461    ///
462    /// 参数说明:
463    /// - `connection`:当前 owner 目录中的连接名。
464    /// - `service`:交给该 provider 校验和投影的服务查询名。
465    ///
466    /// 返回:连接存在时原样返回 provider 的查询键或验证错误,未知连接时报错;本方法不独立检查服务是否在冻结目录中,不产生网络请求。
467    pub fn service_key(&self, connection: &str, service: &str) -> Result<String> {
468        self.core
469            .connections
470            .get(connection)
471            .ok_or_else(|| invalid("unknown discovery connection"))?
472            .provider
473            .service_key(service)
474    }
475}
476
477impl Drop for DiscoveryOwner {
478    /// 业务作用:控制对象丢弃时立即撤权,并在 Tokio 仍存在时触发有限清理。
479    ///
480    /// 参数说明:无。
481    ///
482    /// 返回:显式关闭以外的兜底不保证 runtime 退出后的网络清理。
483    fn drop(&mut self) {
484        if !lock(&self.core.gate).close_started {
485            if tokio::runtime::Handle::try_current().is_ok() {
486                begin_close(self.core.clone(), Instant::now() + millis(3000));
487            } else {
488                let mut gate = lock(&self.core.gate);
489                gate.phase = OwnerPhase::Closed;
490                gate.close_started = true;
491                self.core.stop.cancel();
492                self.core.registrations_stop.cancel();
493                for task in lock(&self.core.tasks).drain(..) {
494                    task.abort();
495                }
496                for task in lock(&self.core.registration_tasks).drain(..) {
497                    task.abort();
498                }
499            }
500        }
501    }
502}
503
504/// 关闭完成后的注册残留、后端错误和期限摘要。
505#[derive(Clone, Debug)]
506pub struct CloseReport {
507    /// 注册目标集合,包含各目标独立的状态或发布策略。
508    pub registrations: Vec<RegistrationStatus>,
509    /// 按连接 qualifier 记录的后端关闭错误。
510    pub provider_errors: BTreeMap<String, DiscoveryError>,
511    /// 关闭总期限是否耗尽;为真时仍需核对注册残留。
512    pub deadline_elapsed: bool,
513}
514
515/// 关闭结果的等待凭据;丢弃凭据不取消后台清理。
516#[derive(Clone)]
517pub struct CloseTicket {
518    receiver: watch::Receiver<Option<CloseReport>>,
519}
520impl CloseTicket {
521    /// 业务作用:等待已发起清理的同一结果,取消本次等待不取消注销。
522    ///
523    /// 参数说明:无。
524    ///
525    /// 返回:包括结果不明和残留的关闭摘要。
526    pub async fn wait(mut self) -> Result<CloseReport> {
527        loop {
528            if let Some(report) = self.receiver.borrow().clone() {
529                return Ok(report);
530            }
531            self.receiver.changed().await.map_err(|_| closed())?;
532        }
533    }
534}
535
536/// 摘流结果的等待凭据;保留业务排空所需的发现连接。
537#[derive(Clone)]
538pub struct QuiesceTicket {
539    receiver: watch::Receiver<Option<Vec<RegistrationStatus>>>,
540}
541impl QuiesceTicket {
542    /// 业务作用:等待注册摘流而不关闭业务排空需要的发现连接。
543    ///
544    /// 参数说明:无。
545    ///
546    /// 返回:全部目标的注销或残留事实,等待取消不停止摘流。
547    pub async fn wait(mut self) -> Result<Vec<RegistrationStatus>> {
548        loop {
549            if let Some(statuses) = self.receiver.borrow().clone() {
550                return Ok(statuses);
551            }
552            self.receiver.changed().await.map_err(|_| closed())?;
553        }
554    }
555}
556
557/// 业务作用:冻结停机总期限并在撤权后接管注册摘流工作。
558///
559/// 参数说明:
560/// - `core`:是受管状态
561/// - `deadline`:首次调用后不能延长。
562///
563/// 返回:单次摘流的只读凭据。
564fn begin_quiesce(core: Arc<Core>, deadline: Instant) -> QuiesceTicket {
565    let mut gate = lock(&core.gate);
566    let ticket = QuiesceTicket {
567        receiver: core.withdrawn.subscribe(),
568    };
569    if gate.shutdown_deadline.is_none() {
570        gate.shutdown_deadline = Some(deadline);
571        gate.phase = OwnerPhase::Quiescing;
572        core.registrations_stop.cancel();
573        let shared = core.clone();
574        *lock(&core.withdrawal_task) = Some(tokio::spawn(async move {
575            use futures_util::FutureExt;
576            let _ = std::panic::AssertUnwindSafe(cleanup_registrations(shared.clone(), deadline))
577                .catch_unwind()
578                .await;
579            finish_withdrawal(&shared);
580        }));
581    }
582    ticket
583}
584
585/// 业务作用:在资源关闭门禁内冻结首次截止点并接管清理工作。
586///
587/// 参数说明:
588/// - `core`:是共享资源
589/// - `deadline`:只在第一次关闭时生效。
590///
591/// 返回:所有调用观察同一关闭结果。
592fn begin_close(core: Arc<Core>, deadline: Instant) -> CloseTicket {
593    begin_quiesce(core.clone(), deadline);
594    let mut gate = lock(&core.gate);
595    let ticket = CloseTicket {
596        receiver: core.close.subscribe(),
597    };
598    if !gate.close_started {
599        // 先关闭发送与发布许可,再启动异步清理,避免等待者取消后继续接流。
600        gate.close_started = true;
601        gate.phase = OwnerPhase::Quiescing;
602        core.registrations_stop.cancel();
603        core.stop.cancel();
604        let deadline = gate.shutdown_deadline.unwrap_or(deadline).min(deadline);
605        let shared = core.clone();
606        *lock(&core.close_task) = Some(tokio::spawn(async move {
607            use futures_util::FutureExt;
608            if std::panic::AssertUnwindSafe(cleanup(shared.clone(), deadline))
609                .catch_unwind()
610                .await
611                .is_err()
612            {
613                finish_withdrawal(&shared);
614                lock(&shared.gate).phase = OwnerPhase::Closed;
615                shared.close.send_replace(Some(CloseReport {
616                    registrations: lock(&shared.gate)
617                        .registrations
618                        .values()
619                        .map(|r| lock(&r.status).clone())
620                        .collect(),
621                    provider_errors: shared
622                        .connections
623                        .keys()
624                        .map(|name| {
625                            (
626                                name.clone(),
627                                DiscoveryError::new(
628                                    nadisc::managed::ErrorCode::Unavailable,
629                                    "provider cleanup was interrupted",
630                                ),
631                            )
632                        })
633                        .collect(),
634                    deadline_elapsed: Instant::now() >= deadline,
635                }));
636            }
637        }));
638    }
639    ticket
640}
641
642/// 业务作用:每连接独立准备和退避,不让可选连接阻塞其他环境。
643///
644/// 参数说明:
645/// - `core`:提供共享预算
646/// - `connection`:是单一身份作用域。
647///
648/// 返回:准备成功或关闭后退出;后续发现和注册各自更新真实证据。
649async fn prepare_loop(core: Arc<Core>, connection: Arc<Connection>) {
650    let mut backoff = connection.policy.initial_backoff_ms;
651    let mut attempt = 0u64;
652    loop {
653        let result = operation(
654            &core,
655            Instant::now() + millis(connection.policy.request_timeout_ms),
656            connection.provider.prepare(),
657        )
658        .await;
659        if core.stop.is_cancelled() {
660            break;
661        }
662        match result {
663            Ok(()) => {
664                connection.prepared.send_replace(true);
665                *lock(&connection.error) = None;
666                changed(&core);
667                return;
668            }
669            Err(error) => {
670                if matches!(
671                    error.code,
672                    ErrorCode::Unauthenticated
673                        | ErrorCode::PermissionDenied
674                        | ErrorCode::PermissionFiltered
675                ) {
676                    let gate = lock(&core.gate);
677                    crate::revoke_connection(&core, &gate, &connection);
678                }
679                *lock(&connection.error) = Some(error);
680                changed(&core);
681            }
682        }
683        tokio::select! { _ = core.stop.cancelled() => break, _ = tokio::time::sleep(crate::retry_delay(&core, &connection, backoff, attempt)) => {} }
684        attempt = attempt.wrapping_add(1);
685        backoff = backoff
686            .saturating_mul(2)
687            .min(connection.policy.max_backoff_ms);
688    }
689}
690
691/// 业务作用:先终止本地写任务,再并行尝试各目标清理并报告不能证明删除的记录。
692///
693/// 参数说明:
694/// - `core`:保留所有已尝试写入事实
695/// - `deadline`:覆盖全部等待与后端关闭。
696///
697/// 返回:发布一次最终摘要,不因单个后端失败放弃其他目标。
698async fn cleanup_registrations(core: Arc<Core>, deadline: Instant) {
699    use futures_util::{stream, StreamExt};
700    let tasks = std::mem::take(&mut *lock(&core.registration_tasks));
701    for task in &tasks {
702        task.abort();
703    }
704    for task in tasks {
705        let _ = tokio::time::timeout_at(deadline, task).await;
706    }
707    let records: Vec<_> = lock(&core.gate).registrations.values().cloned().collect();
708    let count = records.len().max(1);
709    let share = deadline.saturating_duration_since(Instant::now())
710        / count.div_ceil(core.limits.max_parallel_operations) as u32;
711    stream::iter(records.iter().cloned())
712        .for_each_concurrent(core.limits.max_parallel_operations, |record| async move {
713            use futures_util::FutureExt;
714            let _ = std::panic::AssertUnwindSafe(crate::registration::withdraw(
715                record, deadline, share,
716            ))
717            .catch_unwind()
718            .await;
719        })
720        .await;
721}
722
723/// 业务作用:将未完成的摘流记录转换为明确残留,并唤醒全部等待者。
724///
725/// 参数说明:
726/// - `core`:的注册写任务必须已停止,或正在进入全局失败关闭。
727///
728/// 返回:最终摘要不会包含看似仍在正常执行的 Withdrawing 状态。
729fn finish_withdrawal(core: &Core) {
730    let gate = lock(&core.gate);
731    for record in gate.registrations.values() {
732        crate::registration::finish_withdrawal(record);
733    }
734    core.withdrawn.send_replace(Some(
735        gate.registrations
736            .values()
737            .map(|record| lock(&record.status).clone())
738            .collect(),
739    ));
740    changed(core);
741}
742
743/// 业务作用:业务排空后结束出站观察,等待既有摘流并关闭全部 provider。
744///
745/// 参数说明:
746/// - `core`:保留清理记录
747/// - `deadline`:不晚于首次摘流确定的总期限。
748///
749/// 返回:发布最终资源清理摘要,不抹去注销残留。
750async fn cleanup(core: Arc<Core>, deadline: Instant) {
751    use futures_util::{stream, StreamExt};
752    let withdrawal = lock(&core.withdrawal_task).take();
753    if let Some(mut task) = withdrawal {
754        if tokio::time::timeout_at(deadline, &mut task).await.is_err() {
755            task.abort();
756            let _ = task.await;
757        }
758    }
759    // 先终止摘流任务再冻结摘要,避免关闭报告与迟到状态写入互相覆盖。
760    finish_withdrawal(&core);
761    let tasks = std::mem::take(&mut *lock(&core.tasks));
762    for task in &tasks {
763        task.abort();
764    }
765    for task in tasks {
766        let _ = tokio::time::timeout_at(deadline, task).await;
767    }
768    let records: Vec<_> = lock(&core.gate).registrations.values().cloned().collect();
769    let providers = stream::iter(core.connections.values().cloned())
770        .map(|connection| async move {
771            use futures_util::FutureExt;
772            let result = tokio::time::timeout_at(
773                deadline,
774                std::panic::AssertUnwindSafe(connection.provider.close()).catch_unwind(),
775            )
776            .await
777            .map(|result| {
778                result.unwrap_or_else(|_| {
779                    Err(DiscoveryError::new(
780                        nadisc::managed::ErrorCode::Unavailable,
781                        "provider cleanup was interrupted",
782                    ))
783                })
784            })
785            .unwrap_or_else(|_| Err(timeout()));
786            (connection.name.clone(), result)
787        })
788        .buffer_unordered(core.limits.max_parallel_operations)
789        .collect::<Vec<_>>()
790        .await;
791    let provider_errors = providers
792        .into_iter()
793        .filter_map(|(name, result)| result.err().map(|error| (name, error)))
794        .collect();
795    let registrations = records
796        .iter()
797        .map(|record| lock(&record.status).clone())
798        .collect();
799    lock(&core.gate).phase = OwnerPhase::Closed;
800    // 清理结果保留 Residual,不能把补偿响应当作迟到旧写已结束的证明。
801    core.close.send_replace(Some(CloseReport {
802        registrations,
803        provider_errors,
804        deadline_elapsed: Instant::now() >= deadline,
805    }));
806    changed(&core);
807}