1use std::future::Future;
5use std::pin::Pin;
6use std::task::Poll;
7use std::task::ready;
8
9use bytes::Bytes;
10use bytes::BytesMut;
11use futures::AsyncRead;
12use futures::AsyncWrite;
13use futures::AsyncWriteExt;
14use futures::Stream;
15use futures::StreamExt;
16use futures::TryStreamExt;
17use pin_project_lite::pin_project;
18use vortex_array::ArrayRef;
19use vortex_array::dtype::DType;
20use vortex_array::stream::ArrayStream;
21use vortex_error::VortexResult;
22use vortex_error::vortex_bail;
23use vortex_error::vortex_err;
24use vortex_session::VortexSession;
25
26use crate::messages::AsyncMessageReader;
27use crate::messages::DecoderMessage;
28use crate::messages::EncoderMessage;
29use crate::messages::MessageEncoder;
30
31pin_project! {
32 pub struct AsyncIPCReader<R> {
34 #[pin]
35 reader: AsyncMessageReader<R>,
36 dtype: DType,
37 session: VortexSession,
38 }
39}
40
41impl<R: AsyncRead + Unpin> AsyncIPCReader<R> {
42 pub async fn try_new(read: R, session: &VortexSession) -> VortexResult<Self> {
43 let mut reader = AsyncMessageReader::new(read);
44
45 let dtype = match reader.next().await.transpose()? {
46 Some(msg) => match msg {
47 DecoderMessage::DType(dtype) => dtype,
48 msg => {
49 vortex_bail!("Expected DType message, got {:?}", msg);
50 }
51 },
52 None => vortex_bail!("Expected DType message, got EOF"),
53 };
54
55 let dtype = DType::from_flatbuffer(dtype, session)?;
56
57 Ok(AsyncIPCReader {
58 reader,
59 dtype,
60 session: session.clone(),
61 })
62 }
63}
64
65impl<R: AsyncRead> ArrayStream for AsyncIPCReader<R> {
66 fn dtype(&self) -> &DType {
67 &self.dtype
68 }
69}
70
71impl<R: AsyncRead> Stream for AsyncIPCReader<R> {
72 type Item = VortexResult<ArrayRef>;
73
74 fn poll_next(
75 self: Pin<&mut Self>,
76 cx: &mut std::task::Context<'_>,
77 ) -> Poll<Option<Self::Item>> {
78 let this = self.project();
79
80 match ready!(this.reader.poll_next(cx)) {
81 None => Poll::Ready(None),
82 Some(msg) => match msg {
83 Ok(DecoderMessage::Array((array_parts, ctx, row_count))) => Poll::Ready(Some(
84 array_parts
85 .decode(this.dtype, row_count, &ctx, this.session)
86 .and_then(|array| {
87 if array.dtype() != this.dtype {
88 Err(vortex_err!(
89 "Array data type mismatch: expected {:?}, got {:?}",
90 this.dtype,
91 array.dtype()
92 ))
93 } else {
94 Ok(array)
95 }
96 }),
97 )),
98 Ok(msg) => Poll::Ready(Some(Err(vortex_err!(
99 "Expected Array message, got {:?}",
100 msg
101 )))),
102 Err(e) => Poll::Ready(Some(Err(e))),
103 },
104 }
105 }
106}
107
108pub trait ArrayStreamIPC {
110 fn into_ipc(self, session: &VortexSession) -> ArrayStreamIPCBytes
111 where
112 Self: Sized;
113
114 fn write_ipc<W: AsyncWrite + Unpin>(
115 self,
116 write: W,
117 session: &VortexSession,
118 ) -> impl Future<Output = VortexResult<W>>
119 where
120 Self: Sized;
121}
122
123impl<S: ArrayStream + 'static> ArrayStreamIPC for S {
124 fn into_ipc(self, session: &VortexSession) -> ArrayStreamIPCBytes
125 where
126 Self: Sized,
127 {
128 ArrayStreamIPCBytes {
129 stream: Box::pin(self),
130 encoder: MessageEncoder::new(session.clone()),
131 buffers: vec![],
132 written_dtype: false,
133 }
134 }
135
136 async fn write_ipc<W: AsyncWrite + Unpin>(
137 self,
138 mut write: W,
139 session: &VortexSession,
140 ) -> VortexResult<W>
141 where
142 Self: Sized,
143 {
144 let mut stream = self.into_ipc(session);
145 while let Some(chunk) = stream.next().await {
146 write.write_all(&chunk?).await?;
147 }
148 Ok(write)
149 }
150}
151
152pub struct ArrayStreamIPCBytes {
153 stream: Pin<Box<dyn ArrayStream + 'static>>,
154 encoder: MessageEncoder,
155 buffers: Vec<Bytes>,
156 written_dtype: bool,
157}
158
159impl ArrayStreamIPCBytes {
160 pub async fn collect_to_buffer(self) -> VortexResult<Bytes> {
162 let buffers: Vec<Bytes> = self.try_collect().await?;
163 let mut buffer = BytesMut::with_capacity(buffers.iter().map(|b| b.len()).sum());
164 for buf in buffers {
165 buffer.extend_from_slice(buf.as_ref());
166 }
167 Ok(buffer.freeze())
168 }
169}
170
171impl Stream for ArrayStreamIPCBytes {
172 type Item = VortexResult<Bytes>;
173
174 fn poll_next(
175 self: Pin<&mut Self>,
176 cx: &mut std::task::Context<'_>,
177 ) -> Poll<Option<Self::Item>> {
178 let this = self.get_mut();
179
180 if !this.written_dtype {
182 let Ok(buffers) = this
183 .encoder
184 .encode(EncoderMessage::DType(this.stream.dtype()))
185 else {
186 return Poll::Ready(Some(Err(vortex_err!("Failed to encode DType message"))));
187 };
188 this.buffers.extend(buffers);
189 this.written_dtype = true;
190 }
191
192 if !this.buffers.is_empty() {
194 return Poll::Ready(Some(Ok(this.buffers.remove(0))));
195 }
196
197 match ready!(this.stream.poll_next_unpin(cx)) {
199 None => return Poll::Ready(None),
200 Some(chunk) => match chunk.and_then(|c| this.encoder.encode(EncoderMessage::Array(&c)))
201 {
202 Ok(buffers) => {
203 this.buffers.extend(buffers);
204 }
205 Err(e) => return Poll::Ready(Some(Err(e))),
206 },
207 }
208
209 if !this.buffers.is_empty() {
211 return Poll::Ready(Some(Ok(this.buffers.remove(0))));
212 }
213
214 Poll::Ready(None)
216 }
217}
218
219#[cfg(test)]
220mod test {
221 use std::io;
222 use std::pin::Pin;
223 use std::task::Context;
224 use std::task::Poll;
225
226 use futures::io::Cursor;
227 use vortex_array::IntoArray as _;
228 use vortex_array::VortexSessionExecute;
229 use vortex_array::assert_arrays_eq;
230 use vortex_array::stream::ArrayStream;
231 use vortex_array::stream::ArrayStreamExt;
232 use vortex_buffer::buffer;
233
234 use super::*;
235 use crate::test::SESSION;
236
237 #[tokio::test]
238 async fn test_async_stream() {
239 let array = buffer![1, 2, 3].into_array();
240 let ipc_buffer = array
241 .to_array_stream()
242 .into_ipc(&SESSION)
243 .collect_to_buffer()
244 .await
245 .unwrap();
246
247 let reader = AsyncIPCReader::try_new(Cursor::new(ipc_buffer), &SESSION)
248 .await
249 .unwrap();
250
251 assert_eq!(reader.dtype(), array.dtype());
252 let result = reader.read_all().await.unwrap();
253 assert_arrays_eq!(result, array, &mut SESSION.create_execution_ctx());
254 }
255
256 struct ChunkedReader<R> {
258 inner: R,
259 chunk_size: usize,
260 }
261
262 impl<R: AsyncRead + Unpin> AsyncRead for ChunkedReader<R> {
263 fn poll_read(
264 mut self: Pin<&mut Self>,
265 cx: &mut Context<'_>,
266 buf: &mut [u8],
267 ) -> Poll<io::Result<usize>> {
268 let chunk_size = self.chunk_size.min(buf.len());
269 Pin::new(&mut self.inner).poll_read(cx, &mut buf[..chunk_size])
270 }
271 }
272
273 #[tokio::test]
274 async fn test_async_stream_chunked() {
275 let array = buffer![1i32, 2, 3, 4, 5, 6, 7, 8, 9, 10].into_array();
276 let ipc_buffer = array
277 .to_array_stream()
278 .into_ipc(&SESSION)
279 .collect_to_buffer()
280 .await
281 .unwrap();
282
283 let chunked = ChunkedReader {
284 inner: Cursor::new(ipc_buffer),
285 chunk_size: 3,
286 };
287
288 let reader = AsyncIPCReader::try_new(chunked, &SESSION).await.unwrap();
289
290 let result = reader.read_all().await.unwrap();
291 let expected = buffer![1i32, 2, 3, 4, 5, 6, 7, 8, 9, 10].into_array();
292 assert_arrays_eq!(result, expected, &mut SESSION.create_execution_ctx());
293 }
294
295 #[tokio::test]
297 async fn test_async_stream_single_byte_chunks() {
298 let array = buffer![42i64, -1, 0, i64::MAX, i64::MIN].into_array();
299 let ipc_buffer = array
300 .to_array_stream()
301 .into_ipc(&SESSION)
302 .collect_to_buffer()
303 .await
304 .unwrap();
305
306 let chunked = ChunkedReader {
307 inner: Cursor::new(ipc_buffer),
308 chunk_size: 1,
309 };
310
311 let reader = AsyncIPCReader::try_new(chunked, &SESSION).await.unwrap();
312
313 let result = reader.read_all().await.unwrap();
314 let expected = buffer![42i64, -1, 0, i64::MAX, i64::MIN].into_array();
315 assert_arrays_eq!(result, expected, &mut SESSION.create_execution_ctx());
316 }
317}