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}