Skip to main content

shared_files/
reader.rs

1//! File reading functionality, notably the [`SharedFileReader`] type.
2
3use crate::errors::ReadError;
4use crate::{Sentinel, SharedFileType, WriteState};
5use pin_project::{pin_project, pinned_drop};
6use std::io::{ErrorKind, SeekFrom};
7use std::pin::Pin;
8use std::sync::Arc;
9use std::sync::atomic::{AtomicUsize, Ordering};
10use std::task::{Context, Poll};
11use tokio::io;
12use tokio::io::{AsyncRead, AsyncSeek, ReadBuf};
13use uuid::Uuid;
14
15/// A reader for the shared temporary file.
16#[pin_project(PinnedDrop)]
17pub struct SharedFileReader<T> {
18    /// The ID of the reader.
19    id: Uuid,
20    /// The file to read from.
21    #[pin]
22    file: T,
23    /// The sentinel value to keep the file alive.
24    sentinel: Arc<Sentinel<T>>,
25    /// The number of bytes read. Used to keep track
26    /// of how many bytes need to be read from the underlying buffer.
27    read: AtomicUsize,
28}
29
30/// These IDs never leave the current system, so the node ID is arbitrary.
31static NODE_ID: &[u8; 6] = &[2, 3, 0, 6, 1, 2];
32
33impl<T> SharedFileReader<T>
34where
35    T: SharedFileType<Type = T>,
36{
37    pub(crate) fn new(file: T, sentinel: Arc<Sentinel<T>>) -> Self {
38        Self {
39            id: Uuid::now_v1(NODE_ID),
40            file,
41            sentinel,
42            read: AtomicUsize::new(0),
43        }
44    }
45
46    /// Creates a new, independent reader.
47    pub async fn fork(&self) -> Result<Self, T::OpenError> {
48        Ok(Self {
49            id: Uuid::now_v1(NODE_ID),
50            file: self.sentinel.original.open_ro().await?,
51            sentinel: self.sentinel.clone(),
52            read: AtomicUsize::new(0),
53        })
54    }
55}
56
57impl<T> SharedFileReader<T> {
58    /// Gets the (expected) size of the file to read.
59    pub fn file_size(&self) -> FileSize {
60        match self.sentinel.state.load() {
61            WriteState::Pending(committed, _written) => FileSize::AtLeast(committed),
62            WriteState::Completed(size) => FileSize::Exactly(size),
63            WriteState::Failed => FileSize::Error,
64        }
65    }
66}
67
68/// The file size of the file to read.
69#[derive(Debug, Copy, Clone)]
70pub enum FileSize {
71    /// The file is not entirely written yet. The specified amount is the minimum
72    /// number known to exist.
73    AtLeast(usize),
74    /// The file is completely written and has exactly the specified amount of bytes.
75    Exactly(usize),
76    /// An error occurred while writing the file; reading may not complete.
77    Error,
78}
79
80impl FileSize {
81    /// Returns the minimum or exact file size if it is known, or [`None`] otherwise.
82    pub fn minimum_size(&self) -> Option<usize> {
83        if let Self::AtLeast(len) = self {
84            Some(*len)
85        } else {
86            self.exact_size()
87        }
88    }
89
90    /// Returns the exact file size if it is known, or [`None`] otherwise.
91    pub fn exact_size(&self) -> Option<usize> {
92        if let Self::Exactly(len) = self {
93            Some(*len)
94        } else {
95            None
96        }
97    }
98}
99
100#[pinned_drop]
101impl<T> PinnedDrop for SharedFileReader<T> {
102    fn drop(mut self: Pin<&mut Self>) {
103        self.sentinel.remove_reader_waker(&self.id)
104    }
105}
106
107impl<T> AsyncRead for SharedFileReader<T>
108where
109    T: AsyncRead,
110{
111    fn poll_read(
112        self: Pin<&mut Self>,
113        cx: &mut Context<'_>,
114        buf: &mut ReadBuf<'_>,
115    ) -> Poll<io::Result<()>> {
116        let read_so_far = self.read.load(Ordering::Acquire);
117
118        let current_total = loop {
119            match self.sentinel.state.load() {
120                WriteState::Pending(committed, _written) => {
121                    // If there is new committed data, read it.
122                    if read_so_far != committed {
123                        break committed;
124                    }
125
126                    // We are caught up with the committed data. Register our
127                    // waker *before* re-checking the state: a writer that
128                    // commits more bytes (and drains the waker map) between the
129                    // load above and this registration would otherwise leave us
130                    // parked with no pending wake-up. By registering first and
131                    // then re-loading, we either observe the new data now or are
132                    // guaranteed to be woken later.
133                    self.sentinel.register_reader_waker(self.id, cx.waker());
134                    if let WriteState::Pending(committed, _) = self.sentinel.state.load()
135                        && read_so_far == committed
136                    {
137                        return Poll::Pending;
138                    }
139                    // The state advanced after registering; re-evaluate it.
140                }
141                WriteState::Completed(count) => {
142                    // If we have read all there is, we're done.
143                    if read_so_far == count {
144                        return Poll::Ready(Ok(()));
145                    }
146                    break count;
147                }
148                WriteState::Failed => {
149                    return Poll::Ready(Err(io::Error::new(
150                        ErrorKind::BrokenPipe,
151                        ReadError::FileClosed,
152                    )));
153                }
154            }
155        };
156
157        // Ensure to not read more bytes than were actually written
158        // by constraining the actual buffer to a smaller one if needed.
159        let read_at_most = (current_total - read_so_far).min(buf.remaining());
160        let mut smaller_buf = buf.take(read_at_most);
161        let read_offset = smaller_buf.filled().len();
162
163        let this = self.project();
164
165        if let Poll::Ready(result) = this.file.poll_read(cx, &mut smaller_buf) {
166            this.sentinel.remove_reader_waker(this.id);
167            if let Err(e) = result {
168                return Poll::Ready(Err(e));
169            }
170
171            // If the buffer was advanced, return the result.
172            let read_now = smaller_buf.filled().len();
173            if read_now != read_offset {
174                // Advance the parent buffer. `read_now` is the number of bytes
175                // read into the `take`d sub-buffer, which maps onto the parent's
176                // unfilled region. Use the relative `advance` (not `set_filled`,
177                // which takes an absolute length) so that bytes already present
178                // in the parent buffer from an earlier poll are preserved.
179                unsafe {
180                    buf.assume_init(read_now);
181                }
182                buf.advance(read_now);
183
184                let read = read_so_far + (read_now - read_offset);
185                this.read.store(read, Ordering::Release);
186                return Poll::Ready(result);
187            }
188
189            // If the buffer was not advanced and source file is completed (or in fail state),
190            // return as-is. Otherwise, reset and wait.
191            match this.sentinel.state.load() {
192                WriteState::Pending(_, _) => {}
193                WriteState::Completed(_) => return Poll::Ready(Ok(())),
194                WriteState::Failed => {
195                    return Poll::Ready(Err(io::Error::new(
196                        ErrorKind::BrokenPipe,
197                        ReadError::FileClosed,
198                    )));
199                }
200            }
201        }
202
203        // Re-register waker and try again.
204        this.sentinel.register_reader_waker(*this.id, cx.waker());
205        Poll::Pending
206    }
207}
208
209impl<T> AsyncSeek for SharedFileReader<T>
210where
211    T: AsyncSeek,
212{
213    fn start_seek(self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> {
214        let this = self.project();
215        this.file.start_seek(position)
216    }
217
218    fn poll_complete(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
219        let this = self.project();
220        this.file.poll_complete(cx)
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227
228    #[test]
229    fn test_exact_size() {
230        assert_eq!(FileSize::Exactly(42).exact_size(), Some(42));
231        assert_eq!(FileSize::AtLeast(41).exact_size(), None);
232        assert_eq!(FileSize::Error.exact_size(), None);
233    }
234
235    #[test]
236    fn test_minimum_size() {
237        assert_eq!(FileSize::Exactly(42).minimum_size(), Some(42));
238        assert_eq!(FileSize::AtLeast(41).minimum_size(), Some(41));
239        assert_eq!(FileSize::Error.minimum_size(), None);
240    }
241}