Skip to main content

polars_io/cloud/cloud_writer/
io_trait_wrap.rs

1use std::pin::Pin;
2use std::task::{Poll, ready};
3
4use bytes::Bytes;
5use futures::FutureExt;
6use polars_core::runtime::ASYNC;
7
8use crate::cloud::cloud_writer::CloudWriter;
9use crate::utils::file::WritableTrait;
10
11/// Wrapper on [`CloudWriter`] that implements [`std::io::Write`] and [`tokio::io::AsyncWrite`].
12pub struct CloudWriterIoTraitWrap {
13    state: WriterState,
14}
15
16enum WriterState {
17    Ready(Box<CloudWriter>),
18    Poll(
19        Pin<Box<dyn Future<Output = std::io::Result<WriterState>> + Send + 'static>>,
20        PollOperation,
21    ),
22    Finished,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26enum PollOperation {
27    // (slice_addr, slice_len)
28    Write { slice_ptr: usize, written: usize },
29    Shutdown,
30}
31
32struct FinishActivePoll<'a>(Pin<&'a mut WriterState>);
33
34impl<'a> Future for FinishActivePoll<'a> {
35    type Output = std::io::Result<Option<PollOperation>>;
36
37    fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
38        match &mut *self.0 {
39            WriterState::Poll(fut, _) => match fut.poll_unpin(cx) {
40                Poll::Ready(Ok(new_state)) => {
41                    debug_assert!(!matches!(&new_state, WriterState::Poll(..)));
42
43                    let WriterState::Poll(_, operation) =
44                        std::mem::replace(&mut *self.0, new_state)
45                    else {
46                        unreachable!()
47                    };
48
49                    Poll::Ready(Ok(Some(operation)))
50                },
51                Poll::Ready(Err(e)) => {
52                    *self.0 = WriterState::Finished;
53                    Poll::Ready(Err(e))
54                },
55                Poll::Pending => Poll::Pending,
56            },
57
58            WriterState::Ready(_) | WriterState::Finished => Poll::Ready(Ok(None)),
59        }
60    }
61}
62
63impl CloudWriterIoTraitWrap {
64    fn finish_active_poll(&mut self) -> FinishActivePoll<'_> {
65        FinishActivePoll(Pin::new(&mut self.state))
66    }
67
68    fn take_writer_from_ready_state(&mut self) -> Option<Box<CloudWriter>> {
69        if !matches!(&self.state, WriterState::Ready(_)) {
70            return None;
71        }
72
73        let WriterState::Ready(writer) = std::mem::replace(&mut self.state, WriterState::Finished)
74        else {
75            unreachable!()
76        };
77
78        Some(writer)
79    }
80
81    fn get_writer_mut_from_ready_state(&mut self) -> Option<&mut CloudWriter> {
82        if let WriterState::Ready(writer) = &mut self.state {
83            Some(writer.as_mut())
84        } else {
85            None
86        }
87    }
88
89    pub async fn write_all_owned(&mut self, bytes: Bytes) -> std::io::Result<()> {
90        self.finish_active_poll().await?;
91
92        self.get_writer_mut_from_ready_state()
93            .unwrap()
94            .write_all_owned(bytes)
95            .await?;
96
97        Ok(())
98    }
99
100    pub async fn into_cloud_writer(mut self) -> std::io::Result<CloudWriter> {
101        self.finish_active_poll().await?;
102
103        match self.state {
104            WriterState::Ready(writer) => Ok(*writer),
105            WriterState::Poll(..) => unreachable!(),
106            WriterState::Finished => panic!(),
107        }
108    }
109
110    pub fn as_cloud_writer(&mut self) -> std::io::Result<&mut CloudWriter> {
111        if !matches!(self.state, WriterState::Ready(_)) {
112            match &mut self.state {
113                WriterState::Ready(_) => unreachable!(),
114                WriterState::Poll(..) => ASYNC.block_in_place_on(self.finish_active_poll())?,
115                WriterState::Finished => panic!(),
116            };
117        }
118
119        let WriterState::Ready(writer) = &mut self.state else {
120            panic!()
121        };
122
123        Ok(writer)
124    }
125}
126
127impl From<CloudWriter> for CloudWriterIoTraitWrap {
128    fn from(writer: CloudWriter) -> Self {
129        Self {
130            state: WriterState::Ready(Box::new(writer)),
131        }
132    }
133}
134
135impl std::io::Write for CloudWriterIoTraitWrap {
136    fn write(&mut self, mut buf: &[u8]) -> std::io::Result<usize> {
137        let total_buf_len = buf.len();
138        let buf: &mut &[u8] = &mut buf;
139
140        if let Some(writer) = self.get_writer_mut_from_ready_state() {
141            let full = writer.fill_buffer_from_slice(buf);
142
143            if !full {
144                assert!(buf.is_empty());
145                return Ok(total_buf_len);
146            }
147        }
148
149        ASYNC.block_in_place_on(async {
150            self.finish_active_poll().await?;
151
152            let writer = self.get_writer_mut_from_ready_state().unwrap();
153
154            loop {
155                writer.flush_full_chunk().await?;
156
157                if !writer.fill_buffer_from_slice(buf) {
158                    break;
159                }
160            }
161
162            assert!(buf.is_empty());
163
164            Ok(total_buf_len)
165        })
166    }
167
168    fn flush(&mut self) -> std::io::Result<()> {
169        // Nothing is visible before `finish()`, so we only drain an active poll and keep buffering.
170        match &self.state {
171            WriterState::Poll(..) => ASYNC
172                .block_in_place_on(self.finish_active_poll())
173                .map(|_| ()),
174            WriterState::Ready(_) | WriterState::Finished => Ok(()),
175        }
176    }
177}
178
179impl WritableTrait for CloudWriterIoTraitWrap {
180    fn close(&mut self) -> std::io::Result<()> {
181        ASYNC.block_in_place_on(async {
182            self.finish_active_poll().await?;
183
184            let mut writer = self.take_writer_from_ready_state().unwrap();
185            writer.finish().await?;
186
187            Ok(())
188        })
189    }
190
191    fn sync_all(&self) -> std::io::Result<()> {
192        Ok(())
193    }
194
195    fn sync_data(&self) -> std::io::Result<()> {
196        Ok(())
197    }
198}
199
200impl tokio::io::AsyncWrite for CloudWriterIoTraitWrap {
201    fn poll_write(
202        mut self: Pin<&mut Self>,
203        cx: &mut std::task::Context<'_>,
204        buf: &[u8],
205    ) -> std::task::Poll<std::io::Result<usize>> {
206        loop {
207            let offset = match ready!(self.finish_active_poll().poll_unpin(cx))? {
208                Some(PollOperation::Write { slice_ptr, written })
209                    if slice_ptr == buf.as_ptr() as usize =>
210                {
211                    written
212                },
213                Some(_) => panic!(),
214                None => 0,
215            };
216
217            let writer = self.get_writer_mut_from_ready_state().unwrap();
218
219            let offset_buf: &mut &[u8] = &mut &buf[offset..];
220
221            let full = writer.fill_buffer_from_slice(offset_buf);
222
223            if !full {
224                assert!(offset_buf.is_empty());
225                return Poll::Ready(Ok(buf.len()));
226            };
227
228            let new_offset = buf.len() - offset_buf.len();
229
230            let mut writer = self.take_writer_from_ready_state().unwrap();
231
232            self.state = WriterState::Poll(
233                Box::pin(async move {
234                    writer.flush_full_chunk().await?;
235                    Ok(WriterState::Ready(writer))
236                }),
237                PollOperation::Write {
238                    slice_ptr: buf.as_ptr() as usize,
239                    written: new_offset,
240                },
241            );
242        }
243    }
244
245    fn poll_flush(
246        mut self: Pin<&mut Self>,
247        cx: &mut std::task::Context<'_>,
248    ) -> std::task::Poll<std::io::Result<()>> {
249        // Nothing is visible before `finish()`, so we only drain an active poll and keep buffering.
250        match ready!(self.finish_active_poll().poll_unpin(cx))? {
251            Some(_) => panic!(),
252            None => Poll::Ready(Ok(())),
253        }
254    }
255
256    fn poll_shutdown(
257        mut self: Pin<&mut Self>,
258        cx: &mut std::task::Context<'_>,
259    ) -> std::task::Poll<std::io::Result<()>> {
260        loop {
261            match ready!(self.finish_active_poll().poll_unpin(cx))? {
262                Some(PollOperation::Shutdown) => return Poll::Ready(Ok(())),
263                Some(_) => panic!(),
264                None => {
265                    let mut writer = self.take_writer_from_ready_state().unwrap();
266
267                    self.state = WriterState::Poll(
268                        Box::pin(async move {
269                            writer.finish().await?;
270                            Ok(WriterState::Finished)
271                        }),
272                        PollOperation::Shutdown,
273                    );
274                },
275            }
276        }
277    }
278}