Skip to main content

polars_ooc/
spill_frame.rs

1use std::fmt::Debug;
2use std::ops::{Deref, DerefMut};
3use std::sync::Arc;
4
5use polars_async::ASYNC;
6use polars_core::frame::DataFrame;
7use polars_io::ipc::{IpcCompression, IpcReader, IpcWriter};
8use polars_io::{SerReader, SerWriter};
9use polars_utils::compression::ZstdLevel;
10
11use crate::spill_context::ParameterFreeSpillContext;
12use crate::spill_file::SpillFile;
13use crate::{
14    BYTES_SPILLED_TO_DISK, PinnedMut, PinnedRef, SpillContextParam, SpillToken, Spillable,
15    WeakSpillContext, memory_manager,
16};
17
18impl Spillable for DataFrame {
19    type Spilled = Arc<SpillFile>;
20
21    fn estimate_byte_size(&self) -> usize {
22        self.estimated_size()
23    }
24
25    async fn spill(&self, context_id: &str) -> Self::Spilled {
26        let mut df = self.clone();
27        let context_id = context_id.to_owned();
28
29        // Encode in the current task (on computational executor).
30        let mut buf = Vec::new();
31
32        let mut writer = IpcWriter::new(&mut buf).with_parallel(false);
33        let clvl = polars_config::config().ooc_spill_compression_level();
34        if clvl > 0 {
35            let zstd_lvl = ZstdLevel::try_new(clvl.try_into().unwrap()).unwrap();
36            writer = writer.with_compression(Some(IpcCompression::ZSTD(zstd_lvl)));
37        }
38        writer
39            .finish(&mut df)
40            .unwrap_or_else(|e| panic!("failed to encode spill file for '{context_id}': {e}",));
41
42        // Do file creation / writing on tokio.
43        let file = ASYNC
44            .spawn(async move {
45                let size = buf.len() as u64;
46                let spill_file = SpillFile::new(&context_id, "ipc", size);
47                if BYTES_SPILLED_TO_DISK.fetch_add(size).saturating_add(size)
48                    > polars_config::config().ooc_disk_budget_bytes()
49                {
50                    spill_file.creation_aborted();
51                    polars_error::abort::polars_abort_ooc_out_of_disk();
52                }
53                match tokio::fs::write(spill_file.path(), buf).await {
54                    Ok(()) => spill_file,
55                    Err(e) => {
56                        let msg = format!(
57                            "failed to create spill file '{}': {e}",
58                            spill_file.path().display()
59                        );
60                        spill_file.creation_aborted();
61                        panic!("{msg}")
62                    },
63                }
64            })
65            .await
66            .unwrap();
67        Arc::new(file)
68    }
69
70    async fn unspill(location: &Self::Spilled) -> Self {
71        let path = location.path().to_owned();
72        ASYNC
73            .spawn_blocking(move || {
74                let file = std::fs::File::open(&path).unwrap_or_else(|e| {
75                    panic!("failed to open spill file {:?}: {e}", path.display())
76                });
77                IpcReader::new(file).finish().unwrap_or_else(|e| {
78                    panic!("failed to read spill file {:?}: {e}", path.display())
79                })
80            })
81            .await
82            .unwrap()
83    }
84}
85
86#[derive(Clone)]
87pub struct SpillFrame {
88    token: SpillToken<DataFrame>,
89    height: usize,
90}
91
92impl AsRef<SpillToken<DataFrame>> for SpillFrame {
93    fn as_ref(&self) -> &SpillToken<DataFrame> {
94        &self.token
95    }
96}
97
98impl SpillFrame {
99    pub fn new_unregistered(df: DataFrame) -> Self {
100        let height = df.height();
101        let token = SpillToken::new(df);
102        Self { token, height }
103    }
104
105    pub async fn new<C: ParameterFreeSpillContext>(df: DataFrame, ctx: &C) -> Self {
106        let slf = Self::new_unregistered(df);
107        ctx.register(&slf).await;
108        slf
109    }
110
111    pub fn new_blocking<C: ParameterFreeSpillContext>(df: DataFrame, ctx: &C) -> Self {
112        let slf = Self::new_unregistered(df);
113        ctx.register_no_spill_check(&slf);
114        memory_manager().spill_blocking();
115        slf
116    }
117
118    pub fn unregister(&mut self) -> Option<(WeakSpillContext, SpillContextParam)> {
119        self.token.unregister()
120    }
121
122    /// The height of the contained DataFrame. Does not need to unspill DataFrame.
123    pub fn height(&self) -> usize {
124        self.height
125    }
126
127    /// Get a reference to the underlying DataFrame, returning None if it was spilled.
128    pub fn try_get(&self) -> Option<PinnedRef<'_, DataFrame>> {
129        self.token.try_get()
130    }
131
132    /// Get a reference to the underlying DataFrame, unspilling it if it
133    /// was spilled.
134    pub async fn get(&self) -> PinnedRef<'_, DataFrame> {
135        self.token.get().await
136    }
137
138    /// Blocking version of get.
139    pub fn get_blocking(&self) -> PinnedRef<'_, DataFrame> {
140        self.token.get_blocking()
141    }
142
143    /// Get a mutable reference to the underlying DataFrame, unspilling it if it
144    /// was spilled.
145    pub async fn get_mut(&mut self) -> PinnedFrameMut<'_> {
146        PinnedFrameMut {
147            inner: self.token.get_mut().await,
148            height: &mut self.height,
149        }
150    }
151
152    /// Blocking version of get_mut.
153    pub fn get_mut_blocking(&mut self) -> PinnedFrameMut<'_> {
154        PinnedFrameMut {
155            inner: self.token.get_mut_blocking(),
156            height: &mut self.height,
157        }
158    }
159
160    /// Consumes this SpillFrame, unspilling it if it were spilled.
161    pub async fn into_df(self) -> DataFrame {
162        self.token.into_inner().await
163    }
164
165    /// Blocking version of into_df.
166    pub fn into_df_blocking(self) -> DataFrame {
167        self.token.into_inner_blocking()
168    }
169}
170
171impl Debug for SpillFrame {
172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173        let mut s = f.debug_struct("SpillFrame");
174        match self.token.try_get() {
175            Some(df) => s.field("df", &*df),
176            None => s.field("df", &"spilled"),
177        };
178        s.finish()
179    }
180}
181
182pub struct PinnedFrameMut<'a> {
183    height: &'a mut usize,
184    inner: PinnedMut<'a, DataFrame>,
185}
186
187impl<'a> Deref for PinnedFrameMut<'a> {
188    type Target = DataFrame;
189
190    fn deref(&self) -> &Self::Target {
191        &self.inner
192    }
193}
194
195impl<'a> DerefMut for PinnedFrameMut<'a> {
196    fn deref_mut(&mut self) -> &mut Self::Target {
197        &mut self.inner
198    }
199}
200
201impl<'a> Drop for PinnedFrameMut<'a> {
202    fn drop(&mut self) {
203        *self.height = self.inner.height();
204    }
205}