Skip to content

Commit b1f6fd6

Browse files
Allocate files with posix_fallocate.
File::set_len() does not immediately allocate space for the file resulting in potential failures when accessing those files at a later point (e.g., if the disk is full). This functionality can be tested by creating an arbitrarily small tmpfs mount and creating state/data files within it: ```sh sudo mount -t tmpfs -o size=4 tmpfs /tmp/limit ``` As a side effect, this creates more useful error messages: ```rust FailedStateRead(Os { code: 28, kind: StorageFull, message: "No space left on device" }) ````
1 parent 7b30711 commit b1f6fd6

4 files changed

Lines changed: 23 additions & 4 deletions

File tree

src/data.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::os::unix::fs::OpenOptionsExt;
88
use crate::instance::InstanceVersion;
99
use crate::synchronizer::SynchronizerError;
1010
use crate::synchronizer::SynchronizerError::*;
11+
use crate::utils;
1112

1213
/// Data container stores memory mapped data files allowing
1314
/// to switch between them when data instance version is changed
@@ -60,7 +61,7 @@ impl DataContainer {
6061
// grow data file when its current length exceeded
6162
let data_len = data.len() as u64;
6263
if data_len > data_file.metadata().map_err(FailedDataWrite)?.len() {
63-
data_file.set_len(data_len).map_err(FailedDataWrite)?;
64+
utils::set_len(&data_file, data_len as i64).map_err(FailedDataWrite)?;
6465
}
6566

6667
*mmap = Some(unsafe { MmapMut::map_mut(&data_file).map_err(FailedDataWrite)? });

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ pub mod instance;
1616
pub mod locks;
1717
mod state;
1818
pub mod synchronizer;
19+
mod utils;

src/state.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::instance::InstanceVersion;
1313
use crate::locks::WriteLockStrategy;
1414
use crate::synchronizer::SynchronizerError;
1515
use crate::synchronizer::SynchronizerError::*;
16+
use crate::utils;
1617

1718
const STATE_SIZE: usize = mem::size_of::<State>();
1819

@@ -162,9 +163,7 @@ impl<'a, WL: WriteLockStrategy<'a>> StateContainer<WL> {
162163
let mut need_init = false;
163164
// Reset state file size to match exactly `STATE_SIZE`
164165
if state_file.metadata().map_err(FailedStateRead)?.len() != STATE_SIZE as u64 {
165-
state_file
166-
.set_len(STATE_SIZE as u64)
167-
.map_err(FailedStateRead)?;
166+
utils::set_len(&state_file, STATE_SIZE as i64).map_err(FailedStateRead)?;
168167
need_init = true;
169168
}
170169

src/utils.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use std::fs::File;
2+
use std::io;
3+
#[cfg(unix)]
4+
use std::os::fd::AsRawFd;
5+
6+
/// Set the length of the file to the specified length.
7+
pub(crate) fn set_len(file: &File, len: i64) -> Result<(), io::Error> {
8+
// On Unix platforms, `.set_len()` may not return an error of the disk is full, so we allocate
9+
// the entire file to ensure space is available.
10+
#[cfg(unix)]
11+
match unsafe { libc::posix_fallocate(file.as_raw_fd(), 0, len) } {
12+
0 => Ok(()),
13+
err => Err(io::Error::from_raw_os_error(err)),
14+
}
15+
// Support for non-Linux platforms is best-effort.
16+
#[cfg(not(unix))]
17+
data_file.set_len(STATE_SIZE).map_err(FailedStateRead)
18+
}

0 commit comments

Comments
 (0)