polars_ooc/
spill_frame.rs1use 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 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 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 pub fn height(&self) -> usize {
124 self.height
125 }
126
127 pub fn try_get(&self) -> Option<PinnedRef<'_, DataFrame>> {
129 self.token.try_get()
130 }
131
132 pub async fn get(&self) -> PinnedRef<'_, DataFrame> {
135 self.token.get().await
136 }
137
138 pub fn get_blocking(&self) -> PinnedRef<'_, DataFrame> {
140 self.token.get_blocking()
141 }
142
143 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 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 pub async fn into_df(self) -> DataFrame {
162 self.token.into_inner().await
163 }
164
165 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}