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