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