parity_db/
file.rs

1// Copyright 2021-2022 Parity Technologies (UK) Ltd.
2// This file is dual-licensed as Apache-2.0 or MIT.
3
4//! Utilities for db file.
5
6use crate::{
7	error::{try_io, Result},
8	parking_lot::RwLock,
9	table::TableId,
10};
11use std::sync::atomic::{AtomicU64, Ordering};
12
13trait OpenOptionsExt {
14	fn disable_read_ahead(&mut self) -> &mut Self;
15}
16
17impl OpenOptionsExt for std::fs::OpenOptions {
18	#[cfg(not(windows))]
19	fn disable_read_ahead(&mut self) -> &mut Self {
20		// Not supported
21		self
22	}
23
24	#[cfg(windows)]
25	fn disable_read_ahead(&mut self) -> &mut Self {
26		use std::os::windows::fs::OpenOptionsExt;
27		self.custom_flags(winapi::um::winbase::FILE_FLAG_RANDOM_ACCESS)
28	}
29}
30
31#[cfg(target_os = "linux")]
32fn disable_read_ahead(file: &std::fs::File) -> std::io::Result<()> {
33	use std::os::unix::io::AsRawFd;
34	let err = unsafe { libc::posix_fadvise(file.as_raw_fd(), 0, 0, libc::POSIX_FADV_RANDOM) };
35	if err != 0 {
36		Err(std::io::Error::from_raw_os_error(err))
37	} else {
38		Ok(())
39	}
40}
41
42#[cfg(target_os = "macos")]
43fn disable_read_ahead(file: &std::fs::File) -> std::io::Result<()> {
44	use std::os::unix::io::AsRawFd;
45	if unsafe { libc::fcntl(file.as_raw_fd(), libc::F_RDAHEAD, 0) } != 0 {
46		Err(std::io::Error::last_os_error())
47	} else {
48		Ok(())
49	}
50}
51
52#[cfg(not(any(target_os = "macos", target_os = "linux")))]
53fn disable_read_ahead(_file: &std::fs::File) -> std::io::Result<()> {
54	Ok(())
55}
56
57#[cfg(unix)]
58pub fn madvise_random(map: &mut memmap2::MmapMut) {
59	unsafe {
60		libc::madvise(map.as_mut_ptr() as _, map.len(), libc::MADV_RANDOM);
61	}
62}
63
64#[cfg(not(unix))]
65pub fn madvise_random(_map: &mut memmap2::MmapMut) {}
66
67#[cfg(not(windows))]
68fn mmap(file: &std::fs::File, len: usize) -> Result<memmap2::MmapMut> {
69	#[cfg(not(test))]
70	const RESERVE_ADDRESS_SPACE: usize = 1024 * 1024 * 1024; // 1 Gb
71														 // Use a different value for tests to work around docker limits on the test machine.
72	#[cfg(test)]
73	const RESERVE_ADDRESS_SPACE: usize = 64 * 1024 * 1024; // 64 Mb
74
75	let map_len = len + RESERVE_ADDRESS_SPACE;
76	let mut map = try_io!(unsafe { memmap2::MmapOptions::new().len(map_len).map_mut(file) });
77	madvise_random(&mut map);
78	Ok(map)
79}
80
81#[cfg(windows)]
82fn mmap(file: &std::fs::File, _len: usize) -> Result<memmap2::MmapMut> {
83	Ok(try_io!(unsafe { memmap2::MmapOptions::new().map_mut(file) }))
84}
85
86const GROW_SIZE_BYTES: u64 = 256 * 1024;
87
88#[derive(Debug)]
89pub struct TableFile {
90	pub map: RwLock<Option<(memmap2::MmapMut, std::fs::File)>>,
91	pub path: std::path::PathBuf,
92	pub capacity: AtomicU64,
93	pub id: TableId,
94}
95
96impl TableFile {
97	pub fn open(filepath: std::path::PathBuf, entry_size: u16, id: TableId) -> Result<Self> {
98		let mut capacity = 0u64;
99		let map = if std::fs::metadata(&filepath).is_ok() {
100			let file = try_io!(std::fs::OpenOptions::new()
101				.read(true)
102				.write(true)
103				.disable_read_ahead()
104				.open(filepath.as_path()));
105			try_io!(disable_read_ahead(&file));
106			let len = try_io!(file.metadata()).len();
107			if len == 0 {
108				// Preallocate.
109				capacity += GROW_SIZE_BYTES / entry_size as u64;
110				try_io!(file.set_len(GROW_SIZE_BYTES));
111			} else {
112				capacity = len / entry_size as u64;
113			}
114			let map = mmap(&file, len as usize)?;
115			Some((map, file))
116		} else {
117			None
118		};
119		Ok(TableFile {
120			path: filepath,
121			map: RwLock::new(map),
122			capacity: AtomicU64::new(capacity),
123			id,
124		})
125	}
126
127	fn create_file(&self) -> Result<std::fs::File> {
128		log::debug!(target: "parity-db", "Created value table {}", self.id);
129		let file = try_io!(std::fs::OpenOptions::new()
130			.create(true)
131			.read(true)
132			.write(true)
133			.disable_read_ahead()
134			.open(self.path.as_path()));
135		try_io!(disable_read_ahead(&file));
136		Ok(file)
137	}
138
139	pub fn read_at(&self, buf: &mut [u8], offset: u64) -> Result<()> {
140		let offset = offset as usize;
141		let map = self.map.read();
142		let (map, _) = map.as_ref().unwrap();
143		buf.copy_from_slice(&map[offset..offset + buf.len()]);
144		Ok(())
145	}
146
147	#[cfg(not(feature = "loom"))]
148	pub fn slice_at(&self, offset: u64, len: usize) -> MappedBytesGuard {
149		let offset = offset as usize;
150		let map = self.map.read();
151		parking_lot::RwLockReadGuard::map(map, |map| {
152			let (map, _) = map.as_ref().unwrap();
153			&map[offset..offset + len]
154		})
155	}
156
157	#[cfg(feature = "loom")]
158	pub fn slice_at(&self, offset: u64, len: usize) -> MappedBytesGuard {
159		let offset = offset as usize;
160		let map = self.map.read();
161		let (map, _) = map.as_ref().unwrap();
162		MappedBytesGuard::new(map[offset..offset + len].to_vec())
163	}
164
165	pub fn write_at(&self, buf: &[u8], offset: u64) -> Result<()> {
166		let map = self.map.read();
167		let (map, _) = map.as_ref().unwrap();
168		let offset = offset as usize;
169
170		// Nasty mutable pointer cast. We do ensure that all chunks that are being written are
171		// accessed through the overlay in other threads.
172		let ptr: *mut u8 = map.as_ptr() as *mut u8;
173		let data: &mut [u8] = unsafe {
174			let ptr = ptr.add(offset);
175			std::slice::from_raw_parts_mut(ptr, buf.len())
176		};
177		data.copy_from_slice(buf);
178		Ok(())
179	}
180
181	pub fn grow(&self, entry_size: u16) -> Result<()> {
182		let mut map_and_file = self.map.write();
183		let new_len = match map_and_file.as_mut() {
184			None => {
185				let file = self.create_file()?;
186				let len = GROW_SIZE_BYTES;
187				try_io!(file.set_len(len));
188				let map = mmap(&file, 0)?;
189				*map_and_file = Some((map, file));
190				len
191			},
192			Some((map, file)) => {
193				let new_len = try_io!(file.metadata()).len() + GROW_SIZE_BYTES;
194				try_io!(file.set_len(new_len));
195				if map.len() < new_len as usize {
196					let new_map = mmap(&file, new_len as usize)?;
197					let old_map = std::mem::replace(map, new_map);
198					try_io!(old_map.flush());
199				}
200				new_len
201			},
202		};
203		let capacity = new_len / entry_size as u64;
204		self.capacity.store(capacity, Ordering::Relaxed);
205		Ok(())
206	}
207
208	pub fn flush(&self) -> Result<()> {
209		if let Some((map, _)) = self.map.read().as_ref() {
210			try_io!(map.flush());
211		}
212		Ok(())
213	}
214
215	pub fn remove(&self) -> Result<()> {
216		let mut map = self.map.write();
217		if let Some((map, file)) = map.take() {
218			drop(map);
219			drop(file);
220			try_io!(std::fs::remove_file(&self.path));
221		}
222		Ok(())
223	}
224}
225
226// Loom is missing support for guard projection, so we copy the data as a workaround.
227#[cfg(feature = "loom")]
228pub struct MappedBytesGuard<'a> {
229	_phantom: std::marker::PhantomData<&'a ()>,
230	data: Vec<u8>,
231}
232
233#[cfg(feature = "loom")]
234impl<'a> MappedBytesGuard<'a> {
235	pub fn new(data: Vec<u8>) -> Self {
236		Self { _phantom: std::marker::PhantomData, data }
237	}
238}
239
240#[cfg(feature = "loom")]
241impl<'a> std::ops::Deref for MappedBytesGuard<'a> {
242	type Target = [u8];
243
244	fn deref(&self) -> &Self::Target {
245		self.data.as_slice()
246	}
247}
248
249#[cfg(not(feature = "loom"))]
250pub type MappedBytesGuard<'a> = parking_lot::MappedRwLockReadGuard<'a, [u8]>;