Skip to main content

parallel_processor/memory_fs/file/
internal.rs

1use crate::memory_fs::allocator::{AllocatedChunk, CHUNKS_ALLOCATOR};
2use crate::memory_fs::file::flush::GlobalFlush;
3use crate::memory_fs::flushable_buffer::{FileFlushMode, FlushableItem};
4use crate::memory_fs::stats;
5use dashmap::DashMap;
6use once_cell::sync::Lazy;
7use parking_lot::lock_api::{ArcRwLockReadGuard, ArcRwLockWriteGuard};
8use parking_lot::{Mutex, RawRwLock, RwLock};
9use replace_with::replace_with_or_abort;
10use rustc_hash::FxHashMap;
11use std::cmp::min;
12use std::collections::BinaryHeap;
13use std::fs::{remove_file, File};
14use std::io;
15use std::ops::Deref;
16use std::path::{Path, PathBuf};
17use std::ptr::copy_nonoverlapping;
18use std::sync::atomic::{AtomicBool, Ordering};
19use std::sync::{Arc, LazyLock, Weak};
20
21use super::handle::FileHandle;
22
23static MEMORY_MAPPED_FILES: Lazy<DashMap<PathBuf, Arc<RwLock<MemoryFileInternal>>>> =
24    Lazy::new(|| DashMap::new());
25
26#[derive(Default)]
27pub struct SwappableFiles {
28    index: FxHashMap<PathBuf, Weak<RwLock<MemoryFileInternal>>>,
29    queue: BinaryHeap<(usize, PathBuf)>,
30}
31
32impl SwappableFiles {
33    pub fn add_file(
34        &mut self,
35        priority: usize,
36        path: PathBuf,
37        file: Weak<RwLock<MemoryFileInternal>>,
38    ) {
39        self.index.insert(path.clone(), file);
40        self.queue.push((priority, path));
41    }
42
43    pub fn remove_file(&mut self, path: &Path) {
44        self.index.remove(path);
45    }
46
47    pub fn get_next(&mut self) -> Option<Arc<RwLock<MemoryFileInternal>>> {
48        while let Some((_, file)) = self.queue.pop() {
49            if let Some(file_entry) = self.index.remove(&file) {
50                if let Some(file_entry) = file_entry.upgrade() {
51                    let file_read = file_entry.read();
52                    if file_read.is_memory_preferred() && file_read.has_flush_pending_chunks() {
53                        drop(file_read);
54                        return Some(file_entry);
55                    } else {
56                        // Remove the file if it cannot be swapped
57                        file_read.on_swap_list.store(false, Ordering::Relaxed);
58                    }
59                }
60            }
61        }
62
63        None
64    }
65}
66
67pub static SWAPPABLE_FILES: LazyLock<Mutex<SwappableFiles>> =
68    LazyLock::new(|| Mutex::new(Default::default()));
69
70#[derive(Copy, Clone, Eq, PartialEq, Debug)]
71pub enum MemoryFileMode {
72    AlwaysMemory,
73    PreferMemory { swap_priority: usize },
74    DiskOnly,
75}
76
77#[derive(Copy, Clone, Eq, PartialEq)]
78pub enum OpenMode {
79    None,
80    Read,
81    Write,
82}
83
84// struct RwLockIterator<'a, A, B, I: Iterator<Item = B>> {
85//     _lock: RwLockReadGuard<'a, A>,
86//     iterator: I,
87// }
88
89// impl<'a, A, B, I: Iterator<Item = B>> Iterator for RwLockIterator<'a, A, B, I> {
90//     type Item = B;
91
92//     fn next(&mut self) -> Option<Self::Item> {
93//         self.iterator.next()
94//     }
95// }
96
97// pub struct MemoryFileChunksIterator<'a, I: Iterator<Item = &'a mut [u8]>> {
98//     iter: I,
99// }
100
101// impl<'a, I: Iterator<Item = &'a mut [u8]>> Iterator for MemoryFileChunksIterator<'a, I> {
102//     type Item = &'a mut [u8];
103
104//     #[inline(always)]
105//     fn next(&mut self) -> Option<Self::Item> {
106//         self.iter.next()
107//     }
108// }
109
110#[cfg(unix)]
111fn read_at_cross_platform(file: &File, buf: &mut [u8], offset: u64) -> io::Result<usize> {
112    use std::os::unix::fs::FileExt;
113    file.read_at(buf, offset)
114}
115
116#[cfg(windows)]
117fn read_at_cross_platform(file: &File, buf: &mut [u8], offset: u64) -> io::Result<usize> {
118    use std::os::windows::fs::FileExt;
119    file.seek_read(buf, offset)
120}
121
122pub enum FileChunk {
123    OnDisk { offset: u64, len: usize },
124    OnMemory { chunk: AllocatedChunk },
125}
126
127impl FileChunk {
128    pub fn get_length(&self) -> usize {
129        match self {
130            FileChunk::OnDisk { len, .. } => *len,
131            FileChunk::OnMemory { chunk } => chunk.len(),
132        }
133    }
134
135    pub fn is_on_disk(&self) -> bool {
136        matches!(self, FileChunk::OnDisk { .. })
137    }
138
139    pub fn read_at(
140        &self,
141        file: &UnderlyingFile,
142        relative_position: u64,
143        buffer: &mut [u8],
144    ) -> io::Result<usize> {
145        match self {
146            FileChunk::OnDisk { offset, len } => {
147                if let UnderlyingFile::ReadMode(file) = file {
148                    let copyable_bytes = buffer
149                        .len()
150                        .min(len.saturating_sub(relative_position as usize));
151
152                    let file = file.as_ref().unwrap();
153
154                    read_at_cross_platform(
155                        file,
156                        &mut buffer[..copyable_bytes],
157                        *offset + relative_position,
158                    )
159                } else {
160                    panic!("Error, wrong underlying file!");
161                }
162            }
163            FileChunk::OnMemory { chunk } => {
164                let copyable_bytes = buffer
165                    .len()
166                    .min(chunk.len().saturating_sub(relative_position as usize));
167                unsafe {
168                    copy_nonoverlapping(
169                        chunk.get_mut_ptr().add(relative_position as usize),
170                        buffer.as_mut_ptr(),
171                        copyable_bytes,
172                    );
173                }
174                Ok(copyable_bytes)
175            }
176        }
177    }
178}
179
180pub enum UnderlyingFile {
181    NotOpened,
182    MemoryOnly,
183    MemoryPreferred,
184    WriteMode {
185        file: Arc<Mutex<FileHandle>>,
186        chunk_position: usize,
187    },
188    ReadMode(Option<File>),
189}
190
191pub struct MemoryFileInternal {
192    /// Path associated with the current file
193    path: PathBuf,
194    /// Disk read/write structure
195    file: UnderlyingFile,
196    /// Memory mode
197    memory_mode: MemoryFileMode,
198    /// Read or write mode
199    open_mode: (OpenMode, usize),
200    /// Actual memory mapping
201    memory: Vec<Arc<RwLock<FileChunk>>>,
202    /// True if it's in the swap list
203    on_swap_list: AtomicBool,
204    /// True if more chunks can be flushed
205    can_flush: bool,
206}
207
208impl MemoryFileInternal {
209    pub fn create_new(path: impl AsRef<Path>, memory_mode: MemoryFileMode) -> Arc<RwLock<Self>> {
210        let new_file = Arc::new(RwLock::new(Self {
211            path: path.as_ref().into(),
212            file: UnderlyingFile::NotOpened,
213            memory_mode,
214            open_mode: (OpenMode::None, 0),
215            memory: Vec::new(),
216            on_swap_list: AtomicBool::new(false),
217            can_flush: true,
218        }));
219
220        MEMORY_MAPPED_FILES.insert(path.as_ref().into(), new_file.clone());
221
222        new_file
223    }
224
225    pub fn create_from_fs(path: impl AsRef<Path>) -> Option<Arc<RwLock<Self>>> {
226        if !path.as_ref().exists() || !path.as_ref().is_file() {
227            return None;
228        }
229        let len = path.as_ref().metadata().ok()?.len() as usize;
230
231        let new_file = Arc::new(RwLock::new(Self {
232            path: path.as_ref().into(),
233            file: UnderlyingFile::NotOpened,
234            memory_mode: MemoryFileMode::DiskOnly,
235            open_mode: (OpenMode::None, 0),
236            memory: vec![Arc::new(RwLock::new(FileChunk::OnDisk { offset: 0, len }))],
237            on_swap_list: AtomicBool::new(false),
238            can_flush: false,
239        }));
240
241        MEMORY_MAPPED_FILES.insert(path.as_ref().into(), new_file.clone());
242
243        Some(new_file)
244    }
245
246    pub fn debug_dump_files() {
247        for file in MEMORY_MAPPED_FILES.iter() {
248            let file = file.read();
249            crate::log_info!(
250                "File '{}' => chunks: {}",
251                file.path.display(),
252                file.memory.len()
253            );
254        }
255    }
256
257    pub fn is_on_disk(&self) -> bool {
258        self.memory_mode == MemoryFileMode::DiskOnly
259    }
260
261    pub fn is_memory_preferred(&self) -> bool {
262        if let MemoryFileMode::PreferMemory { .. } = self.memory_mode {
263            true
264        } else {
265            false
266        }
267    }
268
269    pub fn get_chunk(&self, index: usize) -> Arc<RwLock<FileChunk>> {
270        self.memory[index].clone()
271    }
272
273    pub fn get_chunks_count(&self) -> usize {
274        self.memory.len()
275    }
276
277    pub fn retrieve_reference(path: impl AsRef<Path>) -> Option<Arc<RwLock<Self>>> {
278        MEMORY_MAPPED_FILES.get(path.as_ref()).map(|f| f.clone())
279    }
280
281    pub fn active_files_count() -> usize {
282        MEMORY_MAPPED_FILES.len()
283    }
284
285    pub fn flush_all_to_disk() {
286        for file in MEMORY_MAPPED_FILES.iter() {
287            let mut file = file.write();
288            file.change_to_disk_only();
289            file.flush_chunks(usize::MAX);
290        }
291    }
292
293    pub fn delete(path: impl AsRef<Path>, remove_fs: bool) -> bool {
294        if let Some(file) = MEMORY_MAPPED_FILES.remove(path.as_ref()) {
295            stats::decrease_files_usage(file.1.read().len() as u64);
296            if remove_fs {
297                match file.1.read().memory_mode {
298                    MemoryFileMode::AlwaysMemory => {}
299                    MemoryFileMode::PreferMemory { .. } => {
300                        SWAPPABLE_FILES.lock().remove_file(path.as_ref())
301                    }
302                    MemoryFileMode::DiskOnly => {
303                        if let Ok(file_meta) = std::fs::metadata(path.as_ref()) {
304                            stats::decrease_disk_usage(file_meta.len());
305                        }
306                        let _ = remove_file(path);
307                    }
308                }
309            }
310            true
311        } else {
312            false
313        }
314    }
315
316    pub fn delete_directory(dir: impl AsRef<Path>, remove_fs: bool) -> bool {
317        let mut all_succeeded = true;
318        let mut to_delete = vec![];
319        for file in MEMORY_MAPPED_FILES.iter() {
320            if file.key().starts_with(&dir) {
321                to_delete.push(file.key().clone());
322            }
323        }
324
325        for file in to_delete {
326            all_succeeded &= Self::delete(&file, remove_fs);
327        }
328
329        all_succeeded
330    }
331
332    fn create_writing_underlying_file(path: &Path) -> UnderlyingFile {
333        // Remove the file if it existed from a previous run
334        let _ = remove_file(path);
335
336        UnderlyingFile::WriteMode {
337            file: Arc::new(Mutex::new(FileHandle::new(path.to_path_buf()))),
338            chunk_position: 0,
339        }
340    }
341
342    pub fn open(&mut self, mode: OpenMode) -> Result<(), String> {
343        if self.open_mode.0 == mode {
344            self.open_mode.1 += 1;
345            return Ok(());
346        }
347
348        if self.open_mode.0 != OpenMode::None {
349            return Err(format!("File {} is already opened!", self.path.display()));
350        }
351
352        {
353            let mut error = None;
354            replace_with_or_abort(&mut self.file, |file| {
355                match mode {
356                    OpenMode::None => UnderlyingFile::NotOpened,
357                    OpenMode::Read => {
358                        self.open_mode = (OpenMode::Read, 1);
359                        self.can_flush = false;
360
361                        if self.memory_mode != MemoryFileMode::DiskOnly {
362                            UnderlyingFile::MemoryOnly
363                        } else {
364                            // Ensure that all chunks are not pending
365                            for chunk in self.memory.iter() {
366                                drop(chunk.read());
367                            }
368
369                            if let UnderlyingFile::WriteMode { file, .. } = file {
370                                file.lock().flush().unwrap();
371                            }
372
373                            UnderlyingFile::ReadMode(
374                                File::open(&self.path)
375                                    .inspect_err(|e| {
376                                        error = Some(format!(
377                                            "Error while opening file {}: {}",
378                                            self.path.display(),
379                                            e
380                                        ));
381                                    })
382                                    .ok(),
383                            )
384                        }
385                    }
386                    OpenMode::Write => {
387                        self.open_mode = (OpenMode::Write, 1);
388                        match self.memory_mode {
389                            MemoryFileMode::AlwaysMemory => UnderlyingFile::MemoryOnly,
390                            MemoryFileMode::PreferMemory { .. } => UnderlyingFile::MemoryPreferred,
391                            MemoryFileMode::DiskOnly => {
392                                Self::create_writing_underlying_file(&self.path)
393                            }
394                        }
395                    }
396                }
397            });
398            if let Some(error) = error {
399                return Err(error);
400            }
401        }
402
403        Ok(())
404    }
405
406    pub fn close(&mut self) {
407        self.open_mode.1 -= 1;
408
409        if self.open_mode.1 == 0 {
410            self.open_mode.0 = OpenMode::None;
411            match &self.file {
412                UnderlyingFile::WriteMode { file, .. } => {
413                    file.lock().flush().unwrap();
414                }
415                _ => {}
416            }
417        }
418    }
419
420    fn put_on_swappable_list(self_: &ArcRwLockWriteGuard<RawRwLock, Self>) {
421        if let MemoryFileMode::PreferMemory { swap_priority } = self_.memory_mode {
422            if !self_.on_swap_list.swap(true, Ordering::Relaxed) {
423                SWAPPABLE_FILES.lock().add_file(
424                    swap_priority,
425                    self_.path.clone(),
426                    Arc::downgrade(ArcRwLockWriteGuard::rwlock(self_)),
427                );
428            }
429        }
430    }
431
432    pub fn reserve_space(
433        self_: &Arc<RwLock<Self>>,
434        last_chunk: AllocatedChunk,
435        out_chunks: &mut Vec<(Option<ArcRwLockReadGuard<RawRwLock, FileChunk>>, &mut [u8])>,
436        mut size: usize,
437        el_size: usize,
438    ) -> AllocatedChunk {
439        let mut chunk = last_chunk;
440
441        loop {
442            let rem_bytes = chunk.remaining_bytes();
443            let rem_elements = rem_bytes / el_size;
444            let el_bytes = min(size, rem_elements * el_size);
445
446            assert!(chunk.max_len() >= el_size);
447
448            let space = if el_bytes > 0 {
449                Some(unsafe { chunk.prealloc_bytes_single_thread(el_bytes) })
450            } else {
451                None
452            };
453
454            size -= el_bytes;
455
456            let mut self_ = self_.write_arc();
457
458            if size > 0 {
459                self_
460                    .memory
461                    .push(Arc::new(RwLock::new(FileChunk::OnMemory { chunk })));
462
463                if let Some(space) = space {
464                    let chunk_guard = self_.memory.last().unwrap().read_arc();
465                    out_chunks.push((Some(chunk_guard), space));
466                }
467                Self::put_on_swappable_list(&self_);
468
469                drop(self_);
470                chunk = CHUNKS_ALLOCATOR.request_chunk(chunk_usage!(FileBuffer {
471                    path: std::panic::Location::caller().to_string()
472                }));
473            } else {
474                if let Some(space) = space {
475                    out_chunks.push((None, space));
476                }
477                return chunk;
478            }
479        }
480    }
481
482    pub fn get_underlying_file(&self) -> &UnderlyingFile {
483        &self.file
484    }
485
486    pub fn add_chunk(self_: &Arc<RwLock<Self>>, chunk: AllocatedChunk) {
487        let mut self_ = self_.write_arc();
488        self_
489            .memory
490            .push(Arc::new(RwLock::new(FileChunk::OnMemory { chunk })));
491        Self::put_on_swappable_list(&self_);
492    }
493
494    pub fn flush_chunks(&mut self, limit: usize) -> usize {
495        if !self.can_flush {
496            return 0;
497        }
498
499        match &self.file {
500            UnderlyingFile::NotOpened | UnderlyingFile::MemoryPreferred => {
501                self.file = Self::create_writing_underlying_file(&self.path);
502            }
503            _ => {}
504        }
505
506        if let UnderlyingFile::WriteMode {
507            file,
508            chunk_position,
509        } = &mut self.file
510        {
511            {
512                let mut flushed_count = 0;
513                while flushed_count < limit {
514                    if *chunk_position >= self.memory.len() {
515                        return flushed_count;
516                    }
517
518                    if let Some(flushable_chunk) = self.memory[*chunk_position].try_write_arc() {
519                        GlobalFlush::add_item_to_flush_queue(FlushableItem {
520                            underlying_file: file.clone(),
521                            mode: FileFlushMode::Append {
522                                chunk: flushable_chunk,
523                            },
524                        });
525                        *chunk_position += 1;
526                        flushed_count += 1;
527                    } else {
528                        return flushed_count;
529                    }
530                }
531                return flushed_count;
532            }
533        }
534
535        return 0;
536    }
537
538    pub fn flush_pending_chunks_count(&self) -> usize {
539        match &self.file {
540            UnderlyingFile::NotOpened
541            | UnderlyingFile::MemoryOnly
542            | UnderlyingFile::ReadMode(_) => 0,
543            UnderlyingFile::WriteMode { chunk_position, .. } => {
544                self.get_chunks_count() - *chunk_position
545            }
546            UnderlyingFile::MemoryPreferred => self.get_chunks_count(),
547        }
548    }
549
550    #[inline(always)]
551    pub fn has_flush_pending_chunks(&self) -> bool {
552        self.flush_pending_chunks_count() > 0
553    }
554
555    pub fn change_to_disk_only(&mut self) {
556        if self.is_memory_preferred() {
557            self.memory_mode = MemoryFileMode::DiskOnly;
558            self.file = Self::create_writing_underlying_file(&self.path);
559        }
560    }
561
562    #[inline(always)]
563    pub fn has_only_one_chunk(&self) -> bool {
564        self.memory.len() == 1
565    }
566
567    #[inline(always)]
568    pub fn get_path(&self) -> &Path {
569        self.path.as_ref()
570    }
571
572    #[inline(always)]
573    pub fn len(&self) -> usize {
574        self.memory
575            .iter()
576            .map(|x| match x.read().deref() {
577                FileChunk::OnDisk { len, .. } => *len,
578                FileChunk::OnMemory { chunk } => chunk.len(),
579            })
580            .sum::<usize>()
581    }
582}
583
584unsafe impl Sync for MemoryFileInternal {}
585unsafe impl Send for MemoryFileInternal {}