linkrs_query/executor/streaming/operators/
base.rs1use std::sync::Arc;
2
3use parking_lot::Mutex;
4
5use super::super::runtime::{ExecutionRuntime, OperatorProfileKey};
6use super::super::spill::SpillManager;
7use super::super::state::{
8 GlobalState, GlobalStateKey, LocalState, LocalStateKey, StateArenaSet, TaskId,
9};
10use crate::executor::streaming::plan::types::PhysicalOperatorId;
11use crate::executor::streaming::slot::SlotLayout;
12use linkrs_core::Arena;
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum OperatorLifecycle {
23 New,
25 Opened,
27 Exhausted,
29 Stopped,
31 Failed,
33 Closed,
35}
36
37impl OperatorLifecycle {
38 pub fn is_opened(self) -> bool {
41 matches!(self, Self::Opened | Self::Exhausted)
42 }
43
44 pub fn is_exhausted(self) -> bool {
45 matches!(self, Self::Exhausted)
46 }
47
48 pub fn mark_opened(&mut self) {
49 debug_assert!(
50 matches!(*self, Self::New),
51 "mark_opened from {:?} (expected New)",
52 self
53 );
54 *self = Self::Opened;
55 }
56
57 pub fn mark_exhausted(&mut self) {
58 if matches!(*self, Self::Opened) {
59 *self = Self::Exhausted;
60 }
61 }
62
63 pub fn mark_stopped(&mut self) {
64 if matches!(*self, Self::Opened | Self::Exhausted) {
65 *self = Self::Stopped;
66 }
67 }
68
69 pub fn mark_failed(&mut self) {
70 *self = Self::Failed;
71 }
72
73 pub fn mark_closed(&mut self) {
74 *self = Self::Closed;
75 }
76}
77
78#[derive(Debug)]
79pub struct OperatorBase {
80 pub plan_node_id: i64,
81 pub runtime: Option<Arc<ExecutionRuntime>>,
82 pub lifecycle: OperatorLifecycle,
83 pub is_global: bool,
86 pub partition_id: Option<usize>,
89 pub chunk_size: usize,
93 pub physical_operator_id: PhysicalOperatorId,
96 pub output_layout: Arc<SlotLayout>,
100 pub reset_used_fallback: bool,
104}
105
106impl OperatorBase {
107 pub fn new(plan_node_id: i64) -> Self {
108 Self {
109 plan_node_id,
110 runtime: None,
111 lifecycle: OperatorLifecycle::New,
112 is_global: false,
113 partition_id: None,
114 chunk_size: 2048,
115 physical_operator_id: PhysicalOperatorId(plan_node_id.unsigned_abs() as usize),
116 output_layout: Arc::new(SlotLayout::new(Vec::new())),
117 reset_used_fallback: false,
118 }
119 }
120
121 pub fn state_arena(&self) -> parking_lot::MutexGuard<'_, StateArenaSet> {
126 self.runtime
127 .as_ref()
128 .expect("runtime required")
129 .state_arena_for(self.partition_id)
130 .lock()
131 }
132
133 pub fn with_physical_operator_id(mut self, operator_id: PhysicalOperatorId) -> Self {
136 self.physical_operator_id = operator_id;
137 self
138 }
139
140 pub fn with_output_layout(mut self, output_layout: Arc<SlotLayout>) -> Self {
142 self.output_layout = output_layout;
143 self
144 }
145
146 pub fn state_key(&self) -> GlobalStateKey {
148 GlobalStateKey(self.physical_operator_id, self.partition_id)
149 }
150
151 pub fn take_state(&mut self) -> Option<GlobalState> {
153 let rt = self.runtime.as_ref()?;
154 let key = self.state_key();
155 rt.state_arena_for(self.partition_id)
156 .lock()
157 .global
158 .remove(&key)
159 }
160
161 pub fn insert_state(&mut self, state: GlobalState) {
166 let Some(rt) = self.runtime.as_ref() else {
167 return;
168 };
169 let key = self.state_key();
170 rt.state_arena_for(self.partition_id)
171 .lock()
172 .global
173 .insert(key, state);
174 }
175
176 pub fn local_state_key(&self, task_id: TaskId) -> LocalStateKey {
180 LocalStateKey(self.physical_operator_id, self.partition_id, task_id)
181 }
182
183 pub fn insert_local_state(&mut self, task_id: TaskId, state: LocalState) {
185 let Some(rt) = self.runtime.as_ref() else {
186 return;
187 };
188 let key = self.local_state_key(task_id);
189 rt.state_arena_for(self.partition_id)
190 .lock()
191 .local
192 .insert(key, state);
193 }
194
195 pub fn take_local_state(&mut self, task_id: TaskId) -> Option<LocalState> {
197 let rt = self.runtime.as_ref()?;
198 let key = self.local_state_key(task_id);
199 rt.state_arena_for(self.partition_id)
200 .lock()
201 .local
202 .remove(&key)
203 }
204
205 pub fn with_local_state<T, F>(&self, task_id: TaskId, f: F) -> Option<T>
210 where
211 F: FnOnce(&mut LocalState) -> T,
212 {
213 let rt = self.runtime.as_ref()?;
214 let key = self.local_state_key(task_id);
215 let mut arena = rt.state_arena_for(self.partition_id).lock();
216 arena.local.get_mut(&key).map(f)
217 }
218
219 pub fn with_chunk_size(mut self, chunk_size: usize) -> Self {
220 self.chunk_size = chunk_size;
221 self
222 }
223
224 pub fn with_runtime(mut self, rt: Option<Arc<ExecutionRuntime>>) -> Self {
225 self.runtime = rt;
226 self
227 }
228
229 pub fn with_global(mut self, is_global: bool) -> Self {
230 self.is_global = is_global;
231 self
232 }
233
234 pub fn with_partition(mut self, partition_id: usize) -> Self {
235 self.partition_id = Some(partition_id);
236 self
237 }
238
239 pub fn profile_key(&self) -> OperatorProfileKey {
240 OperatorProfileKey::new(self.physical_operator_id, self.partition_id)
241 }
242
243 pub fn ensure_not_cancelled(&self) -> Result<(), linkrs_core::error::QueryError> {
244 if let Some(rt) = &self.runtime {
245 rt.ensure_not_cancelled()
246 } else {
247 Ok(())
248 }
249 }
250
251 pub fn record_profile_rows(&self, count: u64) {
252 if let Some(rt) = &self.runtime {
253 if let Some(entry) = rt.profile().get_entry(&self.profile_key()) {
254 entry
255 .output_rows
256 .fetch_add(count, std::sync::atomic::Ordering::Relaxed);
257 entry
258 .advance_count
259 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
260 }
261 }
262 }
263
264 pub fn mark_profile_exhausted(&self) {
270 if let Some(rt) = &self.runtime {
271 if let Some(entry) = rt.profile().get_entry(&self.profile_key()) {
272 entry
273 .exhausted
274 .store(true, std::sync::atomic::Ordering::Relaxed);
275 }
276 }
277 }
278
279 pub fn register_resource<F>(&self, f: F)
280 where
281 F: FnOnce() + Send + 'static,
282 {
283 if let Some(rt) = &self.runtime {
284 rt.on_cleanup(f);
285 }
286 }
287
288 pub fn spill_manager(&self) -> Option<Arc<SpillManager>> {
290 self.runtime.as_ref().and_then(|rt| rt.get_spill_manager())
291 }
292
293 pub fn arena(&self) -> Option<&Arc<Mutex<Arena>>> {
295 self.runtime.as_ref().and_then(|rt| rt.arena())
296 }
297
298 pub fn mark_new(&mut self) {
301 self.lifecycle = OperatorLifecycle::New;
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::OperatorBase;
308 use crate::executor::streaming::plan::types::PhysicalOperatorId;
309
310 #[test]
311 fn state_keys_include_physical_operator_and_partition() {
312 let global = OperatorBase::new(10).with_physical_operator_id(PhysicalOperatorId(7));
313 let partition_zero = OperatorBase::new(10)
314 .with_physical_operator_id(PhysicalOperatorId(7))
315 .with_partition(0);
316 let partition_one = OperatorBase::new(10)
317 .with_physical_operator_id(PhysicalOperatorId(7))
318 .with_partition(1);
319
320 assert_ne!(global.state_key(), partition_zero.state_key());
321 assert_ne!(partition_zero.state_key(), partition_one.state_key());
322 assert_ne!(
323 partition_zero.local_state_key(0),
324 partition_zero.local_state_key(1)
325 );
326 }
327}