Skip to content

Commit b0e738e

Browse files
authored
Merge pull request #3065 from autonomys/direct-io-linux-macos-backport
Backport: Direct I/O on Linux and macOS
2 parents 84ea07b + 8169001 commit b0e738e

File tree

9 files changed

+139
-198
lines changed

9 files changed

+139
-198
lines changed

Diff for: crates/subspace-farmer-components/src/file_ext.rs

+45-26
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,20 @@ pub trait OpenOptionsExt {
99
/// undesirable, only has impact on Windows, for other operating systems see [`FileExt`]
1010
fn advise_random_access(&mut self) -> &mut Self;
1111

12-
/// Advise Windows to not use buffering for this file and that file access will be random.
13-
///
14-
/// NOTE: There are major alignment requirements described here:
15-
/// https://learn.microsoft.com/en-us/windows/win32/fileio/file-buffering#alignment-and-file-access-requirements
16-
#[cfg(windows)]
17-
fn advise_unbuffered(&mut self) -> &mut Self;
18-
1912
/// Advise OS/file system that file will use sequential access and read-ahead behavior is
2013
/// desirable, only has impact on Windows, for other operating systems see [`FileExt`]
2114
fn advise_sequential_access(&mut self) -> &mut Self;
15+
16+
/// Use Direct I/O on Linux and disable buffering on Windows.
17+
///
18+
/// NOTE: There are major alignment requirements described here:
19+
/// https://learn.microsoft.com/en-us/windows/win32/fileio/file-buffering#alignment-and-file-access-requirements
20+
/// https://man7.org/linux/man-pages/man2/open.2.html
21+
fn use_direct_io(&mut self) -> &mut Self;
2222
}
2323

