1use std::collections::HashMap;
16use std::collections::hash_map::Entry;
17use std::sync::Arc;
18
19use risingwave_common::id::JobId;
20use risingwave_common::system_param::reader::SystemParamsReader;
21use risingwave_common::util::iter_util::ZipEqDebug;
22use risingwave_meta_model::ObjectId;
23use risingwave_pb::common::{WorkerNode, WorkerType};
24use risingwave_pb::meta::object::PbObjectInfo;
25use risingwave_pb::meta::subscribe_response::{Info, Operation};
26use risingwave_pb::meta::{
27 MetaSnapshot, PbObject, PbObjectGroup, SubscribeResponse, SubscribeType,
28};
29use thiserror_ext::AsReport;
30use tokio::sync::Mutex;
31use tokio::sync::mpsc::{self, UnboundedSender};
32use tonic::Status;
33
34use crate::controller::SqlMetaStore;
35use crate::manager::WorkerKey;
36use crate::manager::notification_version::NotificationVersionGenerator;
37use crate::model::FragmentId;
38
39pub type MessageStatus = Status;
40pub type Notification = Result<SubscribeResponse, Status>;
41pub type NotificationManagerRef = Arc<NotificationManager>;
42pub type NotificationVersion = u64;
43pub const IGNORED_NOTIFICATION_VERSION: u64 = 0;
45
46pub struct FrontendNotificationBatch<'a> {
52 notification_manager: &'a NotificationManager,
53 pending: Vec<(Operation, Info)>,
54}
55
56impl<'a> FrontendNotificationBatch<'a> {
57 pub fn new(notification_manager: &'a NotificationManager) -> Self {
58 Self {
59 notification_manager,
60 pending: Vec::new(),
61 }
62 }
63
64 pub fn add(&mut self, operation: Operation, info: Info) {
65 self.pending.push((operation, info));
66 }
67
68 #[must_use]
69 pub async fn publish(self) -> Option<NotificationVersion> {
70 self.notification_manager
71 .notify_frontend_batch(self.pending)
72 .await
73 }
74}
75
76#[derive(Clone, Debug)]
77pub enum LocalNotification {
78 WorkerNodeDeleted(WorkerNode),
79 WorkerNodeActivated(WorkerNode),
80 SystemParamsChange(SystemParamsReader),
81 BatchParallelismChange,
82 ServingFragmentMappingsUpsert(Vec<FragmentId>),
83 ServingFragmentMappingsDelete(Vec<FragmentId>),
84 SourceDropped(ObjectId),
85 StreamingJobBackfillFinished(JobId),
86}
87
88#[derive(Debug)]
89struct Target {
90 subscribe_type: SubscribeType,
91 worker_key: Option<WorkerKey>,
93}
94
95impl From<SubscribeType> for Target {
96 fn from(value: SubscribeType) -> Self {
97 Self {
98 subscribe_type: value,
99 worker_key: None,
100 }
101 }
102}
103
104#[derive(Debug)]
105struct Task {
106 target: Target,
107 operation: Operation,
108 info: Info,
109 version: Option<NotificationVersion>,
112}
113
114pub struct NotificationManager {
116 core: Arc<parking_lot::Mutex<NotificationManagerCore>>,
117 task_tx: UnboundedSender<Task>,
119 version_generator: Mutex<NotificationVersionGenerator>,
121}
122
123impl NotificationManager {
124 pub async fn new(meta_store_impl: SqlMetaStore) -> Self {
125 let (task_tx, mut task_rx) = mpsc::unbounded_channel::<Task>();
127 let core = Arc::new(parking_lot::Mutex::new(NotificationManagerCore::new()));
128 let core_clone = core.clone();
129 let version_generator = NotificationVersionGenerator::new(meta_store_impl)
130 .await
131 .unwrap();
132 let mut latest_version = version_generator.current_version();
133
134 tokio::spawn(async move {
135 while let Some(task) = task_rx.recv().await {
136 if let Some(version) = task.version {
137 latest_version = version;
138 }
139 let response = SubscribeResponse {
140 status: None,
141 operation: task.operation as i32,
142 info: Some(task.info),
143 version: latest_version,
144 };
145 core.lock().notify(task.target, response);
146 }
147 });
148
149 Self {
150 core: core_clone,
151 task_tx,
152 version_generator: Mutex::new(version_generator),
153 }
154 }
155
156 pub fn abort_all(&self) {
157 let mut guard = self.core.lock();
158 *guard = NotificationManagerCore::new();
159 guard.exiting = true;
160 }
161
162 #[inline(always)]
163 fn notify(
164 &self,
165 target: Target,
166 operation: Operation,
167 info: Info,
168 version: Option<NotificationVersion>,
169 ) {
170 let task = Task {
171 target,
172 operation,
173 info,
174 version,
175 };
176 self.task_tx.send(task).unwrap();
177 }
178
179 async fn notify_with_version(
181 &self,
182 target: Target,
183 operation: Operation,
184 info: Info,
185 ) -> NotificationVersion {
186 let mut version_guard = self.version_generator.lock().await;
187 version_guard.increase_version().await;
188 let version = version_guard.current_version();
189 self.notify(target, operation, info, Some(version));
190 version
191 }
192
193 async fn notify_frontend_batch(
196 &self,
197 notifications: Vec<(Operation, Info)>,
198 ) -> Option<NotificationVersion> {
199 let batch_len: NotificationVersion = notifications.len().try_into().unwrap();
200 if batch_len == 0 {
201 return None;
202 }
203
204 let mut version_guard = self.version_generator.lock().await;
205 let first_version = version_guard.current_version() + 1;
206 version_guard.increase_version_by(batch_len).await;
207 let final_version = version_guard.current_version();
208
209 for ((operation, info), version) in notifications
210 .into_iter()
211 .zip_eq_debug(first_version..=final_version)
212 {
213 self.notify(
214 SubscribeType::Frontend.into(),
215 operation,
216 info,
217 Some(version),
218 );
219 }
220
221 Some(final_version)
222 }
223
224 #[inline(always)]
226 fn notify_without_version(&self, target: Target, operation: Operation, info: Info) {
227 self.notify(target, operation, info, None);
228 }
229
230 pub fn notify_snapshot(
231 &self,
232 sender: UnboundedSender<Notification>,
233 meta_snapshot: MetaSnapshot,
234 ) {
235 let response = SubscribeResponse {
236 status: None,
237 operation: Operation::Snapshot as i32,
238 info: Some(Info::Snapshot(meta_snapshot)),
239 version: Default::default(),
240 };
241 let _ = sender.send(Ok(response)).inspect_err(|err| {
242 tracing::warn!("Failed to notify direct subscriber: {}", err.as_report());
243 });
244 }
245
246 pub fn notify_all_without_version(&self, operation: Operation, info: Info) {
247 for subscribe_type in [
248 SubscribeType::Frontend,
249 SubscribeType::Hummock,
250 SubscribeType::Compactor,
251 SubscribeType::Compute,
252 ] {
253 self.notify_without_version(subscribe_type.into(), operation, info.clone());
254 }
255 }
256
257 #[must_use]
258 pub async fn notify_frontend(&self, operation: Operation, info: Info) -> NotificationVersion {
259 self.notify_with_version(SubscribeType::Frontend.into(), operation, info)
260 .await
261 }
262
263 #[must_use]
264 pub async fn notify_frontend_object_info(
265 &self,
266 operation: Operation,
267 object_info: PbObjectInfo,
268 ) -> NotificationVersion {
269 self.notify_with_version(
270 SubscribeType::Frontend.into(),
271 operation,
272 Info::ObjectGroup(PbObjectGroup {
273 objects: vec![PbObject {
274 object_info: object_info.into(),
275 }],
276 dependencies: vec![],
277 }),
278 )
279 .await
280 }
281
282 pub(crate) fn notify_hummock_targeted_update(&self, worker_key: WorkerKey, info: Info) {
283 self.notify_without_version(
284 Target {
285 subscribe_type: SubscribeType::Hummock,
286 worker_key: Some(worker_key),
287 },
288 Operation::Update,
289 info,
290 );
291 }
292
293 pub fn notify_compute_without_version(&self, operation: Operation, info: Info) {
294 self.notify_without_version(SubscribeType::Compute.into(), operation, info)
295 }
296
297 pub fn notify_frontend_without_version(&self, operation: Operation, info: Info) {
298 self.notify_without_version(SubscribeType::Frontend.into(), operation, info)
299 }
300
301 pub fn notify_hummock(&self, operation: Operation, info: Info) {
302 self.notify_without_version(SubscribeType::Hummock.into(), operation, info)
303 }
304
305 pub fn notify_compactor(&self, operation: Operation, info: Info) {
306 self.notify_without_version(SubscribeType::Compactor.into(), operation, info)
307 }
308
309 #[cfg(any(test, feature = "test"))]
310 pub fn notify_hummock_with_version(
311 &self,
312 operation: Operation,
313 info: Info,
314 version: Option<NotificationVersion>,
315 ) {
316 self.notify(SubscribeType::Hummock.into(), operation, info, version)
317 }
318
319 pub fn notify_local_subscribers(&self, notification: LocalNotification) {
320 let mut core_guard = self.core.lock();
321 core_guard.local_senders.retain(|sender| {
322 if let Err(err) = sender.send(notification.clone()) {
323 tracing::warn!(error = %err.as_report(), "Failed to notify local subscriber");
324 return false;
325 }
326 true
327 });
328 }
329
330 pub fn delete_sender(&self, worker_type: WorkerType, worker_key: WorkerKey) {
332 let mut core_guard = self.core.lock();
333 match worker_type {
336 WorkerType::Frontend => core_guard.frontend_senders.remove(&worker_key),
337 WorkerType::ComputeNode | WorkerType::RiseCtl => {
338 core_guard.hummock_senders.remove(&worker_key)
339 }
340 WorkerType::Compactor => core_guard.compactor_senders.remove(&worker_key),
341 _ => unreachable!(),
342 };
343 }
344
345 pub fn insert_sender(
347 &self,
348 subscribe_type: SubscribeType,
349 worker_key: WorkerKey,
350 sender: UnboundedSender<Notification>,
351 ) {
352 let mut core_guard = self.core.lock();
353 if core_guard.exiting {
354 tracing::warn!("notification manager exiting.");
355 return;
356 }
357 let senders = core_guard.senders_of(subscribe_type);
358
359 senders.insert(worker_key, sender);
360 }
361
362 pub fn insert_local_sender(&self, sender: UnboundedSender<LocalNotification>) {
363 let mut core_guard = self.core.lock();
364 if core_guard.exiting {
365 tracing::warn!("notification manager exiting.");
366 return;
367 }
368 core_guard.local_senders.push(sender);
369 }
370
371 #[cfg(test)]
372 pub fn clear_local_sender(&self) {
373 self.core.lock().local_senders.clear();
374 }
375
376 pub async fn current_version(&self) -> NotificationVersion {
377 let version_guard = self.version_generator.lock().await;
378 version_guard.current_version()
379 }
380}
381
382type SenderMap = HashMap<WorkerKey, UnboundedSender<Notification>>;
383
384struct NotificationManagerCore {
385 frontend_senders: SenderMap,
387 hummock_senders: SenderMap,
389 compactor_senders: SenderMap,
391 compute_senders: SenderMap,
393 local_senders: Vec<UnboundedSender<LocalNotification>>,
395 exiting: bool,
396}
397
398impl NotificationManagerCore {
399 fn new() -> Self {
400 Self {
401 frontend_senders: HashMap::new(),
402 hummock_senders: HashMap::new(),
403 compactor_senders: HashMap::new(),
404 compute_senders: HashMap::new(),
405 local_senders: vec![],
406 exiting: false,
407 }
408 }
409
410 fn notify(&mut self, target: Target, response: SubscribeResponse) {
411 macro_rules! warn_send_failure {
412 ($subscribe_type:expr, $worker_key:expr, $err:expr) => {
413 tracing::warn!(
414 "Failed to notify {:?} {:?}: {}",
415 $subscribe_type,
416 $worker_key,
417 $err
418 );
419 };
420 }
421
422 let senders = self.senders_of(target.subscribe_type);
423
424 if let Some(worker_key) = target.worker_key {
425 match senders.entry(worker_key.clone()) {
426 Entry::Occupied(entry) => {
427 let _ = entry.get().send(Ok(response)).inspect_err(|err| {
428 warn_send_failure!(target.subscribe_type, &worker_key, err.as_report());
429 entry.remove_entry();
430 });
431 }
432 Entry::Vacant(_) => {
433 tracing::warn!("Failed to find notification sender of {:?}", worker_key)
434 }
435 }
436 } else {
437 senders.retain(|worker_key, sender| {
438 sender
439 .send(Ok(response.clone()))
440 .inspect_err(|err| {
441 warn_send_failure!(target.subscribe_type, &worker_key, err.as_report());
442 })
443 .is_ok()
444 });
445 }
446 }
447
448 fn senders_of(&mut self, subscribe_type: SubscribeType) -> &mut SenderMap {
449 match subscribe_type {
450 SubscribeType::Frontend => &mut self.frontend_senders,
451 SubscribeType::Hummock => &mut self.hummock_senders,
452 SubscribeType::Compactor => &mut self.compactor_senders,
453 SubscribeType::Compute => &mut self.compute_senders,
454 SubscribeType::Unspecified => unreachable!(),
455 }
456 }
457}
458
459#[cfg(test)]
460mod tests {
461 use risingwave_common::id::JobId;
462 use risingwave_pb::common::HostAddress;
463
464 use super::*;
465 use crate::manager::WorkerKey;
466
467 #[tokio::test]
468 async fn test_multiple_subscribers_one_worker() {
469 let mgr = NotificationManager::new(SqlMetaStore::for_test().await).await;
470 let worker_key1 = WorkerKey(HostAddress {
471 host: "a".to_owned(),
472 port: 1,
473 });
474 let worker_key2 = WorkerKey(HostAddress {
475 host: "a".to_owned(),
476 port: 2,
477 });
478 let (tx1, mut rx1) = mpsc::unbounded_channel();
479 let (tx2, mut rx2) = mpsc::unbounded_channel();
480 let (tx3, mut rx3) = mpsc::unbounded_channel();
481 mgr.insert_sender(SubscribeType::Hummock, worker_key1.clone(), tx1.clone());
482 mgr.insert_sender(SubscribeType::Frontend, worker_key1.clone(), tx2);
483 mgr.insert_sender(SubscribeType::Frontend, worker_key2, tx3);
484 mgr.notify_snapshot(tx1, MetaSnapshot::default());
485 assert!(rx1.recv().await.is_some());
486 assert!(rx2.try_recv().is_err());
487 assert!(rx3.try_recv().is_err());
488
489 let version = mgr
490 .notify_frontend(Operation::Add, Info::Database(Default::default()))
491 .await;
492 assert!(rx1.try_recv().is_err());
493 let frontend_notification = rx2.recv().await.unwrap().unwrap();
494 assert_eq!(frontend_notification.version, version);
495 assert_eq!(
496 rx3.recv().await.unwrap().unwrap().version,
497 frontend_notification.version
498 );
499
500 mgr.notify_hummock(Operation::Add, Info::Database(Default::default()));
501 assert_eq!(
502 rx1.recv().await.unwrap().unwrap().version,
503 frontend_notification.version
504 );
505 }
506
507 #[tokio::test]
508 async fn test_frontend_notification_batch_assigns_consecutive_versions() {
509 let mgr = NotificationManager::new(SqlMetaStore::for_test().await).await;
510 let worker_key = WorkerKey(HostAddress {
511 host: "a".to_owned(),
512 port: 1,
513 });
514 let (tx, mut rx) = mpsc::unbounded_channel();
515 mgr.insert_sender(SubscribeType::Frontend, worker_key, tx);
516
517 let initial_version = mgr.current_version().await;
518 let mut batch = FrontendNotificationBatch::new(&mgr);
519 batch.add(Operation::Add, Info::Database(Default::default()));
520 batch.add(Operation::Add, Info::Schema(Default::default()));
521 let published_version = batch
522 .publish()
523 .await
524 .expect("the test notification batch contains two notifications");
525
526 assert_eq!(published_version, initial_version + 2);
527 assert_eq!(mgr.current_version().await, published_version);
528 assert_eq!(
529 rx.recv().await.unwrap().unwrap().version,
530 initial_version + 1
531 );
532 assert_eq!(rx.recv().await.unwrap().unwrap().version, published_version);
533 }
534
535 #[tokio::test]
536 async fn test_notify_hummock_targeted_update() {
537 let mgr = NotificationManager::new(SqlMetaStore::for_test().await).await;
538 let worker_key1 = WorkerKey(HostAddress {
539 host: "a".to_owned(),
540 port: 1,
541 });
542 let worker_key2 = WorkerKey(HostAddress {
543 host: "a".to_owned(),
544 port: 2,
545 });
546 let (hummock_tx1, mut hummock_rx1) = mpsc::unbounded_channel();
547 let (hummock_tx2, mut hummock_rx2) = mpsc::unbounded_channel();
548 let (frontend_tx1, mut frontend_rx1) = mpsc::unbounded_channel();
549 mgr.insert_sender(SubscribeType::Hummock, worker_key1.clone(), hummock_tx1);
550 mgr.insert_sender(SubscribeType::Hummock, worker_key2.clone(), hummock_tx2);
551 mgr.insert_sender(SubscribeType::Frontend, worker_key1.clone(), frontend_tx1);
552
553 let version = mgr.current_version().await;
554 mgr.notify_hummock_targeted_update(worker_key1, Info::Database(Default::default()));
555
556 let notification = hummock_rx1.recv().await.unwrap().unwrap();
557 assert_eq!(notification.version, version);
558 assert_eq!(mgr.current_version().await, version);
559 assert!(hummock_rx2.try_recv().is_err());
560 assert!(frontend_rx1.try_recv().is_err());
561 }
562
563 #[tokio::test]
564 async fn test_snapshot_uses_original_sender_after_resubscribe() {
565 let mgr = NotificationManager::new(SqlMetaStore::for_test().await).await;
566 let worker_key = WorkerKey(HostAddress {
567 host: "a".to_owned(),
568 port: 1,
569 });
570 let (tx1, mut rx1) = mpsc::unbounded_channel();
571 mgr.insert_sender(SubscribeType::Hummock, worker_key.clone(), tx1.clone());
572 mgr.notify_snapshot(tx1, MetaSnapshot::default());
573
574 let (tx2, mut rx2) = mpsc::unbounded_channel();
575 mgr.insert_sender(SubscribeType::Hummock, worker_key, tx2);
576
577 assert!(rx1.recv().await.is_some());
578 assert!(rx2.try_recv().is_err());
579
580 let version = mgr.current_version().await;
581 mgr.notify_hummock(Operation::Add, Info::Database(Default::default()));
582 assert!(rx1.try_recv().is_err());
583 let notification = rx2.recv().await.unwrap().unwrap();
584 assert_eq!(notification.version, version);
585 assert_eq!(mgr.current_version().await, version);
586 }
587
588 #[tokio::test]
589 async fn test_local_notification_backfill_finished() {
590 let mgr = NotificationManager::new(SqlMetaStore::for_test().await).await;
591 let (tx, mut rx) = mpsc::unbounded_channel();
592 mgr.insert_local_sender(tx);
593
594 let job_id = JobId::new(42);
595 mgr.notify_local_subscribers(LocalNotification::StreamingJobBackfillFinished(job_id));
596
597 match rx.recv().await.expect("should receive notification") {
598 LocalNotification::StreamingJobBackfillFinished(received) => {
599 assert_eq!(received, job_id);
600 }
601 other => panic!("unexpected notification: {other:?}"),
602 }
603 }
604}