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::assert_arrays_eq;
229 use vortex_array::stream::ArrayStream;
230 use vortex_array::stream::ArrayStreamExt;
231 use vortex_buffer::buffer;
232
233 use super::*;
234 use crate::test::SESSION;
235
236 #[tokio::test]
237 async fn test_async_stream() {
238 let array = buffer![1, 2, 3].into_array();
239 let ipc_buffer = array
240 .to_array_stream()
241 .into_ipc(&SESSION)
242 .collect_to_buffer()
243 .await
244 .unwrap();
245
246 let reader = AsyncIPCReader::try_new(Cursor::new(ipc_buffer), &SESSION)
247 .await
248 .unwrap();
249
250 assert_eq!(reader.dtype(), array.dtype());
251 let result = reader.read_all().await.unwrap();
252 assert_arrays_eq!(result, array);
253 }
254
255 struct ChunkedReader<R> {
257 inner: R,
258 chunk_size: usize,
259 }
260
261 impl<R: AsyncRead + Unpin> AsyncRead for ChunkedReader<R> {
262 fn poll_read(
263 mut self: Pin<&mut Self>,
264 cx: &mut Context<'_>,
265 buf: &mut [u8],
266 ) -> Poll<io::Result<usize>> {
267 let chunk_size = self.chunk_size.min(buf.len());
268 Pin::new(&mut self.inner).poll_read(cx, &mut buf[..chunk_size])
269 }
270 }
271
272 #[tokio::test]
273 async fn test_async_stream_chunked() {
274 let array = buffer![1i32, 2, 3, 4, 5, 6, 7, 8, 9, 10].into_array();
275 let ipc_buffer = array
276 .to_array_stream()
277 .into_ipc(&SESSION)
278 .collect_to_buffer()
279 .await
280 .unwrap();
281
282 let chunked = ChunkedReader {
283 inner: Cursor::new(ipc_buffer),
284 chunk_size: 3,
285 };
286
287 let reader = AsyncIPCReader::try_new(chunked, &SESSION).await.unwrap();
288
289 let result = reader.read_all().await.unwrap();
290 let expected = buffer![1i32, 2, 3, 4, 5, 6, 7, 8, 9, 10].into_array();
291 assert_arrays_eq!(result, expected);
292 }
293
294 #[tokio::test]
296 async fn test_async_stream_single_byte_chunks() {
297 let array = buffer![42i64, -1, 0, i64::MAX, i64::MIN].into_array();
298 let ipc_buffer = array
299 .to_array_stream()
300 .into_ipc(&SESSION)
301 .collect_to_buffer()
302 .await
303 .unwrap();
304
305 let chunked = ChunkedReader {
306 inner: Cursor::new(ipc_buffer),
307 chunk_size: 1,
308 };
309
310 let reader = AsyncIPCReader::try_new(chunked, &SESSION).await.unwrap();
311
312 let result = reader.read_all().await.unwrap();
313 let expected = buffer![42i64, -1, 0, i64::MAX, i64::MIN].into_array();
314 assert_arrays_eq!(result, expected);
315 }
316}