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