2424
impl OpenOptionsExt for OpenOptions {
25-
#[cfg(target_os = "linux")]
26-
fn advise_random_access(&mut self) -> &mut Self {
27-
// Not supported
28-
self
29-
}
30-
31-
#[cfg(target_os = "macos")]
25+
#[cfg(not(windows))]
3226
fn advise_random_access(&mut self) -> &mut Self {
3327
// Not supported
3428
self
@@ -47,8 +41,20 @@ impl OpenOptionsExt for OpenOptions {
4741
)
4842
}
4943

44+
#[cfg(not(windows))]
45+
fn advise_sequential_access(&mut self) -> &mut Self {
46+
// Not supported
47+
self
48+
}
49+
5050
#[cfg(windows)]
51-
fn advise_unbuffered(&mut self) -> &mut Self {
51+
fn advise_sequential_access(&mut self) -> &mut Self {
52+
use std::os::windows::fs::OpenOptionsExt;
53+
self.custom_flags(winapi::um::winbase::FILE_FLAG_SEQUENTIAL_SCAN)
54+
}
55+
56+
#[cfg(windows)]
57+
fn use_direct_io(&mut self) -> &mut Self {
5258
use std::os::windows::fs::OpenOptionsExt;
5359
self.custom_flags(
5460
winapi::um::winbase::FILE_FLAG_WRITE_THROUGH
@@ -57,22 +63,16 @@ impl OpenOptionsExt for OpenOptions {
5763
}
5864

5965
#[cfg(target_os = "linux")]
60-
fn advise_sequential_access(&mut self) -> &mut Self {
61-
// Not supported
62-
self
66+
fn use_direct_io(&mut self) -> &mut Self {
67+
use std::os::unix::fs::OpenOptionsExt;
68+
self.custom_flags(libc::O_DIRECT)
6369
}
6470

65-
#[cfg(target_os = "macos")]
66-
fn advise_sequential_access(&mut self) -> &mut Self {
71+
#[cfg(not(any(target_os = "linux", windows)))]
72+
fn use_direct_io(&mut self) -> &mut Self {
6773
// Not supported
6874
self
6975
}
70-
71-
#[cfg(windows)]
72-
fn advise_sequential_access(&mut self) -> &mut Self {
73-
use std::os::windows::fs::OpenOptionsExt;
74-
self.custom_flags(winapi::um::winbase::FILE_FLAG_SEQUENTIAL_SCAN)
75-
}
7676
}
7777

7878
/// Extension convenience trait that allows pre-allocating files, suggesting random access pattern
@@ -92,6 +92,9 @@ pub trait FileExt {
9292
/// desirable, on Windows this can only be set when file is opened, see [`OpenOptionsExt`]
9393
fn advise_sequential_access(&self) -> Result<()>;
9494

95+
/// Disable cache on macOS
96+
fn disable_cache(&self) -> Result<()>;
97+
9598
/// Read exact number of bytes at a specific offset
9699
fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> Result<()>;
97100

@@ -163,6 +166,22 @@ impl FileExt for File {
163166
Ok(())
164167
}
165168

169+
#[cfg(not(target_os = "macos"))]
170+
fn disable_cache(&self) -> Result<()> {
171+
// Not supported
172+
Ok(())
173+
}
174+
175+
#[cfg(target_os = "macos")]
176+
fn disable_cache(&self) -> Result<()> {
177+
use std::os::unix::io::AsRawFd;
178+
if unsafe { libc::fcntl(self.as_raw_fd(), libc::F_NOCACHE, 1) } != 0 {
179+
Err(std::io::Error::last_os_error())
180+
} else {
181+
Ok(())
182+
}
183+
}
184+
166185
#[cfg(unix)]
167186
fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> Result<()> {
168187
std::os::unix::fs::FileExt::read_exact_at(self, buf, offset)

Diff for: crates/subspace-farmer/src/bin/subspace-farmer/commands/benchmark.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ use std::path::PathBuf;
1111
use subspace_core_primitives::crypto::kzg::{embedded_kzg_settings, Kzg};
1212
use subspace_core_primitives::{Record, SolutionRange};
1313
use subspace_erasure_coding::ErasureCoding;
14+
use subspace_farmer::single_disk_farm::direct_io_file::DirectIoFile;
1415
use subspace_farmer::single_disk_farm::farming::rayon_files::RayonFiles;
1516
use subspace_farmer::single_disk_farm::farming::{PlotAudit, PlotAuditOptions};
16-
use subspace_farmer::single_disk_farm::unbuffered_io_file_windows::UnbufferedIoFileWindows;
1717
use subspace_farmer::single_disk_farm::{
1818
SingleDiskFarm, SingleDiskFarmInfo, SingleDiskFarmSummary,
1919
};
@@ -212,10 +212,10 @@ where
212212
)
213213
});
214214
}
215-
if cfg!(windows) {
215+
{
216216
let plot = RayonFiles::open_with(
217217
&disk_farm.join(SingleDiskFarm::PLOT_FILE),
218-
UnbufferedIoFileWindows::open,
218+
DirectIoFile::open,
219219
)
220220
.map_err(|error| anyhow::anyhow!("Failed to open plot: {error}"))?;
221221
let plot_audit = PlotAudit::new(&plot);
@@ -430,10 +430,10 @@ where
430430
)
431431
});
432432
}
433-
if cfg!(windows) {
433+
{
434434
let plot = RayonFiles::open_with(
435435
&disk_farm.join(SingleDiskFarm::PLOT_FILE),
436-
UnbufferedIoFileWindows::open,
436+
DirectIoFile::open,
437437
)
438438
.map_err(|error| anyhow::anyhow!("Failed to open plot: {error}"))?;
439439
let plot_audit = PlotAudit::new(&plot);

Diff for: crates/subspace-farmer/src/disk_piece_cache.rs

+3-24
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,21 @@ mod tests;
77
use crate::disk_piece_cache::metrics::DiskPieceCacheMetrics;
88
use crate::farm;
99
use crate::farm::{FarmError, PieceCacheId, PieceCacheOffset};
10-
#[cfg(windows)]
11-
use crate::single_disk_farm::unbuffered_io_file_windows::UnbufferedIoFileWindows;
12-
use crate::single_disk_farm::unbuffered_io_file_windows::DISK_SECTOR_SIZE;
10+
use crate::single_disk_farm::direct_io_file::{DirectIoFile, DISK_SECTOR_SIZE};
1311
use crate::utils::AsyncJoinOnDrop;
1412
use async_trait::async_trait;
1513
use bytes::BytesMut;
1614
use futures::channel::mpsc;
1715
use futures::{stream, SinkExt, Stream, StreamExt};
1816
use parking_lot::Mutex;
1917
use prometheus_client::registry::Registry;
20-
#[cfg(not(windows))]
21-
use std::fs::{File, OpenOptions};
2218
use std::path::Path;
2319
use std::sync::Arc;
2420
use std::task::Poll;
2521
use std::{fs, io, mem};
2622
use subspace_core_primitives::crypto::blake3_hash_list;
2723
use subspace_core_primitives::{Blake3Hash, Piece, PieceIndex};
2824
use subspace_farmer_components::file_ext::FileExt;
29-
#[cfg(not(windows))]
30-
use subspace_farmer_components::file_ext::OpenOptionsExt;
3125
use thiserror::Error;
3226
use tokio::runtime::Handle;
3327
use tokio::task;
@@ -65,10 +59,7 @@ pub enum DiskPieceCacheError {
6559
#[derive(Debug)]
6660
struct Inner {
6761
id: PieceCacheId,
68-
#[cfg(not(windows))]
69-
file: File,
70-
#[cfg(windows)]
71-
file: UnbufferedIoFileWindows,
62+
file: DirectIoFile,
7263
max_num_elements: u32,
7364
metrics: Option<DiskPieceCacheMetrics>,
7465
}
@@ -196,19 +187,7 @@ impl DiskPieceCache {
196187
return Err(DiskPieceCacheError::ZeroCapacity);
197188
}
198189

199-
#[cfg(not(windows))]
200-
let file = OpenOptions::new()
201-
.read(true)
202-
.write(true)
203-
.create(true)
204-
.advise_random_access()
205-
.open(directory.join(Self::FILE_NAME))?;
206-
207-
#[cfg(not(windows))]
208-
file.advise_random_access()?;
209-
210-
#[cfg(windows)]
211-
let file = UnbufferedIoFileWindows::open(&directory.join(Self::FILE_NAME))?;
190+
let file = DirectIoFile::open(&directory.join(Self::FILE_NAME))?;
212191

213192
let expected_size = u64::from(Self::element_size()) * u64::from(capacity);
214193
// Align plot file size for disk sector size

Diff for: crates/subspace-farmer/src/single_disk_farm.rs

+8-58
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//! a small piece cache. It fully manages farming and plotting process, including listening to node
55
//! notifications, producing solutions and singing rewards.
66
7+
pub mod direct_io_file;
78
pub mod farming;
89
pub mod identity;
910
mod metrics;
@@ -13,7 +14,6 @@ pub mod plot_cache;
1314
mod plotted_sectors;
1415
mod plotting;
1516
mod reward_signing;
16-
pub mod unbuffered_io_file_windows;
1717

1818
use crate::disk_piece_cache::{DiskPieceCache, DiskPieceCacheError};
1919
use crate::farm::{
@@ -22,6 +22,7 @@ use crate::farm::{
2222
};
2323
use crate::node_client::NodeClient;
2424
use crate::plotter::Plotter;
25+
use crate::single_disk_farm::direct_io_file::{DirectIoFile, DISK_SECTOR_SIZE};
2526
use crate::single_disk_farm::farming::rayon_files::RayonFiles;
2627
use crate::single_disk_farm::farming::{
2728
farming, slot_notification_forwarder, FarmingOptions, PlotAudit,
@@ -37,9 +38,6 @@ use crate::single_disk_farm::plotting::{
3738
plotting, plotting_scheduler, PlottingOptions, PlottingSchedulerOptions, SectorPlottingOptions,
3839
};
3940
use crate::single_disk_farm::reward_signing::reward_signing;
40-
#[cfg(windows)]
41-
use crate::single_disk_farm::unbuffered_io_file_windows::UnbufferedIoFileWindows;
42-
use crate::single_disk_farm::unbuffered_io_file_windows::DISK_SECTOR_SIZE;
4341
use crate::utils::{tokio_rayon_spawn_handler, AsyncJoinOnDrop};
4442
use crate::{farm, KNOWN_PEERS_CACHE_SIZE};
4543
use async_lock::{Mutex as AsyncMutex, RwLock as AsyncRwLock};
@@ -75,8 +73,6 @@ use subspace_core_primitives::{
7573
};
7674
use subspace_erasure_coding::ErasureCoding;
7775
use subspace_farmer_components::file_ext::FileExt;
78-
#[cfg(not(windows))]
79-
use subspace_farmer_components::file_ext::OpenOptionsExt;
8076
use subspace_farmer_components::reading::ReadSectorRecordChunksMode;
8177
use subspace_farmer_components::sector::{sector_size, SectorMetadata, SectorMetadataChecksummed};
8278
use subspace_farmer_components::{FarmerProtocolInfo, ReadAtSync};
@@ -753,14 +749,8 @@ struct SingleDiskFarmInit {
753749
identity: Identity,
754750
single_disk_farm_info: SingleDiskFarmInfo,
755751
single_disk_farm_info_lock: Option<SingleDiskFarmInfoLock>,
756-
#[cfg(not(windows))]
757-
plot_file: Arc<File>,
758-
#[cfg(windows)]
759-
plot_file: Arc<UnbufferedIoFileWindows>,
760-
#[cfg(not(windows))]
761-
metadata_file: File,
762-
#[cfg(windows)]
763-
metadata_file: UnbufferedIoFileWindows,
752+
plot_file: Arc<DirectIoFile>,
753+
metadata_file: DirectIoFile,
764754
metadata_header: PlotMetadataHeader,
765755
target_sector_count: u16,
766756
sectors_metadata: Arc<AsyncRwLock<Vec<SectorMetadataChecksummed>>>,
@@ -993,17 +983,7 @@ impl SingleDiskFarm {
993983
let farming_plot_fut = task::spawn_blocking(|| {
994984
farming_thread_pool
995985
.install(move || {
996-
#[cfg(windows)]
997-
{
998-
RayonFiles::open_with(
999-
&directory.join(Self::PLOT_FILE),
1000-
UnbufferedIoFileWindows::open,
1001-
)
1002-
}
1003-
#[cfg(not(windows))]
1004-
{
1005-
RayonFiles::open(&directory.join(Self::PLOT_FILE))
1006-
}
986+
RayonFiles::open_with(&directory.join(Self::PLOT_FILE), DirectIoFile::open)
1007987
})
1008988
.map(|farming_plot| (farming_plot, farming_thread_pool))
1009989
});
@@ -1474,19 +1454,7 @@ impl SingleDiskFarm {
14741454
let target_sector_count = allocated_space_distribution.target_sector_count;
14751455

14761456
let metadata_file_path = directory.join(Self::METADATA_FILE);
1477-
#[cfg(not(windows))]
1478-
let metadata_file = OpenOptions::new()
1479-
.read(true)
1480-
.write(true)
1481-
.create(true)
1482-
.advise_random_access()
1483-
.open(&metadata_file_path)?;
1484-
1485-
#[cfg(not(windows))]
1486-
metadata_file.advise_random_access()?;
1487-
1488-
#[cfg(windows)]
1489-
let metadata_file = UnbufferedIoFileWindows::open(&metadata_file_path)?;
1457+
let metadata_file = DirectIoFile::open(&metadata_file_path)?;
14901458

14911459
let metadata_size = metadata_file.size()?;
14921460
let expected_metadata_size = allocated_space_distribution.metadata_file_size;
@@ -1576,19 +1544,7 @@ impl SingleDiskFarm {
15761544
Arc::new(AsyncRwLock::new(sectors_metadata))
15771545
};
15781546

1579-
#[cfg(not(windows))]
1580-
let plot_file = OpenOptions::new()
1581-
.read(true)
1582-
.write(true)
1583-
.create(true)
1584-
.advise_random_access()
1585-
.open(directory.join(Self::PLOT_FILE))?;
1586-
1587-
#[cfg(not(windows))]
1588-
plot_file.advise_random_access()?;
1589-
1590-
#[cfg(windows)]
1591-
let plot_file = UnbufferedIoFileWindows::open(&directory.join(Self::PLOT_FILE))?;
1547+
let plot_file = DirectIoFile::open(&directory.join(Self::PLOT_FILE))?;
15921548

15931549
if plot_file.size()? != allocated_space_distribution.plot_file_size {
15941550
// Allocating the whole file (`set_len` below can create a sparse file, which will cause
@@ -1731,13 +1687,7 @@ impl SingleDiskFarm {
17311687
pub fn read_all_sectors_metadata(
17321688
directory: &Path,
17331689
) -> io::Result<Vec<SectorMetadataChecksummed>> {
1734-
#[cfg(not(windows))]
1735-
let metadata_file = OpenOptions::new()
1736-
.read(true)
1737-
.open(directory.join(Self::METADATA_FILE))?;
1738-
1739-
#[cfg(windows)]
1740-
let metadata_file = UnbufferedIoFileWindows::open(&directory.join(Self::METADATA_FILE))?;
1690+
let metadata_file = DirectIoFile::open(&directory.join(Self::METADATA_FILE))?;
17411691

17421692
let metadata_size = metadata_file.size()?;
17431693
let sector_metadata_size = SectorMetadataChecksummed::encoded_size();

0 commit comments

Comments
 (0)