Skip to main content

shared_files/
writer.rs

1//! File writing functionality, notably the [`SharedFileWriter`] type.
2
3use crate::errors::{CompleteWritingError, WriteError};
4use crate::{FilePath, Sentinel, SharedFileType, WriteState};
5use crossbeam::atomic::AtomicCell;
6use pin_project::{pin_project, pinned_drop};
7use std::io::{Error, ErrorKind, IoSlice};
8use std::path::PathBuf;
9use std::pin::Pin;
10use std::sync::Arc;
11use std::task::{Context, Poll};
12use tokio::io;
13use tokio::io::AsyncWrite;
14
15/// A writer for the shared temporary file.
16///
17/// ## Dropping the writer
18///
19/// Note that while dropping the writer while implicitly change it to "completed",
20/// you must manually call [`SharedFileWriter::sync_all`] or [`SharedFileWriter::sync_data`]
21/// to ensure all content is flushed to the underlying buffer.
22#[pin_project(PinnedDrop)]
23pub struct SharedFileWriter<T> {
24    /// The file to write to.
25    #[pin]
26    file: T,
27    /// The sentinel value to keep the file alive.
28    sentinel: Arc<Sentinel<T>>,
29}
30
31impl<T> SharedFileWriter<T> {
32    pub(crate) fn new(file: T, sentinel: Arc<Sentinel<T>>) -> Self {
33        Self { file, sentinel }
34    }
35
36    /// Gets the file path.
37    pub fn file_path(&self) -> &PathBuf
38    where
39        T: FilePath,
40    {
41        self.file.file_path()
42    }
43
44    /// Synchronizes data and metadata with the disk buffer.
45    pub async fn sync_all(&self) -> Result<(), T::SyncError>
46    where
47        T: SharedFileType,
48    {
49        self.file.sync_all().await?;
50        Self::sync_committed_and_written(&self.sentinel);
51        self.sentinel.wake_readers();
52        Ok(())
53    }
54
55    /// Synchronizes data with the disk buffer.
56    pub async fn sync_data(&self) -> Result<(), T::SyncError>
57    where
58        T: SharedFileType,
59    {
60        self.file.sync_data().await?;
61        Self::sync_committed_and_written(&self.sentinel);
62        self.sentinel.wake_readers();
63        Ok(())
64    }
65
66    /// Completes the writing operation.
67    ///
68    /// Use [`complete_no_sync`](Self::complete_no_sync) if you do not wish
69    /// to sync the file to disk.
70    pub async fn complete(self) -> Result<(), CompleteWritingError>
71    where
72        T: SharedFileType,
73    {
74        if self.sync_all().await.is_err() {
75            return Err(CompleteWritingError::SyncError);
76        }
77        self.complete_no_sync()
78    }
79
80    /// Completes the writing operation.
81    ///
82    /// If you need to sync the file to disk, consider calling
83    /// [`complete`](Self::complete) instead.
84    pub fn complete_no_sync(self) -> Result<(), CompleteWritingError> {
85        self.finalize_state()
86    }
87
88    /// Synchronizes the number of written bytes with the number of committed bytes.
89    ///
90    /// The `load` + `store` here is not an atomic read-modify-write. It is only
91    /// sound because the crate's contract guarantees a single writer: the borrow
92    /// checker prevents a concurrent `&mut self` write and `&self` sync on the
93    /// same writer, so no other task mutates the state in between. Readers only
94    /// ever load the state, never store it.
95    fn sync_committed_and_written(sentinel: &Arc<Sentinel<T>>) {
96        match sentinel.state.load() {
97            WriteState::Pending(_committed, written) => {
98                sentinel.state.store(WriteState::Pending(written, written));
99            }
100            WriteState::Completed(_) => {}
101            WriteState::Failed => {}
102        }
103    }
104
105    /// Sets the state to finalized.
106    ///
107    /// See also [`update_state`](Self::update_state) for increasing the byte count.
108    fn finalize_state(&self) -> Result<(), CompleteWritingError> {
109        let result = match self.sentinel.state.load() {
110            WriteState::Pending(_committed, written) => {
111                // This is called from `Drop` (via `PinnedDrop`), so a hard
112                // `assert!` here could abort the process on a double panic.
113                // Mirror `poll_shutdown` and only check in debug builds.
114                debug_assert_eq!(
115                    _committed, written,
116                    "The number of committed bytes is less than the number of written bytes - call sync before dropping"
117                );
118                self.sentinel.state.store(WriteState::Completed(written));
119                Ok(())
120            }
121            WriteState::Completed(_) => Ok(()),
122            WriteState::Failed => Err(CompleteWritingError::FileWritingFailed),
123        };
124
125        self.sentinel.wake_readers();
126        result
127    }
128
129    /// Updates the internal byte count with the specified number of bytes written.
130    /// Will produce an error if the update failed.
131    ///
132    /// ## Returns
133    /// Returns the number of bytes written in total.
134    ///
135    /// See also [`finalize_state`](Self::finalize_state) for finalizing the write.
136    fn update_state(state: &AtomicCell<WriteState>, written: usize) -> Result<usize, Error> {
137        match state.load() {
138            WriteState::Pending(committed, previously_written) => {
139                let count = previously_written + written;
140                state.store(WriteState::Pending(committed, count));
141                Ok(count)
142            }
143            WriteState::Completed(count) => {
144                // Ensure we do not try to write more data after completing
145                // the file.
146                if written != 0 {
147                    return Err(Error::new(ErrorKind::BrokenPipe, WriteError::FileClosed));
148                }
149                Ok(count)
150            }
151            WriteState::Failed => Err(Error::from(ErrorKind::Other)),
152        }
153    }
154
155    /// Processes a [`Poll`] result from a write operation.
156    ///
157    /// This will update the internal byte count and produce an error
158    /// if the update failed.
159    fn handle_poll_write_result(
160        sentinel: &Sentinel<T>,
161        poll: Poll<Result<usize, Error>>,
162    ) -> Poll<Result<usize, Error>> {
163        match poll {
164            Poll::Ready(result) => match result {
165                Ok(written) => match Self::update_state(&sentinel.state, written) {
166                    Ok(_) => Poll::Ready(Ok(written)),
167                    Err(e) => Poll::Ready(Err(e)),
168                },
169                Err(e) => {
170                    sentinel.state.store(WriteState::Failed);
171                    sentinel.wake_readers();
172                    Poll::Ready(Err(e))
173                }
174            },
175            Poll::Pending => Poll::Pending,
176        }
177    }
178}
179
180#[pinned_drop]
181impl<T> PinnedDrop for SharedFileWriter<T> {
182    fn drop(mut self: Pin<&mut Self>) {
183        self.finalize_state().ok();
184    }
185}
186
187impl<T> AsyncWrite for SharedFileWriter<T>
188where
189    T: AsyncWrite,
190{
191    fn poll_write(
192        self: Pin<&mut Self>,
193        cx: &mut Context<'_>,
194        buf: &[u8],
195    ) -> Poll<io::Result<usize>> {
196        let this = self.project();
197        let poll = this.file.poll_write(cx, buf);
198        Self::handle_poll_write_result(this.sentinel, poll)
199    }
200
201    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
202        let this = self.project();
203        match this.file.poll_flush(cx) {
204            Poll::Ready(result) => match result {
205                Ok(()) => {
206                    Self::sync_committed_and_written(this.sentinel);
207                    this.sentinel.wake_readers();
208                    Poll::Ready(Ok(()))
209                }
210                Err(e) => {
211                    this.sentinel.state.store(WriteState::Failed);
212                    this.sentinel.wake_readers();
213                    Poll::Ready(Err(e))
214                }
215            },
216            Poll::Pending => Poll::Pending,
217        }
218    }
219
220    fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
221        let this = self.project();
222        match this.file.poll_shutdown(cx) {
223            Poll::Ready(result) => match result {
224                Ok(()) => {
225                    if let WriteState::Pending(_committed, written) = this.sentinel.state.load() {
226                        debug_assert_eq!(_committed, written);
227                        this.sentinel.state.store(WriteState::Completed(written));
228                    }
229
230                    // Readers parked in `Pending` must be woken so they can
231                    // observe the completed state instead of hanging forever.
232                    this.sentinel.wake_readers();
233                    Poll::Ready(Ok(()))
234                }
235                Err(e) => {
236                    this.sentinel.state.store(WriteState::Failed);
237                    this.sentinel.wake_readers();
238                    Poll::Ready(Err(e))
239                }
240            },
241            Poll::Pending => Poll::Pending,
242        }
243    }
244
245    fn poll_write_vectored(
246        self: Pin<&mut Self>,
247        cx: &mut Context<'_>,
248        bufs: &[IoSlice<'_>],
249    ) -> Poll<Result<usize, Error>> {
250        let this = self.project();
251        let poll = this.file.poll_write_vectored(cx, bufs);
252        Self::handle_poll_write_result(this.sentinel, poll)
253    }
254
255    fn is_write_vectored(&self) -> bool {
256        self.file.is_write_vectored()
257    }
258}