Skip to main content

bimm_firehose/core/operations/
executor.rs

1use crate::core::operations::environment::FirehoseOperatorEnvironment;
2use crate::core::operations::operator::OperationRunner;
3use crate::core::rows::FirehoseRowBatch;
4use crate::core::schema::FirehoseTableSchema;
5use std::fmt::Debug;
6use std::sync::Arc;
7
8/// Trait for executing a batch of operations on a `RowBatch`.
9pub trait FirehoseBatchExecutor: Debug + Send + Sync {
10    /// Returns the schema used by this executor.
11    fn schema(&self) -> &Arc<FirehoseTableSchema>;
12
13    /// Returns the operator environment used by this executor.
14    fn environment(&self) -> &Arc<dyn FirehoseOperatorEnvironment>;
15
16    /// Runs the butch under the policy of the executor.
17    fn execute_batch(
18        &self,
19        batch: &mut FirehoseRowBatch,
20    ) -> anyhow::Result<()>;
21}
22
23/// A sequential batch executor.
24///
25/// Runs every `BuildPlan` in the batch schema;
26/// executes serially with no threading.
27#[derive(Debug)]
28pub struct SequentialBatchExecutor {
29    /// The schema of the batch to execute.
30    schema: Arc<FirehoseTableSchema>,
31
32    /// The operator environment to use for executing the batch.
33    environment: Arc<dyn FirehoseOperatorEnvironment>,
34
35    /// The operation runners for each plan in the schema.
36    op_runners: Vec<Arc<OperationRunner>>,
37}
38
39impl SequentialBatchExecutor {
40    /// Creates a new `DefaultBatchExecutor` with the given operator environment.
41    pub fn new(
42        schema: Arc<FirehoseTableSchema>,
43        environment: Arc<dyn FirehoseOperatorEnvironment>,
44    ) -> anyhow::Result<Self> {
45        let mut op_runners = Vec::new();
46        let (_base, build_order) = schema.build_order()?;
47        for plan in &build_order {
48            let plan = Arc::new(plan.clone());
49            op_runners.push(Arc::new(OperationRunner::new_for_plan(
50                schema.clone(),
51                plan,
52                environment.as_ref(),
53            )?));
54        }
55
56        Ok(SequentialBatchExecutor {
57            schema,
58            environment,
59            op_runners,
60        })
61    }
62}
63
64impl FirehoseBatchExecutor for SequentialBatchExecutor {
65    fn schema(&self) -> &Arc<FirehoseTableSchema> {
66        &self.schema
67    }
68
69    fn environment(&self) -> &Arc<dyn FirehoseOperatorEnvironment> {
70        &self.environment
71    }
72
73    fn execute_batch(
74        &self,
75        batch: &mut FirehoseRowBatch,
76    ) -> anyhow::Result<()> {
77        for runner in &self.op_runners {
78            runner.apply_to_batch(batch)?;
79        }
80        Ok(())
81    }
82}
83
84/* Disabled because of the Send + Sync requirement on the
85   FirehoseOperatorEnvironment trait, which is not satisfied by the
86   current implementation of the environment.
87
88/// A threaded batch executor.
89#[derive(Debug)]
90pub struct ThreadedBatchExecutor {
91    /// The number of worker threads to use for executing the batch.
92    num_workers: usize,
93
94    /// The thread pool used for executing operations in parallel.
95    pool: threadpool::ThreadPool,
96
97    /// The schema of the batch to execute.
98    schema: Arc<FirehoseTableSchema>,
99
100    /// The operator environment to use for executing the batch.
101    environment: Arc<dyn FirehoseOperatorEnvironment>,
102
103    /// The operation runners for each plan in the schema.
104    op_runners: Vec<Arc<OperationRunner>>,
105
106    /// The sender for sending processed chunks back to the main thread.
107    tx: std::sync::mpsc::Sender<(usize, FirehoseRowBatch)>,
108
109    /// The receiver for receiving processed chunks from worker threads.
110    rx: std::sync::mpsc::Receiver<(usize, FirehoseRowBatch)>,
111}
112
113impl ThreadedBatchExecutor {
114    /// Creates a new `ThreadedBatchExecutor` with the given number of workers and operator environment.
115    pub fn new(
116        num_workers: usize,
117        schema: Arc<FirehoseTableSchema>,
118        environment: Arc<dyn FirehoseOperatorEnvironment>,
119    ) -> anyhow::Result<Self> {
120        let mut op_runners = Vec::new();
121        let (_base, build_order) = schema.build_order()?;
122        for plan in &build_order {
123            let plan = Arc::new(plan.clone());
124            op_runners.push(Arc::new(OperationRunner::new_for_plan(
125                schema.clone(),
126                plan,
127                environment.as_ref(),
128            )?));
129        }
130
131        let pool = threadpool::ThreadPool::new(num_workers);
132
133        let (tx, rx) = std::sync::mpsc::channel();
134
135        Ok(ThreadedBatchExecutor {
136            schema,
137            environment,
138            num_workers,
139            pool,
140            op_runners,
141            tx,
142            rx,
143        })
144    }
145}
146
147impl FirehoseBatchExecutor for ThreadedBatchExecutor {
148    fn schema(&self) -> &Arc<FirehoseTableSchema> {
149        &self.schema
150    }
151
152    fn environment(&self) -> &Arc<dyn FirehoseOperatorEnvironment> {
153        &self.environment
154    }
155
156    fn execute_batch(
157        &self,
158        batch: &mut FirehoseRowBatch,
159    ) -> anyhow::Result<()> {
160        let chunk_size = batch.len() / self.num_workers;
161        for idx in 0..self.num_workers {
162            let mut chunk = batch.empty_like();
163            let k: usize = std::cmp::min(chunk_size, batch.len());
164            batch.drain_rows(0..k).for_each(|r| chunk.add_row(r));
165
166            let tx = self.tx.clone();
167            let op_runners = self.op_runners.clone();
168            self.pool.execute(move || {
169                let mut chunk = chunk;
170                for runner in &op_runners {
171                    runner
172                        .apply_to_batch(&mut chunk)
173                        .expect("Failed to apply operation");
174                }
175                tx.send((idx, chunk))
176                    .expect("Failed to send processed chunk");
177            });
178        }
179
180        let mut chunks = Vec::with_capacity(self.num_workers);
181        for _ in 0..self.num_workers {
182            let recieved = self.rx.recv().expect("Failed to receive chunk");
183            chunks.push(recieved);
184        }
185        chunks.sort_by_key(|(idx, _)| *idx);
186
187        chunks
188            .into_iter()
189            .for_each(|(_, chunk)| batch.append_batch(chunk));
190
191        Ok(())
192    }
193}
194 */
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    const SE_IS_SEND: fn() = || {
201        fn assert_send<T: Send>() {}
202        assert_send::<SequentialBatchExecutor>();
203    };
204    #[test]
205    fn test_sequential_batch_executor_is_send() {
206        SE_IS_SEND();
207    }
208}