Skip to main content

query/dist_plan/
remote_dyn_filter_registry.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::{HashMap, HashSet};
16use std::future::Future;
17use std::sync::{Arc, Mutex, RwLock, Weak};
18use std::time::Duration;
19
20use api::v1::region::{RemoteDynFilterUnregister, RemoteDynFilterUpdate};
21use common_query::request::{DynFilterPayload, REMOTE_DYN_FILTER_PAYLOAD_MAX_BYTES};
22use common_runtime::spawn_global;
23use common_telemetry::{debug, warn};
24use datafusion_physical_expr::PhysicalExpr;
25use datafusion_physical_expr::expressions::DynamicFilterPhysicalExpr;
26use session::query_id::QueryId;
27use store_api::storage::RegionId;
28use tokio::sync::{Notify, watch};
29
30use crate::dist_plan::FilterId;
31use crate::metrics::{
32    REMOTE_DYN_FILTER_ENCODE_TOTAL, REMOTE_DYN_FILTER_PAYLOAD_BYTES,
33    REMOTE_DYN_FILTER_UPDATE_RPC_TOTAL,
34};
35use crate::region_query::{RegionQueryHandlerRef, RegionQueryTarget};
36
37const REMOTE_DYN_FILTER_RECONCILE_INTERVAL: Duration = Duration::from_secs(1);
38/// Bound best-effort RDF control RPCs so one bad subscriber cannot stall fanout.
39const REMOTE_DYN_FILTER_CONTROL_RPC_TIMEOUT: Duration = Duration::from_secs(10);
40
41/// Region and target subscribed to a remote dynamic filter.
42#[derive(Debug, Clone, PartialEq, Eq, Hash)]
43pub struct Subscriber {
44    region_id: RegionId,
45    target: RegionQueryTarget,
46}
47
48impl Subscriber {
49    pub fn new(region_id: RegionId, target: RegionQueryTarget) -> Self {
50        Self { region_id, target }
51    }
52
53    pub fn region_id(&self) -> RegionId {
54        self.region_id
55    }
56
57    pub fn target(&self) -> &RegionQueryTarget {
58        &self.target
59    }
60}
61
62/// Result of registering a remote dynamic filter entry.
63#[derive(Debug, Clone)]
64pub enum EntryRegistration {
65    Inserted(Arc<DynFilterEntry>),
66    /// The filter already existed; this contains the previously registered entry.
67    Existing(Arc<DynFilterEntry>),
68}
69
70/// Result of registering a subscriber under an existing filter entry.
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum SubscriberRegistration {
73    Added,
74    Duplicate,
75    MissingFilter,
76}
77
78/// A registered query-local producer filter and its region subscribers.
79#[derive(Debug)]
80pub struct DynFilterEntry {
81    filter_id: FilterId,
82    producer_filter: Weak<DynamicFilterPhysicalExpr>,
83    subscribers: RwLock<HashSet<Subscriber>>,
84    state: Mutex<DynFilterEntryState>,
85    subscriber_changed: Notify,
86}
87
88#[derive(Debug, Default)]
89struct DynFilterEntryState {
90    last_sent_generation: u64,
91    unregistered: bool,
92    fanout_started: bool,
93}
94
95#[derive(Debug)]
96struct QueryDynFilterRegistryInner {
97    entries: HashMap<FilterId, Arc<DynFilterEntry>>,
98}
99
100impl DynFilterEntry {
101    pub fn new(filter_id: FilterId, producer_filter: Arc<DynamicFilterPhysicalExpr>) -> Self {
102        Self {
103            filter_id,
104            producer_filter: Arc::downgrade(&producer_filter),
105            subscribers: RwLock::new(HashSet::new()),
106            state: Mutex::new(DynFilterEntryState::default()),
107            subscriber_changed: Notify::new(),
108        }
109    }
110
111    pub fn filter_id(&self) -> &FilterId {
112        &self.filter_id
113    }
114
115    pub fn upgrade_producer_filter(&self) -> Option<Arc<DynamicFilterPhysicalExpr>> {
116        self.producer_filter.upgrade()
117    }
118
119    pub fn subscribers(&self) -> Vec<Subscriber> {
120        self.subscribers.read().unwrap().iter().cloned().collect()
121    }
122
123    pub fn register_subscriber(&self, subscriber: Subscriber) -> bool {
124        let mut subscribers = self.subscribers.write().unwrap();
125        subscribers.insert(subscriber)
126    }
127
128    fn mark_generation_sent(&self, generation: u64) -> bool {
129        let mut state = self.state.lock().unwrap();
130        if generation <= state.last_sent_generation {
131            return false;
132        }
133
134        state.last_sent_generation = generation;
135        true
136    }
137
138    fn try_mark_unregistered(&self) -> bool {
139        let mut state = self.state.lock().unwrap();
140        if state.unregistered {
141            return false;
142        }
143
144        state.unregistered = true;
145        true
146    }
147
148    fn reactivate_for_new_subscriber(&self) {
149        {
150            let mut state = self.state.lock().unwrap();
151            // Reset generation/unregister state so late subscribers get the current snapshot.
152            state.last_sent_generation = 0;
153            state.unregistered = false;
154        }
155        self.subscriber_changed.notify_one();
156    }
157
158    fn mark_fanout_started(&self) -> bool {
159        let mut state = self.state.lock().unwrap();
160        if state.fanout_started {
161            return false;
162        }
163
164        state.fanout_started = true;
165        true
166    }
167
168    #[cfg(test)]
169    pub(crate) fn fanout_started_for_test(&self) -> bool {
170        self.state.lock().unwrap().fanout_started
171    }
172}
173
174/// Query-scoped registry that owns all remote dynamic filters for one query.
175#[derive(Debug)]
176pub struct QueryDynFilterRegistry {
177    query_id: QueryId,
178    lifecycle_tx: watch::Sender<()>,
179    inner: RwLock<QueryDynFilterRegistryInner>,
180}
181
182impl QueryDynFilterRegistry {
183    pub fn new(query_id: QueryId) -> Self {
184        // Close-only lifecycle signal; dropping the registry closes it for watchers.
185        let (lifecycle_tx, _) = watch::channel(());
186        Self {
187            query_id,
188            lifecycle_tx,
189            inner: RwLock::new(QueryDynFilterRegistryInner {
190                entries: HashMap::new(),
191            }),
192        }
193    }
194
195    pub fn query_id(&self) -> QueryId {
196        self.query_id
197    }
198
199    pub fn entry_count(&self) -> usize {
200        self.inner.read().unwrap().entries.len()
201    }
202
203    pub fn entries(&self) -> Vec<Arc<DynFilterEntry>> {
204        self.inner
205            .read()
206            .unwrap()
207            .entries
208            .values()
209            .cloned()
210            .collect()
211    }
212
213    pub fn remote_dyn_filter(&self, filter_id: &FilterId) -> Option<Arc<DynFilterEntry>> {
214        self.inner.read().unwrap().entries.get(filter_id).cloned()
215    }
216
217    pub fn register_remote_dyn_filter(
218        &self,
219        filter_id: FilterId,
220        producer_filter: Arc<DynamicFilterPhysicalExpr>,
221    ) -> EntryRegistration {
222        let mut inner = self.inner.write().unwrap();
223        if let Some(existing) = inner.entries.get(&filter_id) {
224            return EntryRegistration::Existing(existing.clone());
225        }
226
227        let entry = Arc::new(DynFilterEntry::new(filter_id.clone(), producer_filter));
228        inner.entries.insert(filter_id, entry.clone());
229        EntryRegistration::Inserted(entry)
230    }
231
232    pub fn register_subscriber(
233        &self,
234        filter_id: &FilterId,
235        subscriber: Subscriber,
236    ) -> SubscriberRegistration {
237        let Some(entry) = self.inner.read().unwrap().entries.get(filter_id).cloned() else {
238            return SubscriberRegistration::MissingFilter;
239        };
240
241        if entry.register_subscriber(subscriber) {
242            // New subscribers need the current snapshot; existing subscribers may see a duplicate.
243            entry.reactivate_for_new_subscriber();
244            SubscriberRegistration::Added
245        } else {
246            SubscriberRegistration::Duplicate
247        }
248    }
249
250    /// Removes one exact subscriber without changing producer or fanout state.
251    pub(crate) fn remove_subscriber(&self, filter_id: &FilterId, subscriber: &Subscriber) -> bool {
252        self.inner
253            .read()
254            .unwrap()
255            .entries
256            .get(filter_id)
257            .is_some_and(|entry| entry.subscribers.write().unwrap().remove(subscriber))
258    }
259
260    /// Starts missing producer fanout watchers for the registry's entries.
261    ///
262    /// Watchers do not hold the registry alive; dropping the registry closes their lifecycle channel.
263    pub fn ensure_fanout_task(self: &Arc<Self>, region_query_handler: RegionQueryHandlerRef) {
264        for entry in self.entries() {
265            ensure_entry_fanout_task(
266                self.query_id,
267                entry,
268                region_query_handler.clone(),
269                self.lifecycle_tx.subscribe(),
270            );
271        }
272    }
273
274    #[cfg(test)]
275    async fn fanout_snapshot(
276        &self,
277        region_query_handler: &RegionQueryHandlerRef,
278        entry: &DynFilterEntry,
279        filter: &DynamicFilterPhysicalExpr,
280        is_complete: bool,
281    ) {
282        let mut lifecycle_rx = self.lifecycle_tx.subscribe();
283        fanout_snapshot_for_query(
284            self.query_id,
285            region_query_handler,
286            entry,
287            filter,
288            is_complete,
289            &mut lifecycle_rx,
290            REMOTE_DYN_FILTER_CONTROL_RPC_TIMEOUT,
291        )
292        .await;
293    }
294
295    #[cfg(test)]
296    async fn unregister_all_once(&self, region_query_handler: &RegionQueryHandlerRef) {
297        for entry in self.entries() {
298            unregister_entry_once_for_query(region_query_handler, self.query_id, &entry).await;
299        }
300    }
301}
302
303fn ensure_entry_fanout_task(
304    query_id: QueryId,
305    entry: Arc<DynFilterEntry>,
306    region_query_handler: RegionQueryHandlerRef,
307    lifecycle_rx: watch::Receiver<()>,
308) {
309    if !entry.mark_fanout_started() {
310        return;
311    }
312
313    let _handle = spawn_global(async move {
314        run_entry_fanout(query_id, entry, region_query_handler, lifecycle_rx).await;
315    });
316}
317
318async fn run_entry_fanout(
319    query_id: QueryId,
320    entry: Arc<DynFilterEntry>,
321    region_query_handler: RegionQueryHandlerRef,
322    mut lifecycle_rx: watch::Receiver<()>,
323) {
324    let mut is_complete = false;
325    // Start reconcile after one interval and skip missed ticks; it is only a coalescing fallback.
326    let mut reconcile_interval = tokio::time::interval_at(
327        tokio::time::Instant::now() + REMOTE_DYN_FILTER_RECONCILE_INTERVAL,
328        REMOTE_DYN_FILTER_RECONCILE_INTERVAL,
329    );
330    reconcile_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
331
332    loop {
333        let Some(filter) = entry.upgrade_producer_filter() else {
334            unregister_entry_once_for_query(&region_query_handler, query_id, &entry).await;
335            return;
336        };
337
338        if !fanout_snapshot_for_query(
339            query_id,
340            &region_query_handler,
341            &entry,
342            &filter,
343            is_complete,
344            &mut lifecycle_rx,
345            REMOTE_DYN_FILTER_CONTROL_RPC_TIMEOUT,
346        )
347        .await
348        {
349            break;
350        }
351
352        if is_complete {
353            tokio::select! {
354                _ = entry.subscriber_changed.notified() => {}
355                result = lifecycle_rx.changed() => {
356                    if result.is_err() {
357                        break;
358                    }
359                }
360            }
361            continue;
362        }
363
364        tokio::select! {
365            _ = filter.wait_update() => {}
366            _ = filter.wait_complete() => {
367                is_complete = true;
368            }
369            // `wait_update()` can miss an update sent while an RPC is in-flight.
370            // Re-read periodically to coalesce to the latest generation.
371            _ = reconcile_interval.tick() => {}
372            _ = entry.subscriber_changed.notified() => {}
373            result = lifecycle_rx.changed() => {
374                if result.is_err() {
375                    break;
376                }
377            }
378        }
379    }
380
381    unregister_entry_once_for_query(&region_query_handler, query_id, &entry).await;
382}
383
384async fn fanout_snapshot_for_query(
385    query_id: QueryId,
386    region_query_handler: &RegionQueryHandlerRef,
387    entry: &DynFilterEntry,
388    filter: &DynamicFilterPhysicalExpr,
389    is_complete: bool,
390    lifecycle_rx: &mut watch::Receiver<()>,
391    control_rpc_timeout: Duration,
392) -> bool {
393    let Some((generation, current)) = current_stable_snapshot(filter, lifecycle_rx).await else {
394        return true;
395    };
396
397    // The entry-global watermark advances before best-effort fanout. A timed-out
398    // subscriber may miss this generation; later/complete snapshots supersede it,
399    // and RDF only prunes.
400    if !is_complete && !entry.mark_generation_sent(generation) {
401        return true;
402    }
403
404    if is_complete {
405        let _ = entry.mark_generation_sent(generation);
406    }
407
408    let payload =
409        match DynFilterPayload::from_datafusion_expr(&current, REMOTE_DYN_FILTER_PAYLOAD_MAX_BYTES)
410        {
411            Ok(DynFilterPayload::Datafusion(payload)) => {
412                REMOTE_DYN_FILTER_ENCODE_TOTAL
413                    .with_label_values(&["success"])
414                    .inc();
415                REMOTE_DYN_FILTER_PAYLOAD_BYTES.observe(payload.len() as f64);
416                payload
417            }
418            Ok(_) => {
419                REMOTE_DYN_FILTER_ENCODE_TOTAL
420                    .with_label_values(&["unsupported"])
421                    .inc();
422                warn!("Ignored unsupported remote dynamic filter producer payload");
423                return true;
424            }
425            Err(error) => {
426                REMOTE_DYN_FILTER_ENCODE_TOTAL
427                    .with_label_values(&["error"])
428                    .inc();
429                warn!(error; "Failed to encode remote dynamic filter producer snapshot");
430                return true;
431            }
432        };
433
434    fanout_update_for_query(
435        query_id,
436        region_query_handler,
437        entry,
438        generation,
439        is_complete,
440        payload,
441        lifecycle_rx,
442        control_rpc_timeout,
443    )
444    .await
445}
446
447#[allow(clippy::too_many_arguments)]
448async fn fanout_update_for_query(
449    query_id: QueryId,
450    region_query_handler: &RegionQueryHandlerRef,
451    entry: &DynFilterEntry,
452    generation: u64,
453    is_complete: bool,
454    payload: Vec<u8>,
455    lifecycle_rx: &mut watch::Receiver<()>,
456    control_rpc_timeout: Duration,
457) -> bool {
458    let query_id = query_id.to_string();
459    let filter_id = entry.filter_id().to_string();
460
461    for subscriber in entry.subscribers() {
462        let update = RemoteDynFilterUpdate {
463            filter_id: filter_id.clone(),
464            payload: payload.clone(),
465            generation,
466            is_complete,
467        };
468
469        match await_control_rpc_or_lifecycle_close(
470            lifecycle_rx,
471            format!(
472                "update query_id={} filter_id={} region_id={}",
473                query_id,
474                filter_id,
475                subscriber.region_id()
476            ),
477            region_query_handler.handle_remote_dyn_filter_update(
478                subscriber.target(),
479                query_id.clone(),
480                update,
481            ),
482            control_rpc_timeout,
483        )
484        .await
485        {
486            ControlRpcResult::Ok(result) => {
487                if let Err(error) = result {
488                    REMOTE_DYN_FILTER_UPDATE_RPC_TOTAL
489                        .with_label_values(&["error"])
490                        .inc();
491                    warn!(
492                        error;
493                        "Failed to fan out remote dynamic filter update, query_id={}, filter_id={}, region_id={}",
494                        query_id,
495                        filter_id,
496                        subscriber.region_id()
497                    );
498                } else {
499                    REMOTE_DYN_FILTER_UPDATE_RPC_TOTAL
500                        .with_label_values(&["success"])
501                        .inc();
502                }
503            }
504            ControlRpcResult::TimedOut => {
505                REMOTE_DYN_FILTER_UPDATE_RPC_TOTAL
506                    .with_label_values(&["timeout"])
507                    .inc();
508            }
509            ControlRpcResult::LifecycleClosed => {
510                REMOTE_DYN_FILTER_UPDATE_RPC_TOTAL
511                    .with_label_values(&["cancelled"])
512                    .inc();
513                return false;
514            }
515        }
516    }
517
518    true
519}
520
521async fn unregister_entry_once_for_query(
522    region_query_handler: &RegionQueryHandlerRef,
523    query_id: QueryId,
524    entry: &DynFilterEntry,
525) {
526    if !entry.try_mark_unregistered() {
527        return;
528    }
529
530    let query_id = query_id.to_string();
531    let filter_id = entry.filter_id().to_string();
532
533    for subscriber in entry.subscribers() {
534        let unregister = RemoteDynFilterUnregister {
535            filter_id: filter_id.clone(),
536        };
537
538        let Some(result) = await_control_rpc_timeout(
539            format!(
540                "unregister query_id={} filter_id={} region_id={}",
541                query_id,
542                filter_id,
543                subscriber.region_id()
544            ),
545            region_query_handler.handle_remote_dyn_filter_unregister(
546                subscriber.target(),
547                query_id.clone(),
548                unregister,
549            ),
550        )
551        .await
552        else {
553            continue;
554        };
555
556        if let Err(error) = result {
557            warn!(
558                error;
559                "Failed to fan out remote dynamic filter unregister, query_id={}, filter_id={}, region_id={}",
560                query_id,
561                filter_id,
562                subscriber.region_id()
563            );
564        }
565    }
566
567    debug!("Remote dynamic filter producer unregistered subscribers");
568}
569
570enum ControlRpcResult<T> {
571    Ok(T),
572    TimedOut,
573    LifecycleClosed,
574}
575
576async fn await_control_rpc_or_lifecycle_close<T>(
577    lifecycle_rx: &mut watch::Receiver<()>,
578    operation: String,
579    rpc: impl Future<Output = T>,
580    control_rpc_timeout: Duration,
581) -> ControlRpcResult<T> {
582    if lifecycle_rx.has_changed().is_err() {
583        return ControlRpcResult::LifecycleClosed;
584    }
585
586    tokio::select! {
587        biased;
588        result = lifecycle_rx.changed() => {
589            if result.is_err() {
590                debug!("Cancelled remote dynamic filter control RPC after lifecycle close");
591            }
592            ControlRpcResult::LifecycleClosed
593        }
594        result = rpc => ControlRpcResult::Ok(result),
595        _ = tokio::time::sleep(control_rpc_timeout) => {
596            warn!("Timed out remote dynamic filter control RPC: {}", operation);
597            ControlRpcResult::TimedOut
598        }
599    }
600}
601
602async fn await_control_rpc_timeout<T>(
603    operation: String,
604    rpc: impl Future<Output = T>,
605) -> Option<T> {
606    tokio::select! {
607        result = rpc => Some(result),
608        _ = tokio::time::sleep(REMOTE_DYN_FILTER_CONTROL_RPC_TIMEOUT) => {
609            warn!("Timed out remote dynamic filter control RPC: {}", operation);
610            None
611        }
612    }
613}
614
615async fn current_stable_snapshot(
616    filter: &DynamicFilterPhysicalExpr,
617    lifecycle_rx: &mut watch::Receiver<()>,
618) -> Option<(u64, Arc<dyn PhysicalExpr>)> {
619    loop {
620        if lifecycle_rx.has_changed().is_err() {
621            return None;
622        }
623
624        let before = filter.snapshot_generation();
625        let current = match filter.current() {
626            Ok(current) => current,
627            Err(error) => {
628                warn!(error; "Failed to read remote dynamic filter producer snapshot");
629                return None;
630            }
631        };
632        let after = filter.snapshot_generation();
633
634        if before == after {
635            return Some((after, current));
636        }
637
638        tokio::select! {
639            biased;
640            result = lifecycle_rx.changed() => {
641                if result.is_err() {
642                    return None;
643                }
644            }
645            _ = tokio::task::yield_now() => {}
646        }
647    }
648}
649
650/// Stream-scoped lease that keeps a query registry alive.
651///
652/// Stream leases own registry lifecycle; the manager only keeps a weak index.
653#[derive(Debug)]
654pub struct RemoteDynFilterRegistryLease {
655    registry_manager: Arc<DynFilterRegistryManager>,
656    /// Always `Some` while the lease is alive.
657    ///
658    /// `Option` lets `Drop` release the strong `Arc` before pruning the weak index.
659    registry: Option<Arc<QueryDynFilterRegistry>>,
660}
661
662impl RemoteDynFilterRegistryLease {
663    fn new(
664        registry_manager: Arc<DynFilterRegistryManager>,
665        registry: Arc<QueryDynFilterRegistry>,
666    ) -> Self {
667        Self {
668            registry_manager,
669            registry: Some(registry),
670        }
671    }
672
673    pub fn registry(&self) -> &QueryDynFilterRegistry {
674        self.registry
675            .as_deref()
676            .expect("remote dyn filter registry lease must hold a registry")
677    }
678
679    pub fn ensure_fanout_task(&self, region_query_handler: RegionQueryHandlerRef) {
680        self.registry
681            .as_ref()
682            .expect("remote dyn filter registry lease must hold a registry")
683            .ensure_fanout_task(region_query_handler);
684    }
685
686    #[cfg(test)]
687    pub(crate) fn ptr_eq(&self, other: &Self) -> bool {
688        Arc::ptr_eq(
689            self.registry.as_ref().unwrap(),
690            other.registry.as_ref().unwrap(),
691        )
692    }
693
694    #[cfg(test)]
695    pub(crate) fn registry_arc_for_test(&self) -> Arc<QueryDynFilterRegistry> {
696        self.registry.as_ref().unwrap().clone()
697    }
698}
699
700impl Drop for RemoteDynFilterRegistryLease {
701    fn drop(&mut self) {
702        let Some(registry) = self.registry.take() else {
703            return;
704        };
705        let query_id = registry.query_id();
706        let registry_weak = Arc::downgrade(&registry);
707
708        // Release this lease before pruning; concurrent drops must not observe each other's strong refs.
709        drop(registry);
710
711        let _ = self
712            .registry_manager
713            .remove_if_dropped_registry(&query_id, &registry_weak);
714    }
715}
716
717/// Query-engine manager for query-scoped remote dynamic filter registries.
718///
719/// Weak index only; stream leases own registries through [`RemoteDynFilterRegistryLease`].
720#[derive(Debug, Default)]
721pub struct DynFilterRegistryManager {
722    registries: RwLock<HashMap<QueryId, Weak<QueryDynFilterRegistry>>>,
723}
724
725impl DynFilterRegistryManager {
726    #[cfg(test)]
727    fn get(&self, query_id: &QueryId) -> Option<Arc<QueryDynFilterRegistry>> {
728        let (registry, stale_entry) = {
729            let registries = self.registries.read().unwrap();
730            let registry = registries.get(query_id)?;
731
732            (registry.upgrade(), registry.clone())
733        };
734
735        if registry.is_none() {
736            self.remove_stale_entry(query_id, &stale_entry);
737        }
738
739        registry
740    }
741
742    #[cfg(test)]
743    fn remove(&self, query_id: &QueryId) -> Option<Weak<QueryDynFilterRegistry>> {
744        self.registries.write().unwrap().remove(query_id)
745    }
746
747    fn remove_if_dropped_registry(
748        &self,
749        query_id: &QueryId,
750        dropped_registry: &Weak<QueryDynFilterRegistry>,
751    ) -> Option<Weak<QueryDynFilterRegistry>> {
752        let mut registries = self
753            .registries
754            .write()
755            .unwrap_or_else(|poisoned| poisoned.into_inner());
756        let current = registries.get(query_id)?;
757
758        // `ptr_eq` protects a newer registry for the same query id; `upgrade` ensures it is dead.
759        if current.ptr_eq(dropped_registry) && current.upgrade().is_none() {
760            registries.remove(query_id)
761        } else {
762            None
763        }
764    }
765
766    #[cfg(test)]
767    fn remove_stale_entry(
768        &self,
769        query_id: &QueryId,
770        stale_registry: &Weak<QueryDynFilterRegistry>,
771    ) {
772        let mut registries = self.registries.write().unwrap();
773        let Some(current) = registries.get(query_id) else {
774            return;
775        };
776
777        if current.ptr_eq(stale_registry) && current.upgrade().is_none() {
778            registries.remove(query_id);
779        }
780    }
781
782    /// Acquires the stream-owned registry lease for `query_id`.
783    ///
784    /// Returns a lease holding a strong registry reference.
785    pub fn acquire_lease(self: &Arc<Self>, query_id: QueryId) -> RemoteDynFilterRegistryLease {
786        let registry = self.get_or_init(query_id);
787        RemoteDynFilterRegistryLease::new(self.clone(), registry)
788    }
789
790    fn get_or_init(&self, query_id: QueryId) -> Arc<QueryDynFilterRegistry> {
791        let mut registries = self.registries.write().unwrap();
792
793        if let Some(registry) = registries.get(&query_id).and_then(Weak::upgrade) {
794            return registry;
795        }
796
797        let registry = Arc::new(QueryDynFilterRegistry::new(query_id));
798        registries.insert(query_id, Arc::downgrade(&registry));
799        registry
800    }
801
802    #[cfg(test)]
803    pub fn registry_count(&self) -> usize {
804        // Test snapshot helper; lifecycle decisions use lease-owned Arcs and weak pruning.
805        self.registries
806            .read()
807            .unwrap()
808            .values()
809            .filter(|registry| registry.strong_count() > 0)
810            .count()
811    }
812
813    #[cfg(test)]
814    fn weak_entry_count(&self) -> usize {
815        self.registries.read().unwrap().len()
816    }
817}
818
819#[cfg(test)]
820mod tests {
821    use std::collections::HashSet;
822    use std::sync::atomic::{AtomicBool, Ordering};
823    use std::sync::{Barrier, Mutex};
824    use std::thread;
825    use std::time::Duration;
826
827    use api::v1::region::{RemoteDynFilterUnregister, RemoteDynFilterUpdate};
828    use async_trait::async_trait;
829    use common_meta::peer::Peer;
830    use common_query::request::QueryRequest;
831    use datafusion_physical_expr::expressions::{Column, lit};
832    use session::ReadPreference;
833    use uuid::Uuid;
834
835    use super::*;
836    use crate::dist_plan::{FilterFingerprint, RemoteDynFilterProducerId};
837    use crate::error::Result as QueryResult;
838    use crate::region_query::{RegionQueryHandler, RegionQueryTarget};
839
840    #[derive(Debug, Clone, PartialEq, Eq)]
841    struct RecordedUpdate {
842        target: RegionQueryTarget,
843        query_id: String,
844        filter_id: String,
845        generation: u64,
846        is_complete: bool,
847        payload: Vec<u8>,
848    }
849
850    #[derive(Debug, Clone, PartialEq, Eq)]
851    struct RecordedUnregister {
852        target: RegionQueryTarget,
853        query_id: String,
854        filter_id: String,
855    }
856
857    #[derive(Default)]
858    struct RecordingRegionQueryHandler {
859        updates: Mutex<Vec<RecordedUpdate>>,
860        unregisters: Mutex<Vec<RecordedUnregister>>,
861        block_next_update: AtomicBool,
862        update_blocked: Notify,
863        release_update: Notify,
864    }
865
866    impl RecordingRegionQueryHandler {
867        fn updates(&self) -> Vec<RecordedUpdate> {
868            self.updates.lock().unwrap().clone()
869        }
870
871        fn unregisters(&self) -> Vec<RecordedUnregister> {
872            self.unregisters.lock().unwrap().clone()
873        }
874
875        fn block_next_update(&self) {
876            self.block_next_update.store(true, Ordering::SeqCst);
877        }
878
879        async fn wait_for_blocked_update(&self) {
880            self.update_blocked.notified().await;
881        }
882
883        fn release_blocked_update(&self) {
884            self.release_update.notify_one();
885        }
886
887        async fn wait_for_update_count(&self, expected: usize) {
888            for _ in 0..300 {
889                if self.updates().len() >= expected {
890                    return;
891                }
892                tokio::time::sleep(Duration::from_millis(10)).await;
893            }
894            panic!("timed out waiting for {expected} remote dyn filter updates");
895        }
896
897        async fn wait_for_complete_update(&self) {
898            for _ in 0..300 {
899                if self.updates().iter().any(|update| update.is_complete) {
900                    return;
901                }
902                tokio::time::sleep(Duration::from_millis(10)).await;
903            }
904            panic!("timed out waiting for completed remote dyn filter update");
905        }
906
907        async fn wait_for_unregister_count(&self, expected: usize) {
908            for _ in 0..300 {
909                if self.unregisters().len() >= expected {
910                    return;
911                }
912                tokio::time::sleep(Duration::from_millis(10)).await;
913            }
914            panic!("timed out waiting for {expected} remote dyn filter unregisters");
915        }
916    }
917
918    async fn wait_for_registry_drop(registry: Weak<QueryDynFilterRegistry>) {
919        for _ in 0..300 {
920            if registry.upgrade().is_none() {
921                return;
922            }
923            tokio::time::sleep(Duration::from_millis(10)).await;
924        }
925        panic!("timed out waiting for remote dyn filter registry drop");
926    }
927
928    #[async_trait]
929    impl RegionQueryHandler for RecordingRegionQueryHandler {
930        async fn select_target(
931            &self,
932            _read_preference: ReadPreference,
933            _region_id: RegionId,
934        ) -> QueryResult<RegionQueryTarget> {
935            unreachable!("remote dyn filter registry tests should not execute remote queries")
936        }
937
938        async fn do_get(
939            &self,
940            _target: &RegionQueryTarget,
941            _request: QueryRequest,
942        ) -> QueryResult<common_recordbatch::SendableRecordBatchStream> {
943            unreachable!("remote dyn filter registry tests should not execute remote queries")
944        }
945
946        async fn handle_remote_dyn_filter_update(
947            &self,
948            target: &RegionQueryTarget,
949            query_id: String,
950            update: RemoteDynFilterUpdate,
951        ) -> QueryResult<()> {
952            let should_block = self.block_next_update.swap(false, Ordering::SeqCst);
953            self.updates.lock().unwrap().push(RecordedUpdate {
954                target: target.clone(),
955                query_id,
956                filter_id: update.filter_id,
957                generation: update.generation,
958                is_complete: update.is_complete,
959                payload: update.payload,
960            });
961            if should_block {
962                self.update_blocked.notify_one();
963                self.release_update.notified().await;
964            }
965            Ok(())
966        }
967
968        async fn handle_remote_dyn_filter_unregister(
969            &self,
970            target: &RegionQueryTarget,
971            query_id: String,
972            unregister: RemoteDynFilterUnregister,
973        ) -> QueryResult<()> {
974            self.unregisters.lock().unwrap().push(RecordedUnregister {
975                target: target.clone(),
976                query_id,
977                filter_id: unregister.filter_id,
978            });
979            Ok(())
980        }
981    }
982
983    fn test_query_id(value: u128) -> QueryId {
984        QueryId::from(Uuid::from_u128(value))
985    }
986
987    fn test_target(id: u64) -> RegionQueryTarget {
988        RegionQueryTarget::new(Peer {
989            id,
990            addr: format!("127.0.0.1:{id}"),
991        })
992    }
993
994    fn test_filter_id(producer_ordinal: u32) -> FilterId {
995        FilterId::new(
996            RemoteDynFilterProducerId::new(42),
997            producer_ordinal,
998            FilterFingerprint::new(0xabc),
999        )
1000    }
1001
1002    fn test_dyn_filter(names: &[&str]) -> Arc<DynamicFilterPhysicalExpr> {
1003        let children = names
1004            .iter()
1005            .enumerate()
1006            .map(|(index, name)| Arc::new(Column::new(name, index)) as _)
1007            .collect();
1008
1009        Arc::new(DynamicFilterPhysicalExpr::new(children, lit(true) as _))
1010    }
1011
1012    #[test]
1013    fn registry_manager_returns_same_registry_for_same_query() {
1014        let manager = Arc::new(DynFilterRegistryManager::default());
1015        let query_id = test_query_id(1);
1016        let first = manager.acquire_lease(query_id);
1017        let second = manager.acquire_lease(query_id);
1018
1019        assert!(first.ptr_eq(&second));
1020        assert_eq!(manager.registry_count(), 1);
1021        assert_eq!(manager.weak_entry_count(), 1);
1022    }
1023
1024    #[test]
1025    fn registry_manager_removes_registry_for_query() {
1026        let manager = Arc::new(DynFilterRegistryManager::default());
1027        let query_id = test_query_id(1);
1028
1029        let lease = manager.acquire_lease(query_id);
1030
1031        assert!(
1032            manager
1033                .remove(&query_id)
1034                .unwrap()
1035                .ptr_eq(&Arc::downgrade(lease.registry.as_ref().unwrap()))
1036        );
1037        assert!(manager.get(&query_id).is_none());
1038        assert_eq!(manager.registry_count(), 0);
1039        assert_eq!(manager.weak_entry_count(), 0);
1040    }
1041
1042    #[test]
1043    fn registry_manager_lease_waits_for_last_query_scoped_stream() {
1044        let manager = Arc::new(DynFilterRegistryManager::default());
1045        let query_id = test_query_id(1);
1046
1047        let first = manager.acquire_lease(query_id);
1048        let second = manager.acquire_lease(query_id);
1049
1050        assert!(first.ptr_eq(&second));
1051        assert_eq!(manager.registry_count(), 1);
1052        assert_eq!(manager.weak_entry_count(), 1);
1053        drop(first);
1054        assert_eq!(manager.registry_count(), 1);
1055        assert_eq!(manager.weak_entry_count(), 1);
1056
1057        drop(second);
1058        assert_eq!(manager.registry_count(), 0);
1059        assert_eq!(manager.weak_entry_count(), 0);
1060    }
1061
1062    #[test]
1063    fn registry_manager_lease_does_not_remove_reacquired_registry() {
1064        let manager = Arc::new(DynFilterRegistryManager::default());
1065        let query_id = test_query_id(1);
1066
1067        let first = manager.acquire_lease(query_id);
1068        drop(first);
1069        assert_eq!(manager.registry_count(), 0);
1070        assert_eq!(manager.weak_entry_count(), 0);
1071
1072        let second = manager.acquire_lease(query_id);
1073
1074        assert_eq!(manager.registry_count(), 1);
1075        assert_eq!(manager.weak_entry_count(), 1);
1076        drop(second);
1077        assert_eq!(manager.registry_count(), 0);
1078        assert_eq!(manager.weak_entry_count(), 0);
1079    }
1080
1081    #[test]
1082    fn registry_manager_concurrent_final_lease_drop_cleans_weak_entry() {
1083        let manager = Arc::new(DynFilterRegistryManager::default());
1084        let query_id = test_query_id(1);
1085        let first = manager.acquire_lease(query_id);
1086        let second = manager.acquire_lease(query_id);
1087        let barrier = Arc::new(Barrier::new(3));
1088
1089        let first_barrier = barrier.clone();
1090        let first_drop = thread::spawn(move || {
1091            first_barrier.wait();
1092            drop(first);
1093        });
1094
1095        let second_barrier = barrier.clone();
1096        let second_drop = thread::spawn(move || {
1097            second_barrier.wait();
1098            drop(second);
1099        });
1100
1101        barrier.wait();
1102        first_drop.join().unwrap();
1103        second_drop.join().unwrap();
1104
1105        assert_eq!(manager.registry_count(), 0);
1106        assert_eq!(manager.weak_entry_count(), 0);
1107    }
1108
1109    #[test]
1110    fn registry_manager_concurrent_first_acquire_shares_registry() {
1111        let manager = Arc::new(DynFilterRegistryManager::default());
1112        let query_id = test_query_id(1);
1113        let worker_count = 8;
1114        let barrier = Arc::new(Barrier::new(worker_count + 1));
1115
1116        let handles = (0..worker_count)
1117            .map(|_| {
1118                let manager = manager.clone();
1119                let barrier = barrier.clone();
1120                thread::spawn(move || {
1121                    barrier.wait();
1122                    manager.acquire_lease(query_id)
1123                })
1124            })
1125            .collect::<Vec<_>>();
1126
1127        barrier.wait();
1128        let leases = handles
1129            .into_iter()
1130            .map(|handle| handle.join().unwrap())
1131            .collect::<Vec<_>>();
1132
1133        let first = leases.first().unwrap();
1134        assert!(leases.iter().all(|lease| first.ptr_eq(lease)));
1135        assert_eq!(manager.registry_count(), 1);
1136        assert_eq!(manager.weak_entry_count(), 1);
1137
1138        drop(leases);
1139        assert_eq!(manager.registry_count(), 0);
1140        assert_eq!(manager.weak_entry_count(), 0);
1141    }
1142
1143    #[test]
1144    fn registry_manager_drop_racing_acquire_does_not_leave_stale_entry() {
1145        let manager = Arc::new(DynFilterRegistryManager::default());
1146        let query_id = test_query_id(1);
1147
1148        for _ in 0..64 {
1149            let old_lease = manager.acquire_lease(query_id);
1150            let barrier = Arc::new(Barrier::new(3));
1151
1152            let drop_barrier = barrier.clone();
1153            let drop_thread = thread::spawn(move || {
1154                drop_barrier.wait();
1155                drop(old_lease);
1156            });
1157
1158            let acquire_manager = manager.clone();
1159            let acquire_barrier = barrier.clone();
1160            let acquire_thread = thread::spawn(move || {
1161                acquire_barrier.wait();
1162                acquire_manager.acquire_lease(query_id)
1163            });
1164
1165            barrier.wait();
1166            drop_thread.join().unwrap();
1167            let new_lease = acquire_thread.join().unwrap();
1168
1169            assert_eq!(manager.registry_count(), 1);
1170            assert_eq!(manager.weak_entry_count(), 1);
1171            drop(new_lease);
1172            assert_eq!(manager.registry_count(), 0);
1173            assert_eq!(manager.weak_entry_count(), 0);
1174        }
1175    }
1176
1177    #[test]
1178    fn registry_manager_old_drop_cannot_remove_replacement_registry() {
1179        let manager = Arc::new(DynFilterRegistryManager::default());
1180        let query_id = test_query_id(1);
1181        let old_lease = manager.acquire_lease(query_id);
1182        let old_registry = Arc::downgrade(old_lease.registry.as_ref().unwrap());
1183
1184        drop(old_lease);
1185        assert_eq!(manager.registry_count(), 0);
1186        assert_eq!(manager.weak_entry_count(), 0);
1187
1188        let replacement_lease = manager.acquire_lease(query_id);
1189        assert_eq!(manager.registry_count(), 1);
1190        assert_eq!(manager.weak_entry_count(), 1);
1191
1192        assert!(
1193            manager
1194                .remove_if_dropped_registry(&query_id, &old_registry)
1195                .is_none(),
1196            "old registry cleanup must not remove the replacement weak entry"
1197        );
1198        assert_eq!(manager.registry_count(), 1);
1199        assert_eq!(manager.weak_entry_count(), 1);
1200
1201        drop(replacement_lease);
1202        assert_eq!(manager.registry_count(), 0);
1203        assert_eq!(manager.weak_entry_count(), 0);
1204    }
1205
1206    #[test]
1207    fn registry_stores_filter_and_deduplicates_subscribers() {
1208        let registry = QueryDynFilterRegistry::new(test_query_id(1));
1209        let filter = test_dyn_filter(&["host"]);
1210        let filter_id = test_filter_id(1);
1211        let entry = match registry.register_remote_dyn_filter(filter_id.clone(), filter.clone()) {
1212            EntryRegistration::Inserted(entry) => entry,
1213            other => panic!("unexpected registration result: {other:?}"),
1214        };
1215
1216        assert_eq!(entry.filter_id(), &filter_id);
1217        assert_eq!(registry.entry_count(), 1);
1218
1219        let subscriber = Subscriber::new(RegionId::new(1024, 1), test_target(1));
1220        assert_eq!(
1221            registry.register_subscriber(&filter_id, subscriber.clone()),
1222            SubscriberRegistration::Added
1223        );
1224        assert_eq!(
1225            registry.register_subscriber(&filter_id, subscriber),
1226            SubscriberRegistration::Duplicate
1227        );
1228        assert_eq!(entry.subscribers().len(), 1);
1229    }
1230
1231    #[test]
1232    fn remove_subscriber_removes_only_exact_key() {
1233        let registry = QueryDynFilterRegistry::new(test_query_id(1));
1234        let filter = test_dyn_filter(&["host"]);
1235        let filter_id = test_filter_id(1);
1236        let entry = match registry.register_remote_dyn_filter(filter_id.clone(), filter) {
1237            EntryRegistration::Inserted(entry) => entry,
1238            other => panic!("unexpected registration result: {other:?}"),
1239        };
1240        let first = Subscriber::new(RegionId::new(1024, 1), test_target(1));
1241        let second = Subscriber::new(RegionId::new(1024, 1), test_target(2));
1242        assert_eq!(
1243            registry.register_subscriber(&filter_id, first.clone()),
1244            SubscriberRegistration::Added
1245        );
1246        assert_eq!(
1247            registry.register_subscriber(&filter_id, second.clone()),
1248            SubscriberRegistration::Added
1249        );
1250
1251        assert!(registry.remove_subscriber(&filter_id, &first));
1252        assert!(!registry.remove_subscriber(&filter_id, &first));
1253        assert_eq!(entry.subscribers(), vec![second]);
1254        assert!(!entry.fanout_started_for_test());
1255    }
1256
1257    #[test]
1258    fn subscriber_identity_includes_region_and_target() {
1259        let first_region = RegionId::new(1024, 1);
1260        let second_region = RegionId::new(1024, 2);
1261        let first_target = test_target(1);
1262        let second_target = test_target(2);
1263        let first = Subscriber::new(first_region, first_target.clone());
1264
1265        let mut subscribers = HashSet::new();
1266        assert!(subscribers.insert(first.clone()));
1267        assert!(!subscribers.insert(first));
1268        assert!(subscribers.insert(Subscriber::new(first_region, second_target)));
1269        assert!(subscribers.insert(Subscriber::new(second_region, first_target)));
1270        assert_eq!(subscribers.len(), 3);
1271    }
1272
1273    #[tokio::test]
1274    async fn fanout_sends_changed_generations_to_subscribers() {
1275        let query_id = test_query_id(1);
1276        let registry = Arc::new(QueryDynFilterRegistry::new(query_id));
1277        let filter = test_dyn_filter(&["host"]);
1278        let filter_id = test_filter_id(1);
1279        let entry = match registry.register_remote_dyn_filter(filter_id.clone(), filter.clone()) {
1280            EntryRegistration::Inserted(entry) => entry,
1281            other => panic!("unexpected registration result: {other:?}"),
1282        };
1283        let subscriber = Subscriber::new(RegionId::new(1024, 7), test_target(1));
1284        assert_eq!(
1285            registry.register_subscriber(&filter_id, subscriber.clone()),
1286            SubscriberRegistration::Added
1287        );
1288
1289        let handler = Arc::new(RecordingRegionQueryHandler::default());
1290        let handler_ref = handler.clone() as RegionQueryHandlerRef;
1291
1292        registry
1293            .fanout_snapshot(&handler_ref, &entry, filter.as_ref(), false)
1294            .await;
1295        let updates = handler.updates();
1296        assert_eq!(updates.len(), 1);
1297        assert_eq!(&updates[0].target, subscriber.target());
1298        assert_eq!(updates[0].query_id, query_id.to_string());
1299        assert_eq!(updates[0].filter_id, filter_id.to_string());
1300        assert_eq!(updates[0].generation, filter.snapshot_generation());
1301        assert!(!updates[0].is_complete);
1302        assert!(!updates[0].payload.is_empty());
1303
1304        registry
1305            .fanout_snapshot(&handler_ref, &entry, filter.as_ref(), false)
1306            .await;
1307        assert_eq!(handler.updates().len(), 1);
1308
1309        filter.update(lit(false) as _).unwrap();
1310        registry
1311            .fanout_snapshot(&handler_ref, &entry, filter.as_ref(), false)
1312            .await;
1313        let updates = handler.updates();
1314        assert_eq!(updates.len(), 2);
1315        assert_eq!(updates[1].generation, filter.snapshot_generation());
1316
1317        let second_subscriber = Subscriber::new(RegionId::new(1024, 8), test_target(2));
1318        assert_eq!(
1319            registry.register_subscriber(&filter_id, second_subscriber.clone()),
1320            SubscriberRegistration::Added
1321        );
1322        registry
1323            .fanout_snapshot(&handler_ref, &entry, filter.as_ref(), false)
1324            .await;
1325        let updates = handler.updates();
1326        assert_eq!(updates.len(), 4);
1327        assert!(
1328            updates[2..]
1329                .iter()
1330                .any(|update| update.target == subscriber.target().clone())
1331        );
1332        assert!(
1333            updates[2..]
1334                .iter()
1335                .any(|update| update.target == second_subscriber.target().clone())
1336        );
1337        assert_eq!(entry.subscribers().len(), 2);
1338    }
1339
1340    #[tokio::test]
1341    async fn fanout_task_waits_for_dynamic_filter_notifications() {
1342        let query_id = test_query_id(3);
1343        let manager = Arc::new(DynFilterRegistryManager::default());
1344        let lease = manager.acquire_lease(query_id);
1345        let registry_weak = Arc::downgrade(lease.registry.as_ref().unwrap());
1346        let filter = test_dyn_filter(&["host"]);
1347        let filter_id = test_filter_id(1);
1348        let _ = lease
1349            .registry()
1350            .register_remote_dyn_filter(filter_id.clone(), filter.clone());
1351        let subscriber = Subscriber::new(RegionId::new(1024, 7), test_target(1));
1352        assert_eq!(
1353            lease
1354                .registry()
1355                .register_subscriber(&filter_id, subscriber.clone()),
1356            SubscriberRegistration::Added
1357        );
1358
1359        let handler = Arc::new(RecordingRegionQueryHandler::default());
1360        lease.ensure_fanout_task(handler.clone() as RegionQueryHandlerRef);
1361
1362        handler.wait_for_update_count(1).await;
1363        let initial_generation = handler.updates()[0].generation;
1364
1365        filter.update(lit(false) as _).unwrap();
1366        handler.wait_for_update_count(2).await;
1367        let updates = handler.updates();
1368        assert!(updates[1].generation > initial_generation);
1369        assert_eq!(&updates[1].target, subscriber.target());
1370        assert_eq!(updates[1].filter_id, filter_id.to_string());
1371
1372        filter.mark_complete();
1373        handler.wait_for_update_count(3).await;
1374        let updates = handler.updates();
1375        assert!(updates[2].is_complete);
1376
1377        drop(lease);
1378        handler.wait_for_unregister_count(1).await;
1379        let unregisters = handler.unregisters();
1380        assert_eq!(&unregisters[0].target, subscriber.target());
1381        assert_eq!(unregisters[0].target, updates[0].target);
1382        assert_eq!(unregisters[0].filter_id, filter_id.to_string());
1383
1384        wait_for_registry_drop(registry_weak).await;
1385    }
1386
1387    #[tokio::test]
1388    async fn late_subscriber_receives_completed_snapshot_at_its_target() {
1389        let query_id = test_query_id(10);
1390        let manager = Arc::new(DynFilterRegistryManager::default());
1391        let lease = manager.acquire_lease(query_id);
1392        let filter = test_dyn_filter(&["host"]);
1393        let filter_id = test_filter_id(1);
1394        let _ = lease
1395            .registry()
1396            .register_remote_dyn_filter(filter_id.clone(), filter.clone());
1397
1398        // Model the period while do_get is awaiting the selected target: the producer may
1399        // advance and complete before a subscriber is registered.
1400        filter.update(lit(false) as _).unwrap();
1401        filter.mark_complete();
1402
1403        let subscriber = Subscriber::new(RegionId::new(1024, 7), test_target(7));
1404        assert_eq!(
1405            lease
1406                .registry()
1407                .register_subscriber(&filter_id, subscriber.clone()),
1408            SubscriberRegistration::Added
1409        );
1410
1411        let handler = Arc::new(RecordingRegionQueryHandler::default());
1412        lease.ensure_fanout_task(handler.clone() as RegionQueryHandlerRef);
1413        handler.wait_for_complete_update().await;
1414
1415        let updates = handler.updates();
1416        let latest = updates.last().unwrap();
1417        assert_eq!(&latest.target, subscriber.target());
1418        assert_eq!(latest.generation, filter.snapshot_generation());
1419        assert!(latest.is_complete);
1420    }
1421
1422    #[tokio::test]
1423    async fn repeated_ensure_fanout_task_keeps_single_watcher() {
1424        let query_id = test_query_id(6);
1425        let manager = Arc::new(DynFilterRegistryManager::default());
1426        let lease = manager.acquire_lease(query_id);
1427        let registry_weak = Arc::downgrade(lease.registry.as_ref().unwrap());
1428        let filter = test_dyn_filter(&["host"]);
1429        let filter_id = test_filter_id(1);
1430        let entry = match lease
1431            .registry()
1432            .register_remote_dyn_filter(filter_id.clone(), filter.clone())
1433        {
1434            EntryRegistration::Inserted(entry) => entry,
1435            other => panic!("unexpected registration result: {other:?}"),
1436        };
1437        let subscriber = Subscriber::new(RegionId::new(1024, 7), test_target(1));
1438        assert_eq!(
1439            lease
1440                .registry()
1441                .register_subscriber(&filter_id, subscriber.clone()),
1442            SubscriberRegistration::Added
1443        );
1444
1445        let handler = Arc::new(RecordingRegionQueryHandler::default());
1446        lease.ensure_fanout_task(handler.clone() as RegionQueryHandlerRef);
1447        lease.ensure_fanout_task(handler.clone() as RegionQueryHandlerRef);
1448
1449        assert!(entry.fanout_started_for_test());
1450        handler.wait_for_update_count(1).await;
1451        tokio::time::sleep(Duration::from_millis(50)).await;
1452        assert_eq!(handler.updates().len(), 1);
1453
1454        filter.update(lit(false) as _).unwrap();
1455        handler.wait_for_update_count(2).await;
1456        tokio::time::sleep(Duration::from_millis(50)).await;
1457        assert_eq!(handler.updates().len(), 2);
1458
1459        drop(lease);
1460        handler.wait_for_unregister_count(1).await;
1461        wait_for_registry_drop(registry_weak).await;
1462    }
1463
1464    #[tokio::test]
1465    async fn fanout_task_resends_complete_snapshot_to_late_subscriber() {
1466        let query_id = test_query_id(7);
1467        let manager = Arc::new(DynFilterRegistryManager::default());
1468        let lease = manager.acquire_lease(query_id);
1469        let registry_weak = Arc::downgrade(lease.registry.as_ref().unwrap());
1470        let filter = test_dyn_filter(&["host"]);
1471        let filter_id = test_filter_id(1);
1472        let _ = lease
1473            .registry()
1474            .register_remote_dyn_filter(filter_id.clone(), filter.clone());
1475        let first_subscriber = Subscriber::new(RegionId::new(1024, 7), test_target(1));
1476        assert_eq!(
1477            lease
1478                .registry()
1479                .register_subscriber(&filter_id, first_subscriber.clone()),
1480            SubscriberRegistration::Added
1481        );
1482
1483        let handler = Arc::new(RecordingRegionQueryHandler::default());
1484        lease.ensure_fanout_task(handler.clone() as RegionQueryHandlerRef);
1485        handler.wait_for_update_count(1).await;
1486
1487        filter.mark_complete();
1488        handler.wait_for_update_count(2).await;
1489        assert!(handler.updates()[1].is_complete);
1490
1491        let late_subscriber = Subscriber::new(RegionId::new(1024, 8), test_target(1));
1492        assert_eq!(
1493            lease
1494                .registry()
1495                .register_subscriber(&filter_id, late_subscriber.clone()),
1496            SubscriberRegistration::Added
1497        );
1498
1499        handler.wait_for_update_count(4).await;
1500        let updates = handler.updates();
1501        assert!(
1502            updates[2..]
1503                .iter()
1504                .any(|update| update.target == first_subscriber.target().clone()
1505                    && update.is_complete)
1506        );
1507        assert!(
1508            updates[2..]
1509                .iter()
1510                .any(|update| update.target == late_subscriber.target().clone()
1511                    && update.is_complete)
1512        );
1513
1514        drop(lease);
1515        handler.wait_for_unregister_count(1).await;
1516        wait_for_registry_drop(registry_weak).await;
1517    }
1518
1519    #[tokio::test]
1520    async fn fanout_task_unregisters_when_producer_filter_is_dropped() {
1521        let query_id = test_query_id(8);
1522        let manager = Arc::new(DynFilterRegistryManager::default());
1523        let lease = manager.acquire_lease(query_id);
1524        let registry_weak = Arc::downgrade(lease.registry.as_ref().unwrap());
1525        let filter = test_dyn_filter(&["host"]);
1526        let filter_id = test_filter_id(1);
1527        let _ = lease
1528            .registry()
1529            .register_remote_dyn_filter(filter_id.clone(), filter.clone());
1530        let subscriber = Subscriber::new(RegionId::new(1024, 7), test_target(1));
1531        assert_eq!(
1532            lease
1533                .registry()
1534                .register_subscriber(&filter_id, subscriber.clone()),
1535            SubscriberRegistration::Added
1536        );
1537
1538        let handler = Arc::new(RecordingRegionQueryHandler::default());
1539        lease.ensure_fanout_task(handler.clone() as RegionQueryHandlerRef);
1540        handler.wait_for_update_count(1).await;
1541
1542        drop(filter);
1543        handler.wait_for_unregister_count(1).await;
1544        let unregisters = handler.unregisters();
1545        assert_eq!(&unregisters[0].target, subscriber.target());
1546        assert_eq!(unregisters[0].filter_id, filter_id.to_string());
1547
1548        drop(lease);
1549        wait_for_registry_drop(registry_weak).await;
1550    }
1551
1552    #[tokio::test]
1553    async fn reconcile_tick_catches_update_while_fanout_is_in_flight() {
1554        let query_id = test_query_id(4);
1555        let manager = Arc::new(DynFilterRegistryManager::default());
1556        let lease = manager.acquire_lease(query_id);
1557        let registry_weak = Arc::downgrade(lease.registry.as_ref().unwrap());
1558        let filter = test_dyn_filter(&["host"]);
1559        let filter_id = test_filter_id(1);
1560        let _ = lease
1561            .registry()
1562            .register_remote_dyn_filter(filter_id.clone(), filter.clone());
1563        let subscriber = Subscriber::new(RegionId::new(1024, 7), test_target(1));
1564        assert_eq!(
1565            lease
1566                .registry()
1567                .register_subscriber(&filter_id, subscriber.clone()),
1568            SubscriberRegistration::Added
1569        );
1570
1571        let handler = Arc::new(RecordingRegionQueryHandler::default());
1572        handler.block_next_update();
1573        lease.ensure_fanout_task(handler.clone() as RegionQueryHandlerRef);
1574
1575        handler.wait_for_blocked_update().await;
1576        let initial_generation = handler.updates()[0].generation;
1577
1578        // Update before the watcher can subscribe again; reconcile must catch it.
1579        filter.update(lit(false) as _).unwrap();
1580        handler.release_blocked_update();
1581
1582        handler.wait_for_update_count(2).await;
1583        let updates = handler.updates();
1584        assert!(updates[1].generation > initial_generation);
1585        assert_eq!(&updates[1].target, subscriber.target());
1586        assert_eq!(updates[1].filter_id, filter_id.to_string());
1587
1588        drop(lease);
1589        handler.wait_for_unregister_count(1).await;
1590        wait_for_registry_drop(registry_weak).await;
1591    }
1592
1593    #[tokio::test]
1594    async fn fanout_task_unregisters_after_lifecycle_close_during_blocked_update() {
1595        let query_id = test_query_id(5);
1596        let manager = Arc::new(DynFilterRegistryManager::default());
1597        let lease = manager.acquire_lease(query_id);
1598        let registry_weak = Arc::downgrade(lease.registry.as_ref().unwrap());
1599        let filter = test_dyn_filter(&["host"]);
1600        let filter_id = test_filter_id(1);
1601        let _ = lease
1602            .registry()
1603            .register_remote_dyn_filter(filter_id.clone(), filter.clone());
1604        let subscriber = Subscriber::new(RegionId::new(1024, 7), test_target(1));
1605        assert_eq!(
1606            lease
1607                .registry()
1608                .register_subscriber(&filter_id, subscriber.clone()),
1609            SubscriberRegistration::Added
1610        );
1611
1612        let handler = Arc::new(RecordingRegionQueryHandler::default());
1613        handler.block_next_update();
1614        lease.ensure_fanout_task(handler.clone() as RegionQueryHandlerRef);
1615
1616        handler.wait_for_blocked_update().await;
1617        drop(lease);
1618
1619        handler.wait_for_unregister_count(1).await;
1620        let unregisters = handler.unregisters();
1621        assert_eq!(&unregisters[0].target, subscriber.target());
1622        assert_eq!(unregisters[0].filter_id, filter_id.to_string());
1623        wait_for_registry_drop(registry_weak).await;
1624    }
1625
1626    #[tokio::test]
1627    async fn update_timeout_does_not_stop_fanout_for_other_subscribers() {
1628        let query_id = test_query_id(9);
1629        let registry = QueryDynFilterRegistry::new(query_id);
1630        let filter = test_dyn_filter(&["host"]);
1631        let filter_id = test_filter_id(1);
1632        let entry = match registry.register_remote_dyn_filter(filter_id.clone(), filter.clone()) {
1633            EntryRegistration::Inserted(entry) => entry,
1634            other => panic!("unexpected registration result: {other:?}"),
1635        };
1636        let first_subscriber = Subscriber::new(RegionId::new(1024, 7), test_target(1));
1637        let second_subscriber = Subscriber::new(RegionId::new(1024, 8), test_target(2));
1638        assert_eq!(
1639            registry.register_subscriber(&filter_id, first_subscriber.clone()),
1640            SubscriberRegistration::Added
1641        );
1642        assert_eq!(
1643            registry.register_subscriber(&filter_id, second_subscriber.clone()),
1644            SubscriberRegistration::Added
1645        );
1646
1647        let handler = Arc::new(RecordingRegionQueryHandler::default());
1648        let handler_ref = handler.clone() as RegionQueryHandlerRef;
1649        let mut lifecycle_rx = registry.lifecycle_tx.subscribe();
1650        handler.block_next_update();
1651        assert!(
1652            fanout_snapshot_for_query(
1653                query_id,
1654                &handler_ref,
1655                &entry,
1656                filter.as_ref(),
1657                false,
1658                &mut lifecycle_rx,
1659                Duration::from_millis(100),
1660            )
1661            .await
1662        );
1663
1664        handler.wait_for_blocked_update().await;
1665        // Fanout is serial and the blocked RPC stays blocked; the second update proves
1666        // timeout continued to the next subscriber.
1667        handler.wait_for_update_count(2).await;
1668
1669        let initial_updates = handler.updates();
1670        assert_eq!(
1671            initial_updates.len(),
1672            2,
1673            "the healthy subscriber must still receive the update after another subscriber times out"
1674        );
1675        assert!(
1676            initial_updates
1677                .iter()
1678                .any(|update| update.target == first_subscriber.target().clone())
1679        );
1680        assert!(
1681            initial_updates
1682                .iter()
1683                .any(|update| update.target == second_subscriber.target().clone())
1684        );
1685
1686        filter.update(lit(false) as _).unwrap();
1687        assert!(
1688            fanout_snapshot_for_query(
1689                query_id,
1690                &handler_ref,
1691                &entry,
1692                filter.as_ref(),
1693                false,
1694                &mut lifecycle_rx,
1695                Duration::from_millis(100),
1696            )
1697            .await
1698        );
1699        handler.wait_for_update_count(4).await;
1700        let updates = handler.updates();
1701        assert!(
1702            updates[2..]
1703                .iter()
1704                .any(|update| update.target == first_subscriber.target().clone())
1705        );
1706        assert!(
1707            updates[2..]
1708                .iter()
1709                .any(|update| update.target == second_subscriber.target().clone())
1710        );
1711
1712        registry.unregister_all_once(&handler_ref).await;
1713        handler.wait_for_unregister_count(1).await;
1714    }
1715
1716    #[tokio::test]
1717    async fn unregister_fanout_is_idempotent() {
1718        let query_id = test_query_id(2);
1719        let registry = QueryDynFilterRegistry::new(query_id);
1720        let filter = test_dyn_filter(&["host"]);
1721        let filter_id = test_filter_id(1);
1722        let _ = registry.register_remote_dyn_filter(filter_id.clone(), filter);
1723        let subscriber = Subscriber::new(RegionId::new(1024, 7), test_target(1));
1724        assert_eq!(
1725            registry.register_subscriber(&filter_id, subscriber.clone()),
1726            SubscriberRegistration::Added
1727        );
1728
1729        let handler = Arc::new(RecordingRegionQueryHandler::default());
1730        let handler_ref = handler.clone() as RegionQueryHandlerRef;
1731
1732        registry.unregister_all_once(&handler_ref).await;
1733        registry.unregister_all_once(&handler_ref).await;
1734
1735        let unregisters = handler.unregisters();
1736        assert_eq!(unregisters.len(), 1);
1737        assert_eq!(&unregisters[0].target, subscriber.target());
1738        assert_eq!(unregisters[0].query_id, query_id.to_string());
1739        assert_eq!(unregisters[0].filter_id, filter_id.to_string());
1740    }
1741}