parallel_processor/memory_fs/file/
internal.rs1use 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 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#[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: PathBuf,
194 file: UnderlyingFile,
196 memory_mode: MemoryFileMode,
198 open_mode: (OpenMode, usize),
200 memory: Vec<Arc<RwLock<FileChunk>>>,
202 on_swap_list: AtomicBool,
204 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 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 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 {}