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_meta_model::ObjectId;
22use risingwave_pb::common::{WorkerNode, WorkerType};
23use risingwave_pb::meta::object::PbObjectInfo;
24use risingwave_pb::meta::subscribe_response::{Info, Operation};
25use risingwave_pb::meta::{
26 MetaSnapshot, PbObject, PbObjectGroup, SubscribeResponse, SubscribeType,
27};
28use thiserror_ext::AsReport;
29use tokio::sync::Mutex;
30use tokio::sync::mpsc::{self, UnboundedSender};
31use tonic::Status;
32
33use crate::controller::SqlMetaStore;
34use crate::manager::WorkerKey;
35use crate::manager::notification_version::NotificationVersionGenerator;
36use crate::model::FragmentId;
37
38pub type MessageStatus = Status;
39pub type Notification = Result<SubscribeResponse, Status>;
40pub type NotificationManagerRef = Arc<NotificationManager>;
41pub type NotificationVersion = u64;
42pub const IGNORED_NOTIFICATION_VERSION: u64 = 0;
44
45#[derive(Clone, Debug)]
46pub enum LocalNotification {
47 WorkerNodeDeleted(WorkerNode),
48 WorkerNodeActivated(WorkerNode),
49 SystemParamsChange(SystemParamsReader),
50 BatchParallelismChange,
51 ServingFragmentMappingsUpsert(Vec<FragmentId>),
52 ServingFragmentMappingsDelete(Vec<FragmentId>),
53 SourceDropped(ObjectId),
54 StreamingJobBackfillFinished(JobId),
55}
56
57#[derive(Debug)]
58struct Target {
59 subscribe_type: SubscribeType,
60 worker_key: Option<WorkerKey>,
62}
63
64impl From<SubscribeType> for Target {
65 fn from(value: SubscribeType) -> Self {
66 Self {
67 subscribe_type: value,
68 worker_key: None,
69 }
70 }
71}
72
73#[derive(Debug)]
74struct Task {
75 target: Target,
76 operation: Operation,
77 info: Info,
78 version: Option<NotificationVersion>,
79}
80
81pub struct NotificationManager {
83 core: Arc<parking_lot::Mutex<NotificationManagerCore>>,
84 task_tx: UnboundedSender<Task>,
86 version_generator: Mutex<NotificationVersionGenerator>,
88}
89
90impl NotificationManager {
91 pub async fn new(meta_store_impl: SqlMetaStore) -> Self {
92 let (task_tx, mut task_rx) = mpsc::unbounded_channel::<Task>();
94 let core = Arc::new(parking_lot::Mutex::new(NotificationManagerCore::new()));
95 let core_clone = core.clone();
96 let version_generator = NotificationVersionGenerator::new(meta_store_impl)
97 .await
98 .unwrap();
99
100 tokio::spawn(async move {
101 while let Some(task) = task_rx.recv().await {
102 let response = SubscribeResponse {
103 status: None,
104 operation: task.operation as i32,
105 info: Some(task.info),
106 version: task.version.unwrap_or_default(),
107 };
108 core.lock().notify(task.target, response);
109 }
110 });
111
112 Self {
113 core: core_clone,
114 task_tx,
115 version_generator: Mutex::new(version_generator),
116 }
117 }
118
119 pub fn abort_all(&self) {
120 let mut guard = self.core.lock();
121 *guard = NotificationManagerCore::new();
122 guard.exiting = true;
123 }
124
125 #[inline(always)]
126 fn notify(
127 &self,
128 target: Target,
129 operation: Operation,
130 info: Info,
131 version: Option<NotificationVersion>,
132 ) {
133 let task = Task {
134 target,
135 operation,
136 info,
137 version,
138 };
139 self.task_tx.send(task).unwrap();
140 }
141
142 async fn notify_with_version(
144 &self,
145 target: Target,
146 operation: Operation,
147 info: Info,
148 ) -> NotificationVersion {
149 let mut version_guard = self.version_generator.lock().await;
150 version_guard.increase_version().await;
151 let version = version_guard.current_version();
152 self.notify(target, operation, info, Some(version));
153 version
154 }
155
156 #[inline(always)]
158 fn notify_without_version(&self, target: Target, operation: Operation, info: Info) {
159 self.notify(target, operation, info, None);
160 }
161
162 pub fn notify_snapshot(
163 &self,
164 worker_key: WorkerKey,
165 subscribe_type: SubscribeType,
166 meta_snapshot: MetaSnapshot,
167 ) {
168 self.notify_without_version(
169 Target {
170 subscribe_type,
171 worker_key: Some(worker_key),
172 },
173 Operation::Snapshot,
174 Info::Snapshot(meta_snapshot),
175 )
176 }
177
178 pub fn notify_all_without_version(&self, operation: Operation, info: Info) {
179 for subscribe_type in [
180 SubscribeType::Frontend,
181 SubscribeType::Hummock,
182 SubscribeType::Compactor,
183 SubscribeType::Compute,
184 ] {
185 self.notify_without_version(subscribe_type.into(), operation, info.clone());
186 }
187 }
188
189 pub async fn notify_frontend(&self, operation: Operation, info: Info) -> NotificationVersion {
190 self.notify_with_version(SubscribeType::Frontend.into(), operation, info)
191 .await
192 }
193
194 pub async fn notify_frontend_object_info(
195 &self,
196 operation: Operation,
197 object_info: PbObjectInfo,
198 ) -> NotificationVersion {
199 self.notify_with_version(
200 SubscribeType::Frontend.into(),
201 operation,
202 Info::ObjectGroup(PbObjectGroup {
203 objects: vec![PbObject {
204 object_info: object_info.into(),
205 }],
206 dependencies: vec![],
207 }),
208 )
209 .await
210 }
211
212 pub async fn notify_hummock(&self, operation: Operation, info: Info) -> NotificationVersion {
213 self.notify_with_version(SubscribeType::Hummock.into(), operation, info)
214 .await
215 }
216
217 pub(crate) async fn notify_hummock_targeted_update(
218 &self,
219 worker_key: WorkerKey,
220 info: Info,
221 ) -> NotificationVersion {
222 self.notify_with_version(
223 Target {
224 subscribe_type: SubscribeType::Hummock,
225 worker_key: Some(worker_key),
226 },
227 Operation::Update,
228 info,
229 )
230 .await
231 }
232
233 pub async fn notify_compactor(&self, operation: Operation, info: Info) -> NotificationVersion {
234 self.notify_with_version(SubscribeType::Compactor.into(), operation, info)
235 .await
236 }
237
238 pub fn notify_compute_without_version(&self, operation: Operation, info: Info) {
239 self.notify_without_version(SubscribeType::Compute.into(), operation, info)
240 }
241
242 pub fn notify_frontend_without_version(&self, operation: Operation, info: Info) {
243 self.notify_without_version(SubscribeType::Frontend.into(), operation, info)
244 }
245
246 pub fn notify_hummock_without_version(&self, operation: Operation, info: Info) {
247 self.notify_without_version(SubscribeType::Hummock.into(), operation, info)
248 }
249
250 pub fn notify_compactor_without_version(&self, operation: Operation, info: Info) {
251 self.notify_without_version(SubscribeType::Compactor.into(), operation, info)
252 }
253
254 #[cfg(any(test, feature = "test"))]
255 pub fn notify_hummock_with_version(
256 &self,
257 operation: Operation,
258 info: Info,
259 version: Option<NotificationVersion>,
260 ) {
261 self.notify(SubscribeType::Hummock.into(), operation, info, version)
262 }
263
264 pub fn notify_local_subscribers(&self, notification: LocalNotification) {
265 let mut core_guard = self.core.lock();
266 core_guard.local_senders.retain(|sender| {
267 if let Err(err) = sender.send(notification.clone()) {
268 tracing::warn!(error = %err.as_report(), "Failed to notify local subscriber");
269 return false;
270 }
271 true
272 });
273 }
274
275 pub fn delete_sender(&self, worker_type: WorkerType, worker_key: WorkerKey) {
277 let mut core_guard = self.core.lock();
278 match worker_type {
281 WorkerType::Frontend => core_guard.frontend_senders.remove(&worker_key),
282 WorkerType::ComputeNode | WorkerType::RiseCtl => {
283 core_guard.hummock_senders.remove(&worker_key)
284 }
285 WorkerType::Compactor => core_guard.compactor_senders.remove(&worker_key),
286 _ => unreachable!(),
287 };
288 }
289
290 pub fn insert_sender(
292 &self,
293 subscribe_type: SubscribeType,
294 worker_key: WorkerKey,
295 sender: UnboundedSender<Notification>,
296 ) {
297 let mut core_guard = self.core.lock();
298 if core_guard.exiting {
299 tracing::warn!("notification manager exiting.");
300 return;
301 }
302 let senders = core_guard.senders_of(subscribe_type);
303
304 senders.insert(worker_key, sender);
305 }
306
307 pub fn insert_local_sender(&self, sender: UnboundedSender<LocalNotification>) {
308 let mut core_guard = self.core.lock();
309 if core_guard.exiting {
310 tracing::warn!("notification manager exiting.");
311 return;
312 }
313 core_guard.local_senders.push(sender);
314 }
315
316 #[cfg(test)]
317 pub fn clear_local_sender(&self) {
318 self.core.lock().local_senders.clear();
319 }
320
321 pub async fn current_version(&self) -> NotificationVersion {
322 let version_guard = self.version_generator.lock().await;
323 version_guard.current_version()
324 }
325}
326
327type SenderMap = HashMap<WorkerKey, UnboundedSender<Notification>>;
328
329struct NotificationManagerCore {
330 frontend_senders: SenderMap,
332 hummock_senders: SenderMap,
334 compactor_senders: SenderMap,
336 compute_senders: SenderMap,
338 local_senders: Vec<UnboundedSender<LocalNotification>>,
340 exiting: bool,
341}
342
343impl NotificationManagerCore {
344 fn new() -> Self {
345 Self {
346 frontend_senders: HashMap::new(),
347 hummock_senders: HashMap::new(),
348 compactor_senders: HashMap::new(),
349 compute_senders: HashMap::new(),
350 local_senders: vec![],
351 exiting: false,
352 }
353 }
354
355 fn notify(&mut self, target: Target, response: SubscribeResponse) {
356 macro_rules! warn_send_failure {
357 ($subscribe_type:expr, $worker_key:expr, $err:expr) => {
358 tracing::warn!(
359 "Failed to notify {:?} {:?}: {}",
360 $subscribe_type,
361 $worker_key,
362 $err
363 );
364 };
365 }
366
367 let senders = self.senders_of(target.subscribe_type);
368
369 if let Some(worker_key) = target.worker_key {
370 match senders.entry(worker_key.clone()) {
371 Entry::Occupied(entry) => {
372 let _ = entry.get().send(Ok(response)).inspect_err(|err| {
373 warn_send_failure!(target.subscribe_type, &worker_key, err.as_report());
374 entry.remove_entry();
375 });
376 }
377 Entry::Vacant(_) => {
378 tracing::warn!("Failed to find notification sender of {:?}", worker_key)
379 }
380 }
381 } else {
382 senders.retain(|worker_key, sender| {
383 sender
384 .send(Ok(response.clone()))
385 .inspect_err(|err| {
386 warn_send_failure!(target.subscribe_type, &worker_key, err.as_report());
387 })
388 .is_ok()
389 });
390 }
391 }
392
393 fn senders_of(&mut self, subscribe_type: SubscribeType) -> &mut SenderMap {
394 match subscribe_type {
395 SubscribeType::Frontend => &mut self.frontend_senders,
396 SubscribeType::Hummock => &mut self.hummock_senders,
397 SubscribeType::Compactor => &mut self.compactor_senders,
398 SubscribeType::Compute => &mut self.compute_senders,
399 SubscribeType::Unspecified => unreachable!(),
400 }
401 }
402}
403
404#[cfg(test)]
405mod tests {
406 use risingwave_common::id::JobId;
407 use risingwave_pb::common::HostAddress;
408
409 use super::*;
410 use crate::manager::WorkerKey;
411
412 #[tokio::test]
413 async fn test_multiple_subscribers_one_worker() {
414 let mgr = NotificationManager::new(SqlMetaStore::for_test().await).await;
415 let worker_key1 = WorkerKey(HostAddress {
416 host: "a".to_owned(),
417 port: 1,
418 });
419 let worker_key2 = WorkerKey(HostAddress {
420 host: "a".to_owned(),
421 port: 2,
422 });
423 let (tx1, mut rx1) = mpsc::unbounded_channel();
424 let (tx2, mut rx2) = mpsc::unbounded_channel();
425 let (tx3, mut rx3) = mpsc::unbounded_channel();
426 mgr.insert_sender(SubscribeType::Hummock, worker_key1.clone(), tx1);
427 mgr.insert_sender(SubscribeType::Frontend, worker_key1.clone(), tx2);
428 mgr.insert_sender(SubscribeType::Frontend, worker_key2, tx3);
429 mgr.notify_snapshot(
430 worker_key1.clone(),
431 SubscribeType::Hummock,
432 MetaSnapshot::default(),
433 );
434 assert!(rx1.recv().await.is_some());
435 assert!(rx2.try_recv().is_err());
436 assert!(rx3.try_recv().is_err());
437
438 mgr.notify_frontend(Operation::Add, Info::Database(Default::default()))
439 .await;
440 assert!(rx1.try_recv().is_err());
441 assert!(rx2.recv().await.is_some());
442 assert!(rx3.recv().await.is_some());
443 }
444
445 #[tokio::test]
446 async fn test_notify_hummock_targeted_update() {
447 let mgr = NotificationManager::new(SqlMetaStore::for_test().await).await;
448 let worker_key1 = WorkerKey(HostAddress {
449 host: "a".to_owned(),
450 port: 1,
451 });
452 let worker_key2 = WorkerKey(HostAddress {
453 host: "a".to_owned(),
454 port: 2,
455 });
456 let (hummock_tx1, mut hummock_rx1) = mpsc::unbounded_channel();
457 let (hummock_tx2, mut hummock_rx2) = mpsc::unbounded_channel();
458 let (frontend_tx1, mut frontend_rx1) = mpsc::unbounded_channel();
459 mgr.insert_sender(SubscribeType::Hummock, worker_key1.clone(), hummock_tx1);
460 mgr.insert_sender(SubscribeType::Hummock, worker_key2.clone(), hummock_tx2);
461 mgr.insert_sender(SubscribeType::Frontend, worker_key1.clone(), frontend_tx1);
462
463 mgr.notify_hummock_targeted_update(worker_key1, Info::Database(Default::default()))
464 .await;
465
466 assert!(hummock_rx1.recv().await.is_some());
467 assert!(hummock_rx2.try_recv().is_err());
468 assert!(frontend_rx1.try_recv().is_err());
469 }
470
471 #[tokio::test]
472 async fn test_local_notification_backfill_finished() {
473 let mgr = NotificationManager::new(SqlMetaStore::for_test().await).await;
474 let (tx, mut rx) = mpsc::unbounded_channel();
475 mgr.insert_local_sender(tx);
476
477 let job_id = JobId::new(42);
478 mgr.notify_local_subscribers(LocalNotification::StreamingJobBackfillFinished(job_id));
479
480 match rx.recv().await.expect("should receive notification") {
481 LocalNotification::StreamingJobBackfillFinished(received) => {
482 assert_eq!(received, job_id);
483 }
484 other => panic!("unexpected notification: {other:?}"),
485 }
486 }
487}