parquet/arrow/async_reader/
spawn.rs1use std::future::Future;
19use std::ops::Range;
20use std::sync::Arc;
21
22use bytes::Bytes;
23use futures::future::BoxFuture;
24use futures::{FutureExt, TryFutureExt};
25use tokio::runtime::Handle;
26
27use crate::arrow::arrow_reader::ArrowReaderOptions;
28use crate::arrow::async_reader::{AsyncFileReader, MetadataSuffixFetch};
29use crate::errors::{ParquetError, Result};
30use crate::file::metadata::ParquetMetaData;
31
32#[derive(Clone, Debug)]
52pub struct SpawnedReader<R> {
53 inner: R,
54 handle: Handle,
55}
56
57impl<R> SpawnedReader<R> {
58 pub fn new(inner: R, handle: Handle) -> Self {
60 Self { inner, handle }
61 }
62
63 pub fn into_inner(self) -> R {
65 self.inner
66 }
67}
68
69fn spawn<T>(
72 handle: &Handle,
73 fut: impl Future<Output = Result<T>> + Send + 'static,
74) -> BoxFuture<'static, Result<T>>
75where
76 T: Send + 'static,
77{
78 handle
79 .spawn(fut)
80 .map_ok_or_else(
81 |e| match e.try_into_panic() {
82 Err(e) => Err(ParquetError::External(Box::new(e))),
83 Ok(p) => std::panic::resume_unwind(p),
84 },
85 |res| res,
86 )
87 .boxed()
88}
89
90impl<R> AsyncFileReader for SpawnedReader<R>
91where
92 R: AsyncFileReader + Clone + Send + 'static,
93{
94 fn get_bytes(&mut self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>> {
95 let mut inner = self.inner.clone();
96 spawn(&self.handle, async move { inner.get_bytes(range).await })
97 }
98
99 fn get_byte_ranges(&mut self, ranges: Vec<Range<u64>>) -> BoxFuture<'_, Result<Vec<Bytes>>> {
100 let mut inner = self.inner.clone();
101 spawn(
102 &self.handle,
103 async move { inner.get_byte_ranges(ranges).await },
104 )
105 }
106
107 fn get_metadata<'a>(
108 &'a mut self,
109 options: Option<&'a ArrowReaderOptions>,
110 ) -> BoxFuture<'a, Result<Arc<ParquetMetaData>>> {
111 let mut inner = self.inner.clone();
112 let options = options.cloned();
113 spawn(&self.handle, async move {
114 inner.get_metadata(options.as_ref()).await
115 })
116 }
117}
118
119impl<R> MetadataSuffixFetch for &mut SpawnedReader<R>
120where
121 R: AsyncFileReader + Clone + Send + 'static,
122 for<'a> &'a mut R: MetadataSuffixFetch,
123{
124 fn fetch_suffix(&mut self, suffix: usize) -> BoxFuture<'_, Result<Bytes>> {
125 let mut inner = self.inner.clone();
126 spawn(&self.handle, async move {
127 (&mut inner).fetch_suffix(suffix).await
128 })
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use crate::arrow::ParquetRecordBatchStreamBuilder;
136 use crate::file::metadata::ParquetMetaDataReader;
137 use futures::TryStreamExt;
138 use std::thread::ThreadId;
139
140 #[derive(Clone)]
142 struct InMemoryReader {
143 data: Bytes,
144 threads: Arc<std::sync::Mutex<Vec<ThreadId>>>,
145 }
146
147 impl InMemoryReader {
148 fn new(data: Bytes) -> Self {
149 Self {
150 data,
151 threads: Default::default(),
152 }
153 }
154 }
155
156 impl AsyncFileReader for InMemoryReader {
157 fn get_bytes(&mut self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>> {
158 self.threads
159 .lock()
160 .unwrap()
161 .push(std::thread::current().id());
162 let data = self.data.slice(range.start as usize..range.end as usize);
163 futures::future::ready(Ok(data)).boxed()
164 }
165
166 fn get_metadata<'a>(
167 &'a mut self,
168 options: Option<&'a ArrowReaderOptions>,
169 ) -> BoxFuture<'a, Result<Arc<ParquetMetaData>>> {
170 self.threads
171 .lock()
172 .unwrap()
173 .push(std::thread::current().id());
174 let metadata = ParquetMetaDataReader::new()
175 .with_arrow_reader_options(options)
176 .parse_and_finish(&self.data);
177 futures::future::ready(metadata.map(Arc::new)).boxed()
178 }
179 }
180
181 #[tokio::test]
182 async fn test_spawned_reader() {
183 let testdata = arrow::util::test_util::parquet_test_data();
184 let path = format!("{testdata}/alltypes_plain.parquet");
185 let data = Bytes::from(std::fs::read(path).unwrap());
186
187 let rt = tokio::runtime::Builder::new_multi_thread()
188 .worker_threads(1)
189 .build()
190 .unwrap();
191
192 let inner = InMemoryReader::new(data);
193 let threads = inner.threads.clone();
194 let reader = SpawnedReader::new(inner, rt.handle().clone());
195
196 let builder = ParquetRecordBatchStreamBuilder::new(reader).await.unwrap();
197 let batches: Vec<_> = builder.build().unwrap().try_collect().await.unwrap();
198
199 assert_eq!(batches.len(), 1);
200 assert_eq!(batches[0].num_rows(), 8);
201
202 let current_id = std::thread::current().id();
204 let threads = threads.lock().unwrap();
205 assert!(!threads.is_empty());
206 assert!(threads.iter().all(|id| *id != current_id));
207
208 tokio::runtime::Handle::current().spawn_blocking(move || drop(rt));
210 }
211
212 #[tokio::test]
213 async fn test_spawned_reader_fails_on_shutdown_runtime() {
214 let rt = tokio::runtime::Builder::new_multi_thread()
215 .worker_threads(1)
216 .build()
217 .unwrap();
218
219 let inner = InMemoryReader::new(Bytes::from_static(b"PAR1"));
220 let mut reader = SpawnedReader::new(inner, rt.handle().clone());
221
222 rt.shutdown_background();
223
224 let err = reader.get_bytes(0..1).await.unwrap_err().to_string();
225 assert!(err.contains("was cancelled"), "{err}");
226 }
227}