Skip to main content

flow/adapter/
worker.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
15//! For single-thread flow worker
16
17use std::collections::{BTreeMap, VecDeque};
18use std::sync::Arc;
19use std::sync::atomic::{AtomicBool, Ordering};
20
21use common_telemetry::info;
22use dfir_rs::scheduled::graph::Dfir;
23use enum_as_inner::EnumAsInner;
24use snafu::ensure;
25use tokio::sync::{Mutex, broadcast, mpsc, oneshot};
26
27use crate::adapter::FlowId;
28use crate::compute::{Context, DataflowState, ErrCollector};
29use crate::error::{Error, FlowAlreadyExistSnafu, InternalSnafu, UnexpectedSnafu};
30use crate::expr::{Batch, GlobalId};
31use crate::plan::TypedPlan;
32use crate::repr::{self, DiffRow};
33
34pub type SharedBuf = Arc<Mutex<VecDeque<DiffRow>>>;
35
36type ReqId = usize;
37
38/// Create both worker(`!Send`) and worker handle(`Send + Sync`)
39pub fn create_worker<'a>() -> (WorkerHandle, Worker<'a>) {
40    let (itc_client, itc_server) = create_inter_thread_call();
41    let worker_handle = WorkerHandle {
42        itc_client,
43        shutdown: AtomicBool::new(false),
44    };
45    let worker = Worker {
46        task_states: BTreeMap::new(),
47        itc_server: Arc::new(Mutex::new(itc_server)),
48    };
49    (worker_handle, worker)
50}
51
52/// ActiveDataflowState is a wrapper around `Dfir` and `DataflowState`
53pub(crate) struct ActiveDataflowState<'subgraph> {
54    df: Dfir<'subgraph>,
55    state: DataflowState,
56    err_collector: ErrCollector,
57}
58
59impl std::fmt::Debug for ActiveDataflowState<'_> {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        f.debug_struct("ActiveDataflowState")
62            .field("df", &"<Dfir>")
63            .field("state", &self.state)
64            .field("err_collector", &self.err_collector)
65            .finish()
66    }
67}
68
69impl Default for ActiveDataflowState<'_> {
70    fn default() -> Self {
71        ActiveDataflowState {
72            df: Dfir::new(),
73            state: DataflowState::default(),
74            err_collector: ErrCollector::default(),
75        }
76    }
77}
78
79impl<'subgraph> ActiveDataflowState<'subgraph> {
80    /// Create a new render context, assigned with given global id
81    pub fn new_ctx<'ctx>(&'ctx mut self, global_id: GlobalId) -> Context<'ctx, 'subgraph>
82    where
83        'subgraph: 'ctx,
84    {
85        Context {
86            id: global_id,
87            df: &mut self.df,
88            compute_state: &mut self.state,
89            err_collector: self.err_collector.clone(),
90            input_collection: Default::default(),
91            local_scope: Default::default(),
92            input_collection_batch: Default::default(),
93            local_scope_batch: Default::default(),
94        }
95    }
96
97    pub fn set_current_ts(&mut self, ts: repr::Timestamp) {
98        self.state.set_current_ts(ts);
99    }
100
101    pub fn set_last_exec_time(&mut self, ts: repr::Timestamp) {
102        self.state.set_last_exec_time(ts);
103    }
104
105    /// Run all available subgraph
106    ///
107    /// return true if any subgraph actually executed
108    pub fn run_available(&mut self) -> bool {
109        self.state.run_available_with_schedule(&mut self.df)
110    }
111}
112
113#[derive(Debug)]
114pub struct WorkerHandle {
115    itc_client: InterThreadCallClient,
116    shutdown: AtomicBool,
117}
118
119impl WorkerHandle {
120    /// create task, return task id
121    pub async fn create_flow(&self, create_reqs: Request) -> Result<Option<FlowId>, Error> {
122        ensure!(
123            matches!(create_reqs, Request::Create { .. }),
124            InternalSnafu {
125                reason: format!(
126                    "Flow Node/Worker itc failed, expect Request::Create, found {create_reqs:?}"
127                ),
128            }
129        );
130
131        let ret = self.itc_client.call_with_resp(create_reqs).await?;
132        ret.into_create().map_err(|ret| {
133            InternalSnafu {
134                reason: format!(
135                    "Flow Node/Worker itc failed, expect Response::Create, found {ret:?}"
136                ),
137            }
138            .build()
139        })?
140    }
141
142    /// remove task, return task id
143    pub async fn remove_flow(&self, flow_id: FlowId) -> Result<bool, Error> {
144        let req = Request::Remove { flow_id };
145
146        let ret = self.itc_client.call_with_resp(req).await?;
147
148        ret.into_remove().map_err(|ret| {
149            InternalSnafu {
150                reason: format!("Flow Node/Worker failed, expect Response::Remove, found {ret:?}"),
151            }
152            .build()
153        })
154    }
155
156    /// trigger running the worker, will not block, and will run the worker parallelly
157    ///
158    /// will set the current timestamp to `now` for all dataflows before running them
159    ///
160    /// `blocking` indicate whether it will wait til all dataflows are finished computing if true or
161    /// just start computing and return immediately if false
162    ///
163    /// the returned error is unrecoverable, and the worker should be shutdown/rebooted
164    pub async fn run_available(&self, now: repr::Timestamp, blocking: bool) -> Result<(), Error> {
165        common_telemetry::trace!("Running available with blocking={}", blocking);
166        if blocking {
167            let resp = self
168                .itc_client
169                .call_with_resp(Request::RunAvail { now, blocking })
170                .await?;
171            common_telemetry::trace!("Running available with response={:?}", resp);
172            Ok(())
173        } else {
174            self.itc_client
175                .call_no_resp(Request::RunAvail { now, blocking })
176        }
177    }
178
179    pub async fn contains_flow(&self, flow_id: FlowId) -> Result<bool, Error> {
180        let req = Request::ContainTask { flow_id };
181        let ret = self.itc_client.call_with_resp(req).await?;
182
183        ret.into_contain_task().map_err(|ret| {
184            InternalSnafu {
185                reason: format!(
186                    "Flow Node/Worker itc failed, expect Response::ContainTask, found {ret:?}"
187                ),
188            }
189            .build()
190        })
191    }
192
193    /// shutdown the worker
194    pub fn shutdown(&self) -> Result<(), Error> {
195        if !self.shutdown.fetch_or(true, Ordering::SeqCst) {
196            self.itc_client.call_no_resp(Request::Shutdown)
197        } else {
198            UnexpectedSnafu {
199                reason: "Worker already shutdown",
200            }
201            .fail()
202        }
203    }
204
205    pub async fn get_full_flow_stat(
206        &self,
207    ) -> Result<
208        (
209            BTreeMap<FlowId, usize>,
210            BTreeMap<FlowId, i64>,
211            BTreeMap<FlowId, i64>,
212        ),
213        Error,
214    > {
215        let ret = self
216            .itc_client
217            .call_with_resp(Request::QueryFullFlowStat)
218            .await?;
219        ret.into_query_full_flow_stat().map_err(|ret| {
220            InternalSnafu {
221                reason: format!(
222                    "Flow Node/Worker get_full_flow_stat failed, expected Response::QueryFullFlowStat, found {ret:?}"
223                ),
224            }
225            .build()
226        })
227    }
228}
229
230impl Drop for WorkerHandle {
231    fn drop(&mut self) {
232        if let Err(ret) = self.shutdown() {
233            common_telemetry::error!(
234                ret;
235                "While dropping Worker Handle, failed to shutdown worker, worker might be in inconsistent state."
236            );
237        } else {
238            info!("Flow Worker shutdown due to Worker Handle dropped.")
239        }
240    }
241}
242
243/// The actual worker that does the work and contain active state
244#[derive(Debug)]
245pub struct Worker<'subgraph> {
246    /// Task states
247    pub(crate) task_states: BTreeMap<FlowId, ActiveDataflowState<'subgraph>>,
248    itc_server: Arc<Mutex<InterThreadCallServer>>,
249}
250
251impl<'s> Worker<'s> {
252    #[allow(clippy::too_many_arguments)]
253    pub fn create_flow(
254        &mut self,
255        flow_id: FlowId,
256        plan: TypedPlan,
257        sink_id: GlobalId,
258        sink_sender: mpsc::UnboundedSender<Batch>,
259        source_ids: &[GlobalId],
260        src_recvs: Vec<broadcast::Receiver<Batch>>,
261        // TODO(discord9): set expire duration for all arrangement and compare to sys timestamp instead
262        expire_after: Option<repr::Duration>,
263        or_replace: bool,
264        create_if_not_exists: bool,
265        err_collector: ErrCollector,
266    ) -> Result<Option<FlowId>, Error> {
267        let already_exist = self.task_states.contains_key(&flow_id);
268        match (create_if_not_exists, or_replace, already_exist) {
269            // if replace, ignore that old flow exists
270            (_, true, true) => {
271                info!("Replacing flow with id={}", flow_id);
272            }
273            (false, false, true) => FlowAlreadyExistSnafu { id: flow_id }.fail()?,
274            // already exists, and not replace, return None
275            (true, false, true) => {
276                info!("Flow with id={} already exists, do nothing", flow_id);
277                return Ok(None);
278            }
279            // continue as normal
280            (_, _, false) => (),
281        }
282
283        let mut cur_task_state = ActiveDataflowState::<'s> {
284            err_collector,
285            ..Default::default()
286        };
287        cur_task_state.state.set_expire_after(expire_after);
288
289        {
290            let mut ctx = cur_task_state.new_ctx(sink_id);
291            for (source_id, src_recv) in source_ids.iter().zip(src_recvs) {
292                let bundle = ctx.render_source_batch(src_recv)?;
293                ctx.insert_global_batch(*source_id, bundle);
294            }
295
296            let rendered = ctx.render_plan_batch(plan)?;
297            ctx.render_unbounded_sink_batch(rendered, sink_sender);
298        }
299        self.task_states.insert(flow_id, cur_task_state);
300        Ok(Some(flow_id))
301    }
302
303    /// remove task, return true if a task is removed
304    pub fn remove_flow(&mut self, flow_id: FlowId) -> bool {
305        self.task_states.remove(&flow_id).is_some()
306    }
307
308    /// Run the worker, blocking, until shutdown signal is received
309    pub fn run(&mut self) {
310        loop {
311            let (req, ret_tx) = if let Some(ret) = self.itc_server.blocking_lock().blocking_recv() {
312                ret
313            } else {
314                common_telemetry::error!(
315                    "Worker's itc server has been closed unexpectedly, shutting down worker now."
316                );
317                break;
318            };
319
320            let ret = self.handle_req(req);
321            match (ret, ret_tx) {
322                (Ok(Some(resp)), Some(ret_tx)) => {
323                    if let Err(err) = ret_tx.send(resp) {
324                        common_telemetry::error!(
325                            err;
326                            "Result receiver is dropped, can't send result"
327                        );
328                    };
329                }
330                (Ok(None), None) => continue,
331                (Ok(Some(resp)), None) => {
332                    common_telemetry::error!(
333                        "Expect no result for current request, but found {resp:?}"
334                    )
335                }
336                (Ok(None), Some(_)) => {
337                    common_telemetry::error!("Expect result for current request, but found nothing")
338                }
339                (Err(()), _) => {
340                    break;
341                }
342            }
343        }
344    }
345
346    /// run with tick acquired from tick manager(usually means system time)
347    /// TODO(discord9): better tick management
348    pub fn run_tick(&mut self, now: repr::Timestamp) {
349        for (_flow_id, task_state) in self.task_states.iter_mut() {
350            task_state.set_current_ts(now);
351            task_state.set_last_exec_time(now);
352            task_state.run_available();
353        }
354    }
355    /// handle request, return response if any, Err if receive shutdown signal
356    ///
357    /// return `Err(())` if receive shutdown request
358    fn handle_req(&mut self, req: Request) -> Result<Option<Response>, ()> {
359        let ret = match req {
360            Request::Create {
361                flow_id,
362                plan,
363                sink_id,
364                sink_sender,
365                source_ids,
366                src_recvs,
367                expire_after,
368                or_replace,
369                create_if_not_exists,
370                err_collector,
371            } => {
372                let task_create_result = self.create_flow(
373                    flow_id,
374                    plan,
375                    sink_id,
376                    sink_sender,
377                    &source_ids,
378                    src_recvs,
379                    expire_after,
380                    or_replace,
381                    create_if_not_exists,
382                    err_collector,
383                );
384                Some(Response::Create {
385                    result: task_create_result,
386                })
387            }
388            Request::Remove { flow_id } => {
389                let ret = self.remove_flow(flow_id);
390                Some(Response::Remove { result: ret })
391            }
392            Request::RunAvail { now, blocking } => {
393                self.run_tick(now);
394                if blocking {
395                    Some(Response::RunAvail)
396                } else {
397                    None
398                }
399            }
400            Request::ContainTask { flow_id } => {
401                let ret = self.task_states.contains_key(&flow_id);
402                Some(Response::ContainTask { result: ret })
403            }
404            Request::Shutdown => return Err(()),
405            Request::QueryFullFlowStat => {
406                let mut state_size = BTreeMap::new();
407                let mut last_exec_time_map = BTreeMap::new();
408                let mut start_time_map = BTreeMap::new();
409                for (flow_id, task_state) in self.task_states.iter() {
410                    state_size.insert(*flow_id, task_state.state.get_state_size());
411                    if let Some(t) = task_state.state.last_exec_time() {
412                        last_exec_time_map.insert(*flow_id, t);
413                    }
414                    if let Some(t) = task_state.state.start_time() {
415                        start_time_map.insert(*flow_id, t);
416                    }
417                }
418                Some(Response::QueryFullFlowStat {
419                    state_size,
420                    last_exec_time_map,
421                    start_time_map,
422                })
423            }
424        };
425        Ok(ret)
426    }
427}
428
429#[derive(Debug, EnumAsInner)]
430pub enum Request {
431    Create {
432        flow_id: FlowId,
433        plan: TypedPlan,
434        sink_id: GlobalId,
435        sink_sender: mpsc::UnboundedSender<Batch>,
436        source_ids: Vec<GlobalId>,
437        src_recvs: Vec<broadcast::Receiver<Batch>>,
438        expire_after: Option<repr::Duration>,
439        or_replace: bool,
440        create_if_not_exists: bool,
441        err_collector: ErrCollector,
442    },
443    Remove {
444        flow_id: FlowId,
445    },
446    /// Trigger the worker to run, useful after input buffer is full
447    RunAvail {
448        now: repr::Timestamp,
449        blocking: bool,
450    },
451    ContainTask {
452        flow_id: FlowId,
453    },
454    Shutdown,
455    QueryFullFlowStat,
456}
457
458#[derive(Debug, EnumAsInner)]
459enum Response {
460    Create {
461        result: Result<Option<FlowId>, Error>,
462        // TODO(discord9): add flow err_collector
463    },
464    Remove {
465        result: bool,
466    },
467    ContainTask {
468        result: bool,
469    },
470    RunAvail,
471    QueryFullFlowStat {
472        state_size: BTreeMap<FlowId, usize>,
473        last_exec_time_map: BTreeMap<FlowId, i64>,
474        start_time_map: BTreeMap<FlowId, i64>,
475    },
476}
477
478fn create_inter_thread_call() -> (InterThreadCallClient, InterThreadCallServer) {
479    let (arg_send, arg_recv) = mpsc::unbounded_channel();
480    let client = InterThreadCallClient {
481        arg_sender: arg_send,
482    };
483    let server = InterThreadCallServer { arg_recv };
484    (client, server)
485}
486
487#[derive(Debug)]
488struct InterThreadCallClient {
489    arg_sender: mpsc::UnboundedSender<(Request, Option<oneshot::Sender<Response>>)>,
490}
491
492impl InterThreadCallClient {
493    /// call without response
494    fn call_no_resp(&self, req: Request) -> Result<(), Error> {
495        self.arg_sender.send((req, None)).map_err(from_send_error)
496    }
497
498    /// call with response
499    async fn call_with_resp(&self, req: Request) -> Result<Response, Error> {
500        let (tx, rx) = oneshot::channel();
501        self.arg_sender
502            .send((req, Some(tx)))
503            .map_err(from_send_error)?;
504        rx.await.map_err(|_| {
505            InternalSnafu {
506                reason: "Sender is dropped",
507            }
508            .build()
509        })
510    }
511}
512
513#[derive(Debug)]
514struct InterThreadCallServer {
515    pub arg_recv: mpsc::UnboundedReceiver<(Request, Option<oneshot::Sender<Response>>)>,
516}
517
518impl InterThreadCallServer {
519    pub async fn recv(&mut self) -> Option<(Request, Option<oneshot::Sender<Response>>)> {
520        self.arg_recv.recv().await
521    }
522
523    pub fn blocking_recv(&mut self) -> Option<(Request, Option<oneshot::Sender<Response>>)> {
524        self.arg_recv.blocking_recv()
525    }
526}
527
528fn from_send_error<T>(err: mpsc::error::SendError<T>) -> Error {
529    InternalSnafu {
530        // this `err` will simply display `channel closed`
531        reason: format!(
532            "Worker's receiver channel have been closed unexpected: {}",
533            err
534        ),
535    }
536    .build()
537}
538
539#[cfg(test)]
540mod test {
541    use tokio::sync::oneshot;
542
543    use super::*;
544    use crate::expr::Id;
545    use crate::plan::Plan;
546    use crate::repr::RelationType;
547
548    #[test]
549    fn drop_handle() {
550        let (tx, rx) = oneshot::channel();
551        let worker_thread_handle = std::thread::spawn(move || {
552            let (handle, mut worker) = create_worker();
553            tx.send(handle).unwrap();
554            worker.run();
555        });
556        let handle = rx.blocking_recv().unwrap();
557        drop(handle);
558        worker_thread_handle.join().unwrap();
559    }
560
561    #[tokio::test]
562    pub async fn test_simple_get_with_worker_and_handle() {
563        let (tx, rx) = oneshot::channel();
564        let worker_thread_handle = std::thread::spawn(move || {
565            let (handle, mut worker) = create_worker();
566            tx.send(handle).unwrap();
567            worker.run();
568        });
569        let handle = rx.await.unwrap();
570        let src_ids = vec![GlobalId::User(1)];
571        let (tx, rx) = broadcast::channel::<Batch>(1024);
572        let (sink_tx, mut sink_rx) = mpsc::unbounded_channel::<Batch>();
573        let (flow_id, plan) = (
574            1,
575            TypedPlan {
576                plan: Plan::Get {
577                    id: Id::Global(GlobalId::User(1)),
578                },
579                schema: RelationType::new(vec![]).into_unnamed(),
580            },
581        );
582        let create_reqs = Request::Create {
583            flow_id,
584            plan,
585            sink_id: GlobalId::User(1),
586            sink_sender: sink_tx,
587            source_ids: src_ids,
588            src_recvs: vec![rx],
589            expire_after: None,
590            or_replace: false,
591            create_if_not_exists: true,
592            err_collector: ErrCollector::default(),
593        };
594        assert_eq!(
595            handle.create_flow(create_reqs).await.unwrap(),
596            Some(flow_id)
597        );
598        tx.send(Batch::empty()).unwrap();
599        handle.run_available(0, true).await.unwrap();
600        let (state_size, _, _) = handle.get_full_flow_stat().await.unwrap();
601        assert_eq!(state_size.len(), 1);
602        assert_eq!(sink_rx.recv().await.unwrap(), Batch::empty());
603        drop(handle);
604        worker_thread_handle.join().unwrap();
605    }
606}