linkrs_query/executor/streaming/
stream.rs1use std::sync::Arc;
2
3use super::chunk::DataChunk;
4use super::chunk::LocalChunkCollector;
5use super::engine::StreamingExecutionEngine;
6use super::runtime::ExecutionRuntime;
7use linkrs_core::error::QueryError;
8use linkrs_core::DataSet;
9
10pub struct ResultStream {
19 engine: Option<StreamingExecutionEngine>,
20 runtime: Arc<ExecutionRuntime>,
21 opened: bool,
22 exhausted: bool,
23 closed: bool,
24 buffered: Vec<DataChunk>,
26 buffered_idx: usize,
27}
28
29impl ResultStream {
30 pub fn new(engine: StreamingExecutionEngine, runtime: Arc<ExecutionRuntime>) -> Self {
36 Self {
37 engine: Some(engine),
38 runtime,
39 opened: false,
40 exhausted: false,
41 closed: false,
42 buffered: Vec::new(),
43 buffered_idx: 0,
44 }
45 }
46
47 pub fn from_collected(
52 chunks: Vec<DataChunk>,
53 engine: StreamingExecutionEngine,
54 runtime: Arc<ExecutionRuntime>,
55 ) -> Self {
56 Self {
57 engine: Some(engine),
58 runtime,
59 opened: true,
60 exhausted: false,
61 closed: false,
62 buffered: chunks,
63 buffered_idx: 0,
64 }
65 }
66
67 fn ensure_opened(&mut self) -> Result<(), QueryError> {
69 if !self.opened {
70 if let Some(ref mut engine) = self.engine {
71 if let Err(error) = engine.open_root() {
72 return self.return_execution_error(error);
73 }
74 }
75 self.opened = true;
76 }
77 Ok(())
78 }
79
80 pub fn next_chunk(&mut self) -> Result<Option<DataChunk>, QueryError> {
85 if let Err(error) = self.runtime.ensure_not_cancelled() {
86 return self.return_execution_error(error);
87 }
88
89 if self.exhausted {
90 return Ok(None);
91 }
92
93 if self.buffered_idx < self.buffered.len() {
100 let layout = self.buffered[self.buffered_idx].get_layout();
101 let empty = DataChunk::new_with_layout(Vec::new(), layout);
102 let chunk = std::mem::replace(&mut self.buffered[self.buffered_idx], empty);
103 self.buffered_idx += 1;
104 return Ok(Some(chunk));
105 }
106
107 self.ensure_opened()?;
108
109 if let Some(ref mut engine) = self.engine {
110 let next = match engine.next_chunk_from_root() {
111 Ok(next) => next,
112 Err(error) => return self.return_execution_error(error),
113 };
114 match next {
115 Some(chunk) => Ok(Some(chunk)),
116 None => {
117 self.exhausted = true;
118 self.close_inner()?;
119 Ok(None)
120 }
121 }
122 } else {
123 Ok(None)
124 }
125 }
126
127 pub fn close(&mut self) -> Result<(), QueryError> {
129 if self.closed {
130 return Ok(());
131 }
132 self.exhausted = true;
133 self.close_inner()
134 }
135
136 fn close_inner(&mut self) -> Result<(), QueryError> {
137 if self.closed {
138 return Ok(());
139 }
140 let result = if let Some(ref mut engine) = self.engine {
141 let stop_error = engine.stop_root().err();
142 let close_error = engine.close_root().err();
143 match (stop_error, close_error) {
144 (Some(stop_err), Some(close_err)) => {
145 log::warn!(
146 "Both stop and close failed during stream teardown; \
147 stop error: {stop_err}; close error: {close_err}"
148 );
149 Err(stop_err)
150 }
151 (Some(stop_err), None) | (None, Some(stop_err)) => Err(stop_err),
152 (None, None) => Ok(()),
153 }
154 } else {
155 Ok(())
156 };
157 self.runtime.profile_end();
158 self.runtime.release_resources();
159 self.runtime.reset_arena();
160 self.closed = true;
161 result
162 }
163
164 fn return_execution_error<T>(&mut self, error: QueryError) -> Result<T, QueryError> {
168 if let Err(close_error) = self.close_inner() {
169 log::warn!(
170 "Failed to clean up streaming result after execution error: {}",
171 close_error
172 );
173 }
174 Err(error)
175 }
176
177 pub fn collect(mut self) -> Result<DataSet, QueryError> {
182 let spill_manager = self.runtime.get_spill_manager();
183 let mut collector: Option<LocalChunkCollector> = None;
184
185 while let Some(mut chunk) = self.next_chunk()? {
186 let collector = match collector {
187 Some(ref mut c) => c,
188 None => {
189 let mut fresh = LocalChunkCollector::new(chunk.col_names());
190 if let Some(ref manager) = spill_manager {
191 fresh.attach_spill_manager(manager.clone());
192 }
193 collector.insert(fresh)
194 }
195 };
196 collector.push_chunk(&mut chunk)?;
197 }
198
199 let Some(mut collector) = collector else {
200 return Ok(DataSet::with_columns(Vec::new()));
201 };
202 collector.finish_spill()?;
203 let stats = self.runtime.columnar_stats();
204 let spilled_rows = collector.spilled_rows();
205 let spilled_bytes = collector.spilled_bytes();
206 let spilled_runs = collector.spilled_run_count() as u64;
207 let (all_rows, col_names) = collector.into_rows()?;
208 if spilled_rows > 0 {
209 stats.record_spill_with_runs(spilled_rows, spilled_bytes, spilled_runs);
210 }
211 Ok(DataSet::from_rows(all_rows, col_names))
212 }
213}
214
215impl Drop for ResultStream {
216 fn drop(&mut self) {
217 if !self.closed {
218 let _ = self.close_inner();
219 }
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use std::sync::atomic::{AtomicBool, Ordering};
226
227 use super::*;
228 use crate::executor::base::MemoryBudget;
229 use crate::executor::streaming::executor::StreamingExecutor;
230 use crate::executor::streaming::operators::base::OperatorBase;
231 use crate::executor::streaming::operators::source_operator::SourceOperator;
232 use crate::executor::streaming::operators::source_operator::SourceOperatorKind;
233 use crate::executor::streaming::runtime::QueryIdentity;
234 use crate::executor::streaming::slot::SlotLayout;
235 use linkrs_core::Value;
236
237 #[test]
238 fn cancellation_error_releases_resources_immediately() {
239 let runtime = Arc::new(ExecutionRuntime::new(
240 QueryIdentity::default(),
241 MemoryBudget::default_budget(),
242 None,
243 crate::executor::base::SearchContext::default(),
244 ));
245 let released = Arc::new(AtomicBool::new(false));
246 let released_for_cleanup = released.clone();
247 runtime.on_cleanup(move || {
248 released_for_cleanup.store(true, Ordering::Relaxed);
249 });
250
251 let mut engine = StreamingExecutionEngine::new();
252 engine.register_executor(
253 0,
254 StreamingExecutor::Source(
255 OperatorBase::new(1),
256 SourceOperator::new(
257 SourceOperatorKind::ScanVertices {
258 buffer: vec![vec![Value::BigInt(1)]],
259 current_index: 0,
260 col_names: vec!["id".to_string()],
261 },
262 Arc::new(SlotLayout::from_names(&["id".to_string()])),
263 ),
264 ),
265 );
266 engine.set_runtime(runtime.clone());
267 let mut stream = engine.into_stream().expect("create stream");
268
269 runtime.cancel();
270 assert!(stream.next_chunk().is_err());
271 assert!(released.load(Ordering::Relaxed));
272 assert!(stream.close().is_ok());
273 }
274}