Skip to main content

risingwave_common_service/
observer_manager.rs

1// Copyright 2022 RisingWave Labs
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::time::Duration;
16
17use risingwave_pb::meta::subscribe_response::Info;
18use risingwave_pb::meta::{SubscribeResponse, SubscribeType};
19use risingwave_rpc_client::MetaClient;
20use risingwave_rpc_client::error::RpcError;
21use thiserror_ext::AsReport;
22use tokio::task::JoinHandle;
23use tonic::{Status, Streaming};
24
25/// `ObserverManager` is used to update data based on notification from meta.
26/// Call `start` to spawn a new asynchronous task
27/// We can write the notification logic by implementing `ObserverNodeImpl`.
28pub struct ObserverManager<T: NotificationClient, S: ObserverState> {
29    rx: T::Channel,
30    client: T,
31    observer_states: S,
32}
33
34pub trait ObserverState: Send + 'static {
35    fn subscribe_type() -> SubscribeType;
36    /// modify data after receiving notification from meta
37    fn handle_notification(&mut self, resp: SubscribeResponse);
38
39    /// Initialize data from the meta. It will be called at start or resubscribe
40    fn handle_initialization_notification(&mut self, resp: SubscribeResponse);
41}
42
43impl<S: ObserverState> ObserverManager<RpcNotificationClient, S> {
44    pub async fn new_with_meta_client(meta_client: MetaClient, observer_states: S) -> Self {
45        let client = RpcNotificationClient { meta_client };
46        Self::new(client, observer_states).await
47    }
48}
49
50/// Error type for [`ObserverManager`].
51#[derive(thiserror::Error, Debug)]
52pub enum ObserverError {
53    #[error("notification channel closed")]
54    ChannelClosed,
55
56    #[error(transparent)]
57    Rpc(
58        #[from]
59        #[backtrace]
60        RpcError,
61    ),
62}
63
64impl From<tonic::Status> for ObserverError {
65    fn from(status: tonic::Status) -> Self {
66        Self::Rpc(RpcError::from_meta_status(status))
67    }
68}
69
70impl<T, S> ObserverManager<T, S>
71where
72    T: NotificationClient,
73    S: ObserverState,
74{
75    pub async fn new(client: T, observer_states: S) -> Self {
76        let rx = client.subscribe(S::subscribe_type()).await.unwrap();
77        Self {
78            rx,
79            client,
80            observer_states,
81        }
82    }
83
84    /// `wait_init_notification` is used to wait for the initial notification from meta
85    /// and process the buffered notifications before the initial notification if needed.
86    async fn wait_init_notification(&mut self) -> Result<(), ObserverError> {
87        let mut notification_vec = Vec::new();
88        let init_notification = loop {
89            // notification before init notification must be received successfully.
90            match self.rx.message().await? {
91                Some(notification) => {
92                    if !matches!(notification.info.as_ref().unwrap(), &Info::Snapshot(_)) {
93                        notification_vec.push(notification);
94                    } else {
95                        break notification;
96                    }
97                }
98                None => return Err(ObserverError::ChannelClosed),
99            }
100        };
101
102        let Info::Snapshot(info) = init_notification.info.as_ref().unwrap() else {
103            unreachable!();
104        };
105
106        notification_vec.retain_mut(|notification| match notification.info.as_ref().unwrap() {
107            Info::Database(_)
108            | Info::Schema(_)
109            | Info::ObjectGroup(_)
110            | Info::User(_)
111            | Info::Connection(_)
112            | Info::Secret(_)
113            | Info::Function(_) => {
114                notification.version > info.version.as_ref().unwrap().catalog_version
115            }
116            Info::Node(_) => {
117                notification.version > info.version.as_ref().unwrap().worker_node_version
118            }
119            Info::HummockVersionDeltas(version_delta) => {
120                version_delta.version_deltas[0].id > info.hummock_version.as_ref().unwrap().id
121            }
122            Info::MetaBackupManifestId(_) => true,
123            Info::SystemParams(_) | Info::SessionParam(_) => true,
124            Info::Snapshot(_) | Info::HummockWriteLimits(_) => unreachable!(),
125            Info::HummockStats(_) => true,
126            Info::Recovery(_) => true,
127            Info::ClusterResource(_) => true,
128            Info::StreamingWorkerSlotMapping(_) => {
129                notification.version
130                    > info
131                        .version
132                        .as_ref()
133                        .unwrap()
134                        .streaming_worker_slot_mapping_version
135            }
136            Info::ServingWorkerSlotMappings(_) => true,
137            Info::TableRefillRuntimeConfig(_) => {
138                notification.version
139                    > info
140                        .table_refill_runtime_config
141                        .as_ref()
142                        .map(|config| config.version)
143                        .unwrap_or_default()
144            }
145        });
146
147        self.observer_states
148            .handle_initialization_notification(init_notification);
149
150        for notification in notification_vec {
151            self.observer_states.handle_notification(notification);
152        }
153
154        Ok(())
155    }
156
157    /// `start` is used to spawn a new asynchronous task which receives meta's notification and
158    /// call the `handle_initialization_notification` and `handle_notification` to update node data.
159    pub async fn start(mut self) -> JoinHandle<()> {
160        if let Err(err) = self.wait_init_notification().await {
161            tracing::warn!(error = %err.as_report(), "Receives meta's notification err");
162            self.re_subscribe().await;
163        }
164
165        tokio::spawn(async move {
166            loop {
167                match self.rx.message().await {
168                    Ok(resp) => {
169                        if resp.is_none() {
170                            tracing::warn!("Stream of notification terminated.");
171                            self.re_subscribe().await;
172                            continue;
173                        }
174                        self.observer_states.handle_notification(resp.unwrap());
175                    }
176                    Err(err) => {
177                        tracing::warn!(error = %err.as_report(), "Receives meta's notification err");
178                        self.re_subscribe().await;
179                    }
180                }
181            }
182        })
183    }
184
185    /// `re_subscribe` is used to re-subscribe to the meta's notification.
186    async fn re_subscribe(&mut self) {
187        loop {
188            match self.client.subscribe(S::subscribe_type()).await {
189                Ok(rx) => {
190                    tracing::debug!("re-subscribe success");
191                    self.rx = rx;
192                    match self.wait_init_notification().await {
193                        Err(err) => {
194                            tracing::warn!(error = %err.as_report(), "Receives meta's notification err");
195                            tokio::time::sleep(RE_SUBSCRIBE_RETRY_INTERVAL).await;
196                            continue;
197                        }
198                        _ => {
199                            break;
200                        }
201                    }
202                }
203                Err(_) => {
204                    tokio::time::sleep(RE_SUBSCRIBE_RETRY_INTERVAL).await;
205                }
206            }
207        }
208    }
209}
210
211const RE_SUBSCRIBE_RETRY_INTERVAL: Duration = Duration::from_millis(100);
212
213#[async_trait::async_trait]
214pub trait Channel: Send + 'static {
215    type Item;
216    async fn message(&mut self) -> std::result::Result<Option<Self::Item>, Status>;
217}
218
219#[async_trait::async_trait]
220impl<T: Send + 'static> Channel for Streaming<T> {
221    type Item = T;
222
223    async fn message(&mut self) -> std::result::Result<Option<T>, Status> {
224        self.message().await
225    }
226}
227
228#[async_trait::async_trait]
229pub trait NotificationClient: Send + Sync + 'static {
230    type Channel: Channel<Item = SubscribeResponse>;
231    async fn subscribe(
232        &self,
233        subscribe_type: SubscribeType,
234    ) -> Result<Self::Channel, ObserverError>;
235}
236
237pub struct RpcNotificationClient {
238    meta_client: MetaClient,
239}
240
241impl RpcNotificationClient {
242    pub fn new(meta_client: MetaClient) -> Self {
243        Self { meta_client }
244    }
245}
246
247#[async_trait::async_trait]
248impl NotificationClient for RpcNotificationClient {
249    type Channel = Streaming<SubscribeResponse>;
250
251    async fn subscribe(
252        &self,
253        subscribe_type: SubscribeType,
254    ) -> Result<Self::Channel, ObserverError> {
255        self.meta_client
256            .subscribe(subscribe_type)
257            .await
258            .map_err(Into::into)
259    }
260}
261
262#[cfg(test)]
263mod tests {
264    use std::collections::VecDeque;
265
266    use risingwave_pb::meta::meta_snapshot::SnapshotVersion;
267    use risingwave_pb::meta::subscribe_response::Info;
268    use risingwave_pb::meta::{
269        MetaSnapshot, SubscribeResponse, SubscribeType, TableRefillRuntimeConfig,
270    };
271
272    use super::*;
273
274    struct TestChannel {
275        messages: VecDeque<SubscribeResponse>,
276    }
277
278    #[async_trait::async_trait]
279    impl Channel for TestChannel {
280        type Item = SubscribeResponse;
281
282        async fn message(&mut self) -> std::result::Result<Option<Self::Item>, Status> {
283            Ok(self.messages.pop_front())
284        }
285    }
286
287    struct TestClient;
288
289    #[async_trait::async_trait]
290    impl NotificationClient for TestClient {
291        type Channel = TestChannel;
292
293        async fn subscribe(
294            &self,
295            _subscribe_type: SubscribeType,
296        ) -> Result<Self::Channel, ObserverError> {
297            Ok(TestChannel {
298                messages: VecDeque::new(),
299            })
300        }
301    }
302
303    #[derive(Default)]
304    struct TestObserverState {
305        initialized: bool,
306        table_refill_runtime_config_versions: Vec<u64>,
307        database_notifications: usize,
308    }
309
310    impl ObserverState for TestObserverState {
311        fn subscribe_type() -> SubscribeType {
312            SubscribeType::Hummock
313        }
314
315        fn handle_notification(&mut self, resp: SubscribeResponse) {
316            match resp.info.unwrap() {
317                Info::TableRefillRuntimeConfig(_) => {
318                    self.table_refill_runtime_config_versions.push(resp.version)
319                }
320                Info::Database(_) => self.database_notifications += 1,
321                info => panic!("unexpected notification: {info:?}"),
322            }
323        }
324
325        fn handle_initialization_notification(&mut self, resp: SubscribeResponse) {
326            assert!(matches!(resp.info, Some(Info::Snapshot(_))));
327            self.initialized = true;
328        }
329    }
330
331    fn notification(version: u64, info: Info) -> SubscribeResponse {
332        SubscribeResponse {
333            info: Some(info),
334            version,
335            ..Default::default()
336        }
337    }
338
339    #[tokio::test]
340    async fn test_filter_stale_table_refill_runtime_config_before_snapshot() {
341        let snapshot = SubscribeResponse {
342            info: Some(Info::Snapshot(MetaSnapshot {
343                version: Some(SnapshotVersion {
344                    catalog_version: 1,
345                    worker_node_version: 1,
346                    streaming_worker_slot_mapping_version: 1,
347                }),
348                table_refill_runtime_config: Some(TableRefillRuntimeConfig {
349                    version: 10,
350                    ..Default::default()
351                }),
352                ..Default::default()
353            })),
354            ..Default::default()
355        };
356        let rx = TestChannel {
357            messages: VecDeque::from([
358                notification(
359                    9,
360                    Info::TableRefillRuntimeConfig(TableRefillRuntimeConfig {
361                        version: 9,
362                        ..Default::default()
363                    }),
364                ),
365                notification(
366                    11,
367                    Info::TableRefillRuntimeConfig(TableRefillRuntimeConfig {
368                        version: 11,
369                        ..Default::default()
370                    }),
371                ),
372                notification(1, Info::Database(Default::default())),
373                snapshot,
374            ]),
375        };
376        let mut observer_manager = ObserverManager {
377            rx,
378            client: TestClient,
379            observer_states: TestObserverState::default(),
380        };
381
382        observer_manager.wait_init_notification().await.unwrap();
383
384        assert!(observer_manager.observer_states.initialized);
385        assert_eq!(
386            observer_manager
387                .observer_states
388                .table_refill_runtime_config_versions,
389            vec![11]
390        );
391        assert_eq!(observer_manager.observer_states.database_notifications, 0);
392    }
393}