Skip to main content

nadisc_runtime/
observation.rs

1//! 共享观察与发送资格。有效性槽独立于快照分配额度,撤权不需要分配实例数组。
2
3use crate::{
4    closed, lock, millis, operation, spawn_owned, timeout, Connection, Core, OwnerPhase, Quota,
5};
6use nadisc::managed::{DiscoveryError, ErrorCode, EvidenceKind, Result};
7use nadisc::{
8    is_traffic_instance, DiscoveryClient, Instance, ServiceWatch, ServiceWatchGuard, WatchOptions,
9};
10use std::{
11    collections::BTreeSet,
12    sync::{atomic::Ordering, Arc, Mutex, Weak},
13};
14use tokio::{sync::watch, time::Instant};
15use tokio_util::sync::CancellationToken;
16
17pub(crate) struct Slot {
18    pub service: String,
19    pub state: Mutex<State>,
20    pub changes: watch::Sender<u64>,
21}
22
23pub(crate) struct State {
24    data: Option<Arc<Data>>,
25    pub valid_until: Option<Instant>,
26    pub observed_at: Option<Instant>,
27    pub error: Option<DiscoveryError>,
28    sequence: u64,
29    epoch: u64,
30    revocation: u64,
31    touched: Instant,
32}
33
34struct Data {
35    instances: Vec<Instance>,
36    identities: Vec<String>,
37    evidence: EvidenceKind,
38    bytes: usize,
39    quota: Arc<Mutex<Quota>>,
40}
41
42impl Drop for Data {
43    /// 业务作用:最后一个受管引用释放时归还旧快照占用的配额。
44    ///
45    /// 参数说明:无。
46    ///
47    /// 返回:慢消费者持有的旧代在释放前持续计入总容量。
48    fn drop(&mut self) {
49        let mut quota = lock(&self.quota);
50        quota.instances -= self.instances.len();
51        quota.bytes -= self.bytes;
52    }
53}
54
55/// 保留受管数据的只读凭据;数据用于诊断或选址,提交请求仍需再次检查 Observation。
56#[derive(Clone)]
57pub struct Snapshot {
58    data: Arc<Data>,
59    /// 产生证据的 owner 标识,用于区分独立运行时。
60    pub owner_id: String,
61    /// 证据所属连接的 qualifier。
62    pub connection: String,
63    /// 证据对应的逻辑服务名。
64    pub service: String,
65    /// 后端身份代次;失权前的证据不能跨代继续使用。
66    pub epoch: u64,
67    /// 同一观察中的快照序号,用于区分实例集合变化。
68    pub sequence: u64,
69    /// 当前证据被确认的本地单调时刻。
70    pub observed_at: Instant,
71    /// 当前证据的有效截止时刻;不替代提交请求时的复验。
72    pub valid_until: Option<Instant>,
73}
74
75impl Snapshot {
76    /// 业务作用:读取后端完整实例事实,包括不承载流量的实例。
77    ///
78    /// 参数说明:无。
79    ///
80    /// 返回:共享只读列表;该切片本身不构成持续发送许可。
81    pub fn instances(&self) -> &[Instance] {
82        &self.data.instances
83    }
84
85    /// 业务作用:提供与完整实例列表逐项绑定的后端身份,供诊断和自定义协议保留所有权边界。
86    ///
87    /// 参数说明:无。
88    ///
89    /// 返回:当前连接及服务作用域内的稳定键,不能用作无界指标标签。
90    pub fn identities(&self) -> &[String] {
91        &self.data.identities
92    }
93
94    /// 业务作用:区分地址解析、静态配置与后端健康证据。
95    ///
96    /// 参数说明:无。
97    ///
98    /// 返回:该快照真实采用的证据等级。
99    pub fn evidence(&self) -> EvidenceKind {
100        self.data.evidence
101    }
102}
103
104/// 只有观察权的具名连接句柄,不能关闭其他消费者使用的连接。
105#[derive(Clone)]
106pub struct DiscoveryHandle {
107    pub(crate) core: Weak<Core>,
108    pub(crate) connection: Arc<Connection>,
109}
110
111/// 服务观察凭据。变更通知可合并,当前有效性始终从固定状态槽重新读取。
112pub struct Observation {
113    core: Weak<Core>,
114    connection: Arc<Connection>,
115    slot: Arc<Slot>,
116    changes: watch::Receiver<u64>,
117}
118
119impl Clone for Observation {
120    /// 业务作用:增加当前目标的观察需求,共享已有后端查询。
121    ///
122    /// 参数说明:无。
123    ///
124    /// 返回:独立通知游标,不启动第二个轮询。
125    fn clone(&self) -> Self {
126        Self {
127            core: self.core.clone(),
128            connection: self.connection.clone(),
129            slot: self.slot.clone(),
130            changes: self.changes.clone(),
131        }
132    }
133}
134
135impl DiscoveryHandle {
136    /// 业务作用:取得连接内服务的共享观察,首个等待者取消不影响其他需求。
137    ///
138    /// 参数说明:
139    /// - `service`:是后端名或明确绑定别名
140    /// - `deadline`:是调用方总期限。
141    ///
142    /// 返回:总期限内取得共享证据后返回凭据;过期调用不取得缓存或创建需求,失败、容量不足和关闭返回错误。
143    pub async fn observe(&self, service: &str, deadline: Instant) -> Result<Observation> {
144        // 调用方已经失去等待预算时不能触碰缓存或占用观察额度,也不能派生无需求的查询。
145        if Instant::now() >= deadline {
146            return Err(timeout());
147        }
148        let core = self.core.upgrade().ok_or_else(closed)?;
149        if service.is_empty() || service.len() > 512 || service.trim() != service {
150            return Err(crate::config::invalid("invalid discovery service"));
151        }
152        let mut observation = {
153            let gate = lock(&core.gate);
154            if gate.failed
155                || matches!(gate.phase, OwnerPhase::Prepared | OwnerPhase::Closed)
156                || gate.close_started
157            {
158                return Err(closed());
159            }
160            let mut slots = lock(&self.connection.slots);
161            // 门禁与观察槽的锁等待也消耗总期限,必须在复用或分配需求之前再次核验。
162            if Instant::now() >= deadline {
163                return Err(timeout());
164            }
165            let slot = if let Some(slot) = slots.get(service) {
166                let mut state = lock(&slot.state);
167                if Instant::now() >= deadline {
168                    return Err(timeout());
169                }
170                state.touched = Instant::now();
171                slot.clone()
172            } else {
173                if slots.len() >= core.limits.max_service_watches_per_connection {
174                    return Err(DiscoveryError::new(
175                        ErrorCode::CapacityExceeded,
176                        "service observation capacity reached",
177                    ));
178                }
179                let slot = Arc::new(Slot {
180                    service: service.into(),
181                    state: Mutex::new(State {
182                        data: None,
183                        valid_until: None,
184                        observed_at: None,
185                        error: None,
186                        sequence: 0,
187                        epoch: 0,
188                        revocation: 0,
189                        touched: Instant::now(),
190                    }),
191                    changes: watch::channel(0).0,
192                });
193                slots.insert(service.into(), slot.clone());
194                let (shared, connection, target) =
195                    (core.clone(), self.connection.clone(), slot.clone());
196                spawn_owned(&core, async move {
197                    observe_loop(shared, connection, target).await
198                });
199                slot
200            };
201            Observation {
202                core: self.core.clone(),
203                connection: self.connection.clone(),
204                changes: slot.changes.subscribe(),
205                slot,
206            }
207        };
208        loop {
209            // 通知与期限可能同时就绪;单个等待者过期只释放自己的需求,不取消其他等待者的共享查询。
210            if Instant::now() >= deadline {
211                return Err(timeout());
212            }
213            if lock(&observation.slot.state).sequence != 0 {
214                let snapshot = observation.snapshot(false);
215                // 读取证据时的锁竞争不能使成功结果越过调用方总期限。
216                if Instant::now() >= deadline {
217                    return Err(timeout());
218                }
219                snapshot?;
220                return Ok(observation);
221            }
222            tokio::select! {
223                _ = core.stop.cancelled() => return Err(closed()),
224                result = tokio::time::timeout_at(deadline, observation.changes.changed()) => {
225                    result.map_err(|_| timeout())?.map_err(|_| closed())?;
226                }
227            }
228        }
229    }
230
231    /// 业务作用:显示连接的精确名称,避免跨环境猜测默认目标。
232    ///
233    /// 参数说明:无。
234    ///
235    /// 返回:构造时冻结的连接名。
236    pub fn name(&self) -> &str {
237        &self.connection.name
238    }
239}
240
241impl Observation {
242    /// 业务作用:按当前资格读取完整快照,严格调用可拒绝任何最近一次查询错误。
243    ///
244    /// 参数说明:
245    /// - `strict`:为 true 时不接受异常宽限。
246    ///
247    /// 返回:有效证据快照;关闭、超龄或撤权不返回旧路由。
248    pub fn snapshot(&self, strict: bool) -> Result<Snapshot> {
249        let core = self.core.upgrade().ok_or_else(closed)?;
250        let gate = lock(&core.gate);
251        if gate.failed || gate.close_started || gate.phase == OwnerPhase::Closed {
252            return Err(closed());
253        }
254        let state = lock(&self.slot.state);
255        check(&self.connection, &state, strict)?;
256        Ok(Snapshot {
257            data: state.data.as_ref().ok_or_else(closed)?.clone(),
258            owner_id: core.id.clone(),
259            connection: self.connection.name.clone(),
260            service: self.slot.service.clone(),
261            epoch: state.epoch,
262            sequence: state.sequence,
263            observed_at: state.observed_at.ok_or_else(closed)?,
264            valid_until: state.valid_until,
265        })
266    }
267
268    /// 业务作用:等待证据变化或有效期结束,避免无变化时永久保留旧值。
269    ///
270    /// 参数说明:
271    /// - `deadline`:是本次等待上限。
272    ///
273    /// 返回:收到通知或真实证据期限到达时成功,调用方须重新读取 snapshot;关闭或仅调用方等待超时返回错误,静态证据没有到期期限。
274    pub async fn changed(&mut self, deadline: Instant) -> Result<()> {
275        let core = self.core.upgrade().ok_or_else(closed)?;
276        let expiry = lock(&self.slot.state).valid_until;
277        let wake_at = expiry.map_or(deadline, |expiry| expiry.min(deadline));
278        tokio::select! {
279            _ = core.stop.cancelled() => Err(closed()),
280            result = self.changes.changed() => result.map_err(|_| closed()),
281            // 等待上限不是证据事件;只有存在且先到达的证据期限才能触发成功通知。
282            _ = tokio::time::sleep_until(wake_at) => if expiry.is_some_and(|expiry| expiry <= deadline) { Ok(()) } else { Err(timeout()) },
283        }
284    }
285
286    /// 业务作用:在撤权共用的同步保护范围内提交一次传输。
287    ///
288    /// 参数说明:
289    /// - `instance`:必须仍是当前健康实例
290    /// - `submit`:不得阻塞或重入发现接口。
291    ///
292    /// 返回:资格仍成立时执行回调;否则不提交。
293    pub fn submit_if_current<T>(
294        &self,
295        instance: &Instance,
296        submit: impl FnOnce() -> T,
297    ) -> Result<T> {
298        self.submit(instance, false, submit)
299    }
300
301    /// 业务作用:在强一致业务提交处同时拒绝最新查询失败和候选撤销,不使用异常宽限。
302    ///
303    /// 参数说明:
304    /// - `instance`:是此前选中的完整实例
305    /// - `submit`:是不阻塞且不重入的同步提交。
306    ///
307    /// 返回:当前完整证据允许该候选才执行动作。
308    pub fn submit_if_current_strict<T>(
309        &self,
310        instance: &Instance,
311        submit: impl FnOnce() -> T,
312    ) -> Result<T> {
313        self.submit(instance, true, submit)
314    }
315
316    /// 业务作用:在同一保护范围执行严格或普通提交裁决。
317    ///
318    /// 参数说明:
319    /// - `instance`:是完整候选
320    /// - `strict`:禁止异常宽限
321    /// - `submit`:只能同步提交。
322    ///
323    /// 返回:失权时不调用动作,成功不代表远端已执行。
324    fn submit<T>(
325        &self,
326        instance: &Instance,
327        strict: bool,
328        submit: impl FnOnce() -> T,
329    ) -> Result<T> {
330        let core = self.core.upgrade().ok_or_else(closed)?;
331        let gate = lock(&core.gate);
332        if gate.failed || gate.close_started || gate.phase == OwnerPhase::Closed {
333            return Err(closed());
334        }
335        let state = lock(&self.slot.state);
336        check(&self.connection, &state, strict)?;
337        if !is_traffic_instance(instance)
338            || !state
339                .data
340                .as_ref()
341                .is_some_and(|data| data.instances.contains(instance))
342        {
343            return Err(DiscoveryError::new(
344                ErrorCode::NoInstances,
345                "candidate no longer has routing eligibility",
346            ));
347        }
348        Ok(submit())
349    }
350}
351
352/// 业务作用:统一检查数据代次、错误宽限和固定截止点。
353///
354/// 参数说明:
355/// - `connection`:提供当前身份代次
356/// - `state`:是锁定的证据槽
357/// - `strict`:禁用宽限。
358///
359/// 返回:只有仍可用于调用的证据通过;读取不会刷新时间。
360pub(crate) fn check(connection: &Connection, state: &State, strict: bool) -> Result<()> {
361    if state.epoch != connection.epoch.load(Ordering::Acquire)
362        || state.revocation != connection.revocation.load(Ordering::Acquire)
363    {
364        return Err(closed());
365    }
366    if strict && state.error.is_some() {
367        return Err(state.error.clone().unwrap());
368    }
369    if state.data.is_none()
370        || state.observed_at.is_none()
371        || state
372            .valid_until
373            .is_some_and(|deadline| Instant::now() >= deadline)
374        || (state.valid_until.is_none()
375            && state
376                .data
377                .as_ref()
378                .is_none_or(|data| data.evidence != EvidenceKind::Static))
379    {
380        return Err(state.error.clone().unwrap_or_else(|| {
381            DiscoveryError::new(
382                ErrorCode::Unavailable,
383                "discovery evidence is unavailable or expired",
384            )
385        }));
386    }
387    Ok(())
388}
389
390/// 业务作用:让轮询和 watch 失败使用相同的证据裁决,重复失败只能缩短既有发送期限。
391///
392/// 参数说明:
393/// - `connection`:提供冻结的年龄与宽限
394/// - `state`:是当前证据槽
395/// - `error`:区分同步不完整和明确撤权事实。
396///
397/// 返回:保存错误供严格读取拒绝;普通读取最多沿用最后一次成功时刻起的较短宽限,不恢复已经失效的路由。
398fn record_failure(connection: &Connection, state: &mut State, error: &DiscoveryError) {
399    let grace = if error.revokes_evidence() {
400        0
401    } else {
402        connection
403            .policy
404            .stale_if_error_ms
405            .min(connection.policy.max_age_ms)
406    };
407    let failure_deadline = state
408        .observed_at
409        .map_or(Instant::now(), |at| at + millis(grace));
410    // 错误不会产生新证据;保留已撤权的更早截止点,避免后续普通网络失败重新开放旧路由。
411    state.valid_until = Some(
412        state
413            .valid_until
414            .map_or(failure_deadline, |current| current.min(failure_deadline)),
415    );
416    state.error = Some(error.clone());
417}
418
419/// 业务作用:执行一个服务的唯一完整查询序列并在无人使用后回收。
420///
421/// 参数说明:
422/// - `core`:接管任务
423/// - `connection`:提供作用域
424/// - `slot`:保存固定有效性槽。
425///
426/// 返回:关闭或空闲回收后终止,不在内部启动其他轮询。
427async fn observe_loop(core: Arc<Core>, connection: Arc<Connection>, slot: Arc<Slot>) {
428    let mut backoff = connection.policy.initial_backoff_ms;
429    loop {
430        let epoch = connection.epoch.load(Ordering::Acquire);
431        let revocation = connection.revocation.load(Ordering::Acquire);
432        let deadline = Instant::now() + millis(connection.policy.request_timeout_ms);
433        let started = Instant::now();
434        let result = operation(&core, deadline, connection.provider.observe(&slot.service)).await;
435        let failed = result.is_err();
436        connection.queries.fetch_add(1, Ordering::Relaxed);
437        connection.query_micros.fetch_add(
438            started.elapsed().as_micros().min(u64::MAX as u128) as u64,
439            Ordering::Relaxed,
440        );
441        if result.is_err() {
442            connection.query_errors.fetch_add(1, Ordering::Relaxed);
443        }
444        {
445            let gate = lock(&core.gate);
446            if gate.failed || gate.close_started || core.stop.is_cancelled() {
447                break;
448            }
449            if epoch == connection.epoch.load(Ordering::Acquire)
450                && revocation == connection.revocation.load(Ordering::Acquire)
451            {
452                let mut state = lock(&slot.state);
453                let result = result.and_then(|snapshot| {
454                    if let Some(data) = state.data.as_ref().filter(|data| {
455                        data.evidence == snapshot.evidence && data.instances == snapshot.instances
456                    }) {
457                        Ok(data.clone())
458                    } else {
459                        // 完整结果已明确撤下旧候选时,即使新代因容量拒绝,也不能借同步失败宽限继续发送旧候选。
460                        if state.data.as_ref().is_some_and(|data| {
461                            data.instances
462                                .iter()
463                                .filter(|instance| is_traffic_instance(instance))
464                                .any(|instance| !snapshot.instances.contains(instance))
465                        }) {
466                            state.valid_until = Some(Instant::now());
467                        }
468                        allocate(
469                            &core,
470                            &connection,
471                            snapshot.instances,
472                            snapshot.evidence,
473                            &mut state.data,
474                        )
475                    }
476                });
477                state.sequence = state.sequence.wrapping_add(1);
478                state.epoch = epoch;
479                state.revocation = revocation;
480                match result {
481                    Ok(data) => {
482                        let now = Instant::now();
483                        state.valid_until = (data.evidence != EvidenceKind::Static)
484                            .then(|| now + millis(connection.policy.max_age_ms));
485                        state.data = Some(data);
486                        state.observed_at = Some(now);
487                        state.error = None;
488                    }
489                    Err(error) => {
490                        record_failure(&connection, &mut state, &error);
491                        if matches!(
492                            error.code,
493                            ErrorCode::Unauthenticated
494                                | ErrorCode::PermissionDenied
495                                | ErrorCode::PermissionFiltered
496                        ) {
497                            drop(state);
498                            crate::revoke_connection(&core, &gate, &connection);
499                        }
500                    }
501                }
502                slot.changes
503                    .send_modify(|value| *value = value.wrapping_add(1));
504            }
505        }
506        let delay = if failed {
507            let delay = crate::retry_delay(
508                &core,
509                &connection,
510                backoff,
511                connection.queries.load(Ordering::Relaxed),
512            );
513            backoff = backoff
514                .saturating_mul(2)
515                .min(connection.policy.max_backoff_ms);
516            delay
517        } else {
518            backoff = connection.policy.initial_backoff_ms;
519            millis(connection.policy.poll_interval_ms)
520        };
521        let wait_for_change = async {
522            if failed {
523                return std::future::pending().await;
524            }
525            if let Some(timeout) = connection.provider.change_wait_timeout() {
526                // 长连接使用独立有限额度;排队期间周期性全量查询仍可推进,不占用短请求和注销额度。
527                let streams = connection.provider.change_wait_streams().max(1);
528                let _permit = core
529                    .watches
530                    .acquire_many(streams)
531                    .await
532                    .map_err(|_| closed())?;
533                tokio::time::timeout(timeout, connection.provider.wait_for_change(&slot.service))
534                    .await
535                    .unwrap_or_else(|_| Err(crate::timeout()))
536            } else {
537                std::future::pending().await
538            }
539        };
540        tokio::select! {
541            _ = core.stop.cancelled() => break,
542            _ = tokio::time::sleep(delay) => {},
543            result = wait_for_change => {
544                if let Err(error) = result {
545                    {
546                        let gate = lock(&core.gate);
547                        let mut state = lock(&slot.state);
548                        record_failure(&connection, &mut state, &error);
549                        drop(state);
550                        if matches!(error.code, ErrorCode::Unauthenticated | ErrorCode::PermissionDenied | ErrorCode::PermissionFiltered) {
551                            crate::revoke_connection(&core, &gate, &connection);
552                        }
553                        slot.changes.send_modify(|value| *value = value.wrapping_add(1));
554                    }
555                    // 立即断开的 watch 必须退避,防止权限拒绝或持续 EOF 形成热循环。
556                    tokio::select! {
557                        _ = core.stop.cancelled() => break,
558                        _ = tokio::time::sleep(millis(connection.policy.initial_backoff_ms)) => {},
559                    }
560                } else {
561                    // watch 持续立即返回仍须限制同步频率,避免空事件或索引回退占满控制队列。
562                    tokio::select! {
563                        _ = core.stop.cancelled() => break,
564                        _ = tokio::time::sleep(millis(connection.policy.poll_interval_ms.min(100))) => {},
565                    }
566                }
567            }
568        }
569        let _gate = lock(&core.gate);
570        let mut slots = lock(&connection.slots);
571        if slot.changes.receiver_count() == 0
572            && lock(&slot.state).touched.elapsed() >= millis(connection.policy.idle_timeout_ms)
573        {
574            slots.remove(&slot.service);
575            break;
576        }
577    }
578}
579
580/// 业务作用:在发布前核对实例内容并计入包括旧代在内的全局配额。
581///
582/// 参数说明:
583/// - `core`:提供限额
584/// - `connection`:定义后端实例身份
585/// - `instances`:为完整候选
586/// - `evidence`:是证据等级
587/// - `previous`:是当前完整旧代。
588///
589/// 返回:验证和配额均通过才替换旧代;失败保留其数据与计费,不发布部分集合。无消费者持有的旧代可原位替换以复用额度。
590fn allocate(
591    core: &Core,
592    connection: &Connection,
593    instances: Vec<Instance>,
594    evidence: EvidenceKind,
595    previous: &mut Option<Arc<Data>>,
596) -> Result<Arc<Data>> {
597    let mut keys = BTreeSet::new();
598    let mut identities = Vec::with_capacity(instances.len());
599    let mut bytes = instances
600        .capacity()
601        .saturating_mul(std::mem::size_of::<Instance>());
602    for instance in &instances {
603        let identity = connection.provider.instance_key(instance);
604        if instance.ip.is_empty()
605            || instance.port == 0
606            || !instance.weight.is_finite()
607            || identity.is_empty()
608            || identity.len() > 2048
609            || !keys.insert(identity.clone())
610        {
611            return Err(DiscoveryError::new(
612                ErrorCode::InvalidSnapshot,
613                "invalid or duplicate instance in complete observation",
614            ));
615        }
616        bytes = bytes.saturating_add(identity.capacity());
617        identities.push(identity);
618        bytes = bytes
619            .saturating_add(instance.ip.capacity())
620            .saturating_add(instance.cluster_name.as_ref().map_or(0, String::capacity))
621            .saturating_add(
622                instance
623                    .metadata
624                    .capacity()
625                    .saturating_mul(std::mem::size_of::<(String, String)>() + 16),
626            );
627        for (key, value) in &instance.metadata {
628            bytes = bytes
629                .saturating_add(key.capacity())
630                .saturating_add(value.capacity());
631        }
632    }
633    bytes = bytes.saturating_add(
634        identities
635            .capacity()
636            .saturating_mul(std::mem::size_of::<String>()),
637    );
638    let mut quota = lock(&core.quota);
639    // 只有独占旧代才可复用额度;外部 Snapshot 仍持有的旧代必须与新代同时计费。
640    let reusable = previous.as_mut().and_then(Arc::get_mut);
641    let old_instances = reusable.as_ref().map_or(0, |data| data.instances.len());
642    let old_bytes = reusable.as_ref().map_or(0, |data| data.bytes);
643    let next_instances = (quota.instances - old_instances).saturating_add(instances.len());
644    let next_bytes = (quota.bytes - old_bytes).saturating_add(bytes);
645    if instances.len() > core.limits.max_instances_per_service
646        || next_instances > core.limits.max_total_instances
647        || next_bytes > core.limits.max_snapshot_bytes
648    {
649        return Err(DiscoveryError::new(
650            ErrorCode::CapacityExceeded,
651            "snapshot capacity reached",
652        ));
653    }
654    // 容量裁决成功后才一起更新计费与数据,失败不能提前丢弃尚在宽限内的完整证据。
655    quota.instances = next_instances;
656    quota.bytes = next_bytes;
657    if let Some(data) = reusable {
658        data.instances = instances;
659        data.identities = identities;
660        data.evidence = evidence;
661        data.bytes = bytes;
662        return Ok(previous.as_ref().unwrap().clone());
663    }
664    Ok(Arc::new(Data {
665        instances,
666        identities,
667        evidence,
668        bytes,
669        quota: core.quota.clone(),
670    }))
671}
672
673#[async_trait::async_trait]
674impl DiscoveryClient for DiscoveryHandle {
675    /// 业务作用:要求兼容调用方逐次检查发送许可。
676    ///
677    /// 参数说明:无。
678    ///
679    /// 返回:受管连接始终需要提交门禁。
680    fn requires_admission(&self) -> bool {
681        true
682    }
683
684    /// 业务作用:使旧发现接口的发送路径服从统一观察门禁。
685    ///
686    /// 参数说明:
687    /// - `service`:需要在当前连接已登记 slot 中查找的服务名。
688    /// - `instance`:本次发送绑定的完整候选实例。
689    /// - `submit`:只有候选仍有效时才同步调用的提交动作。
690    ///
691    /// 返回:服务 slot 存在且观察门禁允许候选时同步执行 submit,门禁或 slot 缺失返回错误;不把取得观察句柄视为持续有效的发送权。
692    fn submit_if_current(
693        &self,
694        service: &str,
695        instance: &Instance,
696        submit: &mut dyn FnMut(),
697    ) -> anyhow::Result<()> {
698        let slot = lock(&self.connection.slots)
699            .get(service)
700            .cloned()
701            .ok_or_else(closed)?;
702        let observation = Observation {
703            core: self.core.clone(),
704            connection: self.connection.clone(),
705            changes: slot.changes.subscribe(),
706            slot,
707        };
708        Ok(observation.submit_if_current(instance, submit)?)
709    }
710
711    /// 业务作用:列出当前连接明确允许枚举的服务。
712    ///
713    /// 参数说明:无。
714    ///
715    /// 返回:不支持枚举时失败,不扩大后端权限范围。
716    async fn list_services(&self) -> anyhow::Result<Vec<String>> {
717        let core = self.core.upgrade().ok_or_else(closed)?;
718        Ok(operation(
719            &core,
720            Instant::now() + millis(self.connection.policy.request_timeout_ms),
721            self.connection.provider.services(),
722        )
723        .await?)
724    }
725
726    /// 业务作用:为兼容调用方返回仍有效的可接流实例。
727    ///
728    /// 参数说明:
729    /// - `service`:是明确服务目标。
730    ///
731    /// 返回:过滤后的业务自有数组;后续提交仍须复验。
732    async fn discover(&self, service: &str) -> anyhow::Result<Vec<Instance>> {
733        Ok(self
734            .discover_all(service)
735            .await?
736            .into_iter()
737            .filter(is_traffic_instance)
738            .collect())
739    }
740
741    /// 业务作用:通过共享观察提供完整实例,不另起每请求轮询。
742    ///
743    /// 参数说明:
744    /// - `service`:是明确服务目标。
745    ///
746    /// 返回:有效完整快照的副本,副本不计入受管快照配额。
747    async fn discover_all(&self, service: &str) -> anyhow::Result<Vec<Instance>> {
748        let observation = self
749            .observe(
750                service,
751                Instant::now() + millis(self.connection.policy.request_timeout_ms),
752            )
753            .await?;
754        Ok(observation.snapshot(false)?.instances().to_vec())
755    }
756
757    /// 业务作用:桥接旧 Vec watch,在失效时关闭 channel 而非伪造成功空集合。
758    ///
759    /// 参数说明:
760    /// - `service`:是目标
761    /// - `options`:不得要求快于冻结观察周期的校正。
762    ///
763    /// 返回:共享观察的兼容订阅,guard 释放即停止桥接任务。
764    async fn watch_with_options(
765        &self,
766        service: &str,
767        options: WatchOptions,
768    ) -> anyhow::Result<ServiceWatch> {
769        options.validate()?;
770        if options.poll_interval < millis(self.connection.policy.poll_interval_ms) {
771            return Err(crate::config::invalid(
772                "watch interval is faster than frozen observation policy",
773            )
774            .into());
775        }
776        let core = self.core.upgrade().ok_or_else(closed)?;
777        let bridge = core.bridges.clone().try_acquire_owned().map_err(|_| {
778            DiscoveryError::new(
779                ErrorCode::CapacityExceeded,
780                "compatibility watch capacity reached",
781            )
782        })?;
783        let mut observation = self
784            .observe(
785                service,
786                Instant::now() + millis(self.connection.policy.request_timeout_ms),
787            )
788            .await?;
789        let initial = observation
790            .snapshot(false)?
791            .instances()
792            .iter()
793            .filter(|i| is_traffic_instance(i))
794            .cloned()
795            .collect();
796        let (sender, receiver) = watch::channel(initial);
797        let cancel = CancellationToken::new();
798        let stopped = cancel.clone();
799        spawn_owned(&core, async move {
800            let _bridge = bridge;
801            loop {
802                tokio::select! {
803                    _ = stopped.cancelled() => break,
804                    result = observation.changed(Instant::now() + millis(86_400_000)) => if result.is_err() { break; },
805                }
806                let Ok(snapshot) = observation.snapshot(false) else {
807                    break;
808                };
809                if sender
810                    .send(
811                        snapshot
812                            .instances()
813                            .iter()
814                            .filter(|i| is_traffic_instance(i))
815                            .cloned()
816                            .collect(),
817                    )
818                    .is_err()
819                {
820                    break;
821                }
822            }
823        });
824        Ok(ServiceWatch {
825            guard: Box::new(BridgeGuard(cancel)),
826            receiver,
827        })
828    }
829}
830
831struct BridgeGuard(CancellationToken);
832impl Drop for BridgeGuard {
833    /// 业务作用:释放兼容订阅时停止本消费者桥接。
834    ///
835    /// 参数说明:无。
836    ///
837    /// 返回:共享后端观察仍由其他需求维持。
838    fn drop(&mut self) {
839        self.0.cancel();
840    }
841}
842#[async_trait::async_trait]
843impl ServiceWatchGuard for BridgeGuard {
844    /// 业务作用:显式解除本消费者订阅。
845    ///
846    /// 参数说明:无。
847    ///
848    /// 返回:桥接任务收到取消,不关闭共享连接。
849    async fn unsubscribe(self: Box<Self>) -> anyhow::Result<()> {
850        self.0.cancel();
851        Ok(())
852    }
853}