use std::{
cell::RefCell,
collections::hash_map::Entry,
ffi::OsString,
fs::{DirEntry, ReadDir, create_dir_all, metadata, read_dir, remove_file as sync_remove_file},
io::{Error as IoError, ErrorKind, Result as IoResult},
path::{Path, PathBuf},
rc::Rc,
str::from_utf8,
sync::{
Arc,
atomic::{AtomicBool, AtomicI32, AtomicU32, AtomicU64, Ordering},
},
};
use compio::{
buf::{BufResult, IntoInner, IoBuf},
fs::{File, OpenOptions, remove_file},
io::{AsyncReadAt, AsyncWriteAt},
};
use futures_util::future::join_all;
use gxhash::HashMap;
#[cfg(windows)]
use wbase::map::{ConcurrentMap, new_concurrent_map};
use wbase::{
AlignedBuf, BufferPool, DEFAULT_SECTOR_SIZE, MIN_SECTOR_SIZE,
base32::{BASE32_LEN_U64, decode_u64, encode_u64},
is_valid_sector_size,
};
use crate::{
chunk::{SegmentChunks, segment_mask, segment_shift, validate_aligned_io},
device::Device,
error::{Error, Result},
sys::MAX_SEGMENT_SIZE,
};
static NEXT_DEVICE_ID: AtomicU64 = AtomicU64::new(1);
thread_local! {
static LOCAL_FILES: RefCell<HashMap<(u64, u32), Rc<File>>> =
RefCell::new(HashMap::default());
}
#[cfg(windows)]
#[inline]
fn concurrent_map<K, V>() -> ConcurrentMap<K, V>
where
K: Send + Sync + 'static,
V: Send + Sync + 'static,
{
new_concurrent_map()
}
#[cfg(debug_assertions)]
const SYNC_GUARD_SEGMENTS: usize = 128;
pub struct SegmentedDevice {
device_id: u64,
base_path: PathBuf,
segment_size: Option<u64>,
sector_size: usize,
read_only: bool,
preallocate: bool,
delete_on_close: bool,
start_segment: AtomicU32,
end_segment: AtomicI32,
direct_io: AtomicBool,
#[cfg(target_os = "linux")]
direct_io_probed: AtomicBool,
#[cfg(windows)]
pending_removes: ConcurrentMap<u32, ()>,
capacity: Option<u64>,
pool: Arc<BufferPool>,
dir_syncs: AtomicU64,
#[cfg(debug_assertions)]
dirty_segs: [AtomicU64; 2],
}
fn sync_dir(parent: &Path) -> bool {
#[cfg(unix)]
match crate::sync_dir(parent) {
Ok(()) => true,
Err(e) => {
log::warn!(
"新建段文件后 fsync 父目录 {} 失败: {e},崩溃后新段可能不可见",
parent.display()
);
false
}
}
#[cfg(not(unix))]
{
let _ = crate::sync_dir(parent);
false
}
}
#[inline]
fn parse_segment_suffix(rest: &[u8]) -> Option<u32> {
let s = from_utf8(rest).ok()?;
let val = decode_u64(s)?;
if encode_u64(val).as_str() != s {
return None;
}
u32::try_from(val).ok()
}
struct SegmentEntries<'a> {
prefix: &'a [u8],
read_dir: ReadDir,
}
impl Iterator for SegmentEntries<'_> {
type Item = IoResult<(u32, DirEntry)>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let entry = match self.read_dir.next()? {
Ok(e) => e,
Err(e) => return Some(Err(e)),
};
let name = entry.file_name();
let name_bytes = name.as_encoded_bytes();
let Some(rest) = name_bytes.strip_prefix(self.prefix) else {
continue;
};
let Some(rest) = rest.strip_prefix(b".") else {
continue;
};
let Some(id) = parse_segment_suffix(rest) else {
continue;
};
return Some(Ok((id, entry)));
}
}
}
impl SegmentedDevice {
pub fn new(
base_path: impl Into<PathBuf>,
segment_size: Option<u64>,
sector_size: usize,
) -> Result<Self> {
if !is_valid_sector_size(sector_size) {
return Err(Error::InvalidSectorSize {
size: sector_size,
min: MIN_SECTOR_SIZE,
});
}
let pool = BufferPool::new(sector_size)?;
Self::with_pool(base_path, segment_size, sector_size, pool)
}
pub fn with_pool(
base_path: impl Into<PathBuf>,
segment_size: Option<u64>,
sector_size: usize,
pool: Arc<BufferPool>,
) -> Result<Self> {
if !is_valid_sector_size(sector_size) {
return Err(Error::InvalidSectorSize {
size: sector_size,
min: MIN_SECTOR_SIZE,
});
}
if pool.sector_size() != sector_size {
return Err(Error::PoolSectorMismatch {
pool: pool.sector_size(),
device: sector_size,
});
}
if let Some(seg_size) = segment_size
&& (seg_size == 0
|| !seg_size.is_power_of_two()
|| seg_size < sector_size as u64
|| seg_size > MAX_SEGMENT_SIZE)
{
return Err(Error::InvalidSegmentSize(seg_size));
}
let base_path = base_path.into();
if let Some(parent) = base_path.parent()
&& !parent.as_os_str().is_empty()
{
create_dir_all(parent)?;
}
Ok(Self {
base_path,
segment_size,
sector_size,
read_only: false,
preallocate: false,
delete_on_close: false,
device_id: NEXT_DEVICE_ID.fetch_add(1, Ordering::Relaxed),
start_segment: AtomicU32::new(0),
end_segment: AtomicI32::new(-1),
direct_io: AtomicBool::new(cfg!(target_os = "linux")),
#[cfg(target_os = "linux")]
direct_io_probed: AtomicBool::new(false),
#[cfg(windows)]
pending_removes: concurrent_map(),
#[cfg(debug_assertions)]
dirty_segs: [const { AtomicU64::new(0) }; 2],
capacity: None,
pool,
dir_syncs: AtomicU64::new(0),
})
}
pub fn set_capacity(&mut self, capacity: Option<u64>) -> Result<()> {
if let (Some(cap), Some(seg_size)) = (capacity, self.segment_size)
&& (cap == 0 || cap % seg_size != 0)
{
return Err(Error::InvalidCapacity { capacity: cap });
}
self.capacity = capacity;
Ok(())
}
#[inline]
pub fn set_read_only(&mut self, read_only: bool) -> &mut Self {
self.read_only = read_only;
self
}
#[inline]
pub fn set_preallocate(&mut self, preallocate: bool) -> &mut Self {
self.preallocate = preallocate;
self
}
#[inline]
pub fn set_delete_on_close(&mut self, delete_on_close: bool) -> &mut Self {
self.delete_on_close = delete_on_close;
self
}
#[inline]
pub fn is_read_only(&self) -> bool {
self.read_only
}
#[inline]
pub fn is_preallocate(&self) -> bool {
self.preallocate
}
#[inline]
pub fn is_delete_on_close(&self) -> bool {
self.delete_on_close
}
#[inline]
pub fn sector_size(&self) -> usize {
self.sector_size
}
#[inline]
pub fn segment_size(&self) -> Option<u64> {
self.segment_size
}
#[inline]
pub fn direct_io(&self) -> bool {
self.direct_io.load(Ordering::Relaxed)
}
#[inline]
pub fn dir_sync_count(&self) -> u64 {
self.dir_syncs.load(Ordering::Relaxed)
}
#[inline]
pub fn single_file(base_path: impl Into<PathBuf>) -> Result<Self> {
Self::new(base_path, None, DEFAULT_SECTOR_SIZE)
}
#[inline]
pub fn segmented(base_path: impl Into<PathBuf>, segment_size: u64) -> Result<Self> {
Self::new(base_path, Some(segment_size), DEFAULT_SECTOR_SIZE)
}
#[inline]
fn parent_dir(&self) -> &Path {
match self.base_path.parent() {
Some(p) if !p.as_os_str().is_empty() => p,
_ => Path::new("."),
}
}
pub fn segment_path(&self, segment_id: u32) -> PathBuf {
match self.segment_size {
Some(_) => {
let seg_str = encode_u64(u64::from(segment_id));
let base = self.base_path.as_os_str();
let mut path = OsString::with_capacity(base.len() + 1 + BASE32_LEN_U64);
path.push(base);
path.push(".");
path.push(seg_str);
PathBuf::from(path)
}
None => self.base_path.clone(),
}
}
fn segment_entries(&self) -> IoResult<Option<SegmentEntries<'_>>> {
let Some(file_name) = self.base_path.file_name() else {
return Ok(None);
};
let read_dir = read_dir(self.parent_dir())?;
Ok(Some(SegmentEntries {
prefix: file_name.as_encoded_bytes(),
read_dir,
}))
}
#[inline]
pub fn get_segment_and_offset(&self, offset: u64) -> Result<(u32, u64)> {
match self.segment_size {
Some(seg_size) => {
let seg_id_u64 = offset >> segment_shift(seg_size);
let seg_id = u32::try_from(seg_id_u64).map_err(|_| Error::SegmentExceeded(seg_id_u64))?;
Ok((seg_id, offset & segment_mask(seg_size)))
}
None => Ok((0, offset)),
}
}
#[inline]
fn open_options(read_only: bool, create: bool) -> OpenOptions {
let mut opts = OpenOptions::new();
opts.read(true);
if read_only {
opts.write(false).create(false);
} else {
opts.write(true).create(create);
}
opts
}
async fn try_preallocate(file: &File, path: &Path, preallocate: Option<u64>) {
if let Some(sz) = preallocate
&& let Err(e) = file.set_len(sz).await
{
log::warn!("段文件 {} 预分配至 {sz} 字节失败: {e}", path.display());
}
}
async fn open_file(
&self,
path: &Path,
read_only: bool,
preallocate: Option<u64>,
create: bool,
) -> Result<File> {
if !read_only
&& let Some(parent) = path.parent()
&& !parent.as_os_str().is_empty()
{
let _ = create_dir_all(parent);
}
#[cfg(target_os = "linux")]
if self.direct_io.load(Ordering::Relaxed) {
let mut opts = Self::open_options(read_only, create);
opts.custom_flags(libc::O_DIRECT);
match opts.open(path).await {
Ok(file) => {
self.direct_io_probed.store(true, Ordering::Relaxed);
log::debug!("成功以 Direct I/O (O_DIRECT) 打开文件: {}", path.display());
if !read_only {
Self::try_preallocate(&file, path, preallocate).await;
}
return Ok(file);
}
Err(e) if matches!(e.kind(), ErrorKind::InvalidInput | ErrorKind::Unsupported) => {
if !self.direct_io_probed.swap(true, Ordering::Relaxed) {
self.direct_io.store(false, Ordering::Relaxed);
LOCAL_FILES.with(|m| {
m.borrow_mut()
.retain(|&(dev_id, _), _| dev_id != self.device_id);
});
log::error!(
"Direct I/O 探测失败({}: {e}),设备一次性定型为常规缓存 I/O,此后 Direct 打开失败将直接上抛",
path.display()
);
} else if !self.direct_io.load(Ordering::Acquire) {
log::debug!(
"并发 Direct I/O 探测竞态败方({e}),按定型后的常规缓存 I/O 打开: {}",
path.display()
);
} else {
return Err(Error::from(e));
}
}
Err(e) => return Err(Error::from(e)),
}
}
let file = Self::open_options(read_only, create).open(path).await?;
log::debug!("成功打开文件: {}", path.display());
if !read_only {
Self::try_preallocate(&file, path, preallocate).await;
}
Ok(file)
}
async fn get_or_open_file(&self, segment_id: u32, create: bool) -> Result<Rc<File>> {
if segment_id < self.start_segment.load(Ordering::SeqCst) {
return Err(Error::SegmentNotFound(segment_id));
}
let key = (self.device_id, segment_id);
if let Some(file) = LOCAL_FILES.with(|m| m.borrow().get(&key).cloned()) {
return Ok(file);
}
let path = self.segment_path(segment_id);
let is_new_segment = if create {
!self.read_only && metadata(&path).is_err_and(|e| e.kind() == ErrorKind::NotFound)
} else {
match metadata(&path) {
Ok(_) => false,
Err(e) if e.kind() == ErrorKind::NotFound => {
return Err(Error::SegmentNotFound(segment_id));
}
Err(e) => return Err(e.into()),
}
};
let prealloc = if self.preallocate && !self.read_only {
self.segment_size
} else {
None
};
let file = match self
.open_file(&path, self.read_only, prealloc, create)
.await
{
Ok(f) => f,
Err(Error::Io(e)) if !create && e.kind() == ErrorKind::NotFound => {
return Err(Error::SegmentNotFound(segment_id));
}
Err(e) => return Err(e),
};
if is_new_segment && sync_dir(self.parent_dir()) {
self.dir_syncs.fetch_add(1, Ordering::Relaxed);
}
let truncated = segment_id < self.start_segment.load(Ordering::SeqCst);
if truncated {
if !self.read_only {
let _ = remove_file(&path).await;
}
return Err(Error::SegmentNotFound(segment_id));
}
let rc = Rc::new(file);
LOCAL_FILES.with(|m| {
m.borrow_mut().insert(key, Rc::clone(&rc));
});
Ok(rc)
}
pub fn get_file_size(&self, segment_id: u32) -> Result<u64> {
if segment_id < self.start_segment.load(Ordering::SeqCst)
|| (self.segment_size.is_none() && segment_id > 0)
{
return Ok(0);
}
let path = self.segment_path(segment_id);
match metadata(&path) {
Ok(meta) => Ok(meta.len()),
Err(e) if e.kind() == ErrorKind::NotFound => Ok(0),
Err(e) => Err(Error::Io(e)),
}
}
pub async fn remove_segment(&self, segment_id: u32) -> Result<()> {
if self.segment_size.is_none() && segment_id > 0 {
return Ok(());
}
LOCAL_FILES.with(|m| {
m.borrow_mut().remove(&(self.device_id, segment_id));
});
#[cfg(debug_assertions)]
self.debug_clear_segment(segment_id);
let path = self.segment_path(segment_id);
match remove_file(&path).await {
Ok(()) => Ok(()),
Err(e) if e.kind() == ErrorKind::NotFound => Ok(()),
Err(e) => Err(Error::Io(e)),
}
}
pub fn reset(&self) {
LOCAL_FILES.with(|m| {
m.borrow_mut()
.retain(|&(dev_id, _), _| dev_id != self.device_id);
});
#[cfg(debug_assertions)]
{
for word in &self.dirty_segs {
word.store(0, Ordering::Relaxed);
}
}
}
#[inline]
pub fn start_segment(&self) -> u32 {
self.start_segment.load(Ordering::SeqCst)
}
#[inline]
pub fn end_segment(&self) -> Option<u32> {
let v = self.end_segment.load(Ordering::SeqCst);
(v >= 0).then_some(v as u32)
}
#[inline]
pub fn capacity(&self) -> Option<u64> {
self.capacity
}
#[inline]
fn within_single_segment(&self, offset: u64, len: usize) -> bool {
match self.segment_size {
None => true,
Some(seg_size) => {
let off_in_seg = offset & segment_mask(seg_size);
off_in_seg
.checked_add(len as u64)
.is_some_and(|end| end <= seg_size)
}
}
}
pub fn recover(&self) -> Result<()> {
let Some(seg_size) = self.segment_size else {
return Ok(());
};
let mut segids: Vec<u32> = Vec::new();
if let Some(entries) = self.segment_entries()? {
for item in entries {
let (id, entry) = item?;
match entry.metadata() {
Ok(m) => {
let file_size = m.len();
if file_size > seg_size {
return Err(Error::SegmentSizeMismatch {
segment: id,
file_size,
segment_size: seg_size,
});
}
segids.push(id);
}
Err(e) if e.kind() == ErrorKind::NotFound => {}
Err(e) => return Err(Error::Io(e)),
}
}
}
segids.sort_unstable();
let mut prev: i64 = -1;
let mut recovered_start = 0u32;
for id in segids {
if i64::from(id) != prev + 1 {
recovered_start = id;
} else {
let seg = i32::try_from(id).unwrap_or(i32::MAX);
self.end_segment.fetch_max(seg, Ordering::SeqCst);
}
prev = i64::from(id);
}
self
.start_segment
.fetch_max(recovered_start, Ordering::SeqCst);
Ok(())
}
async fn handle_capacity(&self, segment: u32) -> Result<()> {
#[cfg(windows)]
self.retry_pending_removes().await;
let seg = i32::try_from(segment).unwrap_or(i32::MAX);
if self.end_segment.fetch_max(seg, Ordering::SeqCst) >= seg {
return Ok(());
}
let (Some(cap), Some(seg_size)) = (self.capacity, self.segment_size) else {
return Ok(());
};
let new_start = (segment as u64).saturating_sub(cap >> segment_shift(seg_size));
if new_start > 0 {
self.truncate_until_segment(new_start as u32).await?;
}
Ok(())
}
#[cfg(windows)]
async fn retry_pending_removes(&self) {
let ids: Vec<u32> = self
.pending_removes
.pin()
.iter()
.map(|(&id, _)| id)
.collect();
let mut done = Vec::new();
for id in ids {
match remove_file(self.segment_path(id)).await {
Ok(()) => done.push(id),
Err(e) if e.kind() == ErrorKind::NotFound => done.push(id),
Err(_) => {}
}
}
if !done.is_empty() {
let pin = self.pending_removes.pin();
for id in done {
pin.remove(&id);
}
}
}
pub async fn sync(&self) -> Result<()> {
self.sync_internal(false).await
}
pub async fn sync_data(&self) -> Result<()> {
self.sync_internal(true).await
}
async fn sync_internal(&self, datasync: bool) -> Result<()> {
let min_seg = self.start_segment.load(Ordering::Relaxed);
let max_seg = self.end_segment.load(Ordering::Relaxed);
#[cfg(debug_assertions)]
let pending = self.debug_dirty_segments();
let mut files: HashMap<u32, Rc<File>> = LOCAL_FILES.with(|map| {
let mut map = map.borrow_mut();
map.retain(|&(dev_id, sid), _| dev_id != self.device_id || sid >= min_seg);
map
.iter()
.filter(|&(&(dev_id, sid), _)| dev_id == self.device_id && sid >= min_seg)
.map(|(&(_, sid), f)| (sid, Rc::clone(f)))
.collect()
});
if max_seg >= min_seg as i32 {
let max_u32 = max_seg as u32;
for sid in min_seg..=max_u32 {
if let Entry::Vacant(e) = files.entry(sid) {
match self.get_or_open_file(sid, false).await {
Ok(file) => {
e.insert(file);
}
Err(Error::SegmentNotFound(_)) => {}
Err(e) => return Err(e),
}
}
}
}
if files.is_empty() {
#[cfg(debug_assertions)]
self.debug_verify_synced(&pending, &files);
return Ok(());
}
if files.len() == 1
&& let Some(file) = files.values().next()
{
let res = if datasync {
file.sync_data().await
} else {
file.sync_all().await
};
res.map_err(Error::from)?;
#[cfg(debug_assertions)]
self.debug_verify_synced(&pending, &files);
return Ok(());
}
let results = join_all(files.values().map(|file| {
let file = Rc::clone(file);
async move {
if datasync {
file.sync_data().await
} else {
file.sync_all().await
}
}
}))
.await;
let mut first_err = None;
for res in results {
if let Err(e) = res
&& first_err.is_none()
{
first_err = Some(Error::from(e));
}
}
if let Some(err) = first_err {
return Err(err);
}
#[cfg(debug_assertions)]
self.debug_verify_synced(&pending, &files);
Ok(())
}
#[cfg(debug_assertions)]
fn debug_mark_dirty(&self, segment_id: u32) {
let seg = segment_id as usize;
if seg < SYNC_GUARD_SEGMENTS {
self.dirty_segs[seg / 64].fetch_or(1 << (seg % 64), Ordering::Relaxed);
}
}
#[cfg(debug_assertions)]
fn debug_verify_synced(&self, pending: &[u32], synced: &HashMap<u32, Rc<File>>) {
let start_seg = u64::from(self.start_segment.load(Ordering::Relaxed));
for &seg in pending {
let bit = !(1 << (seg as usize % 64));
let covered = synced.contains_key(&seg) || u64::from(seg) < start_seg;
assert!(
covered,
"sync 契约违约:段 {seg} 在册有写入,但本次全局 sync 未覆盖其句柄且段未被截断背书,脏页无人 fsync"
);
self.dirty_segs[seg as usize / 64].fetch_and(bit, Ordering::Relaxed);
}
}
#[cfg(debug_assertions)]
fn debug_clear_segment(&self, segment_id: u32) {
let seg = segment_id as usize;
if seg < SYNC_GUARD_SEGMENTS {
self.dirty_segs[seg / 64].fetch_and(!(1 << (seg % 64)), Ordering::Relaxed);
}
}
#[cfg(debug_assertions)]
pub fn debug_dirty_segments(&self) -> Vec<u32> {
(0..SYNC_GUARD_SEGMENTS)
.filter(|&seg| self.dirty_segs[seg / 64].load(Ordering::Relaxed) & (1 << (seg % 64)) != 0)
.map(|seg| seg as u32)
.collect()
}
async fn read_impl(
&self,
offset: u64,
mut buf: AlignedBuf,
aligned: bool,
) -> (Result<usize>, AlignedBuf) {
let sector_size = self.sector_size;
let target_len = buf.required_len().min(buf.capacity());
if aligned {
if let Err(e) = validate_aligned_io(offset, target_len, &buf, sector_size) {
return (Err(e), buf);
}
} else if offset.checked_add(target_len as u64).is_none() {
return (
Err(Error::OutOfBounds {
offset,
len: target_len,
}),
buf,
);
}
if target_len == 0 {
return (Ok(0), buf);
}
if self.within_single_segment(offset, target_len) {
let (seg_id, start_off) = match self.get_segment_and_offset(offset) {
Ok(v) => v,
Err(e) => return (Err(e), buf),
};
let file = match self.get_or_open_file(seg_id, true).await {
Ok(f) => f,
Err(e) => return (Err(e), buf),
};
let slice = buf.slice(0..target_len);
let BufResult(res, slice) = file.read_at(slice, start_off).await;
buf = slice.into_inner();
let bytes_read = match res {
Ok(n) => n,
Err(e) => {
unsafe { buf.set_len_unchecked(0) };
return (Err(Error::from(e)), buf);
}
};
unsafe { buf.set_len_unchecked(bytes_read) };
return (Ok(bytes_read), buf);
}
unsafe { buf.set_len_unchecked(target_len) };
let mut total_read = 0;
let mut first_err = None;
for chunk in SegmentChunks::new(offset, target_len, self.segment_size) {
let chunk = match chunk {
Ok(c) => c,
Err(e) => {
first_err = Some(e);
break;
}
};
let file = match self.get_or_open_file(chunk.seg_id, true).await {
Ok(f) => f,
Err(e) => {
first_err = Some(e);
break;
}
};
let slice = buf.slice(chunk.buf_pos..chunk.buf_pos + chunk.len);
let BufResult(res, slice) = file.read_at(slice, chunk.off_in_seg).await;
buf = slice.into_inner();
match res {
Ok(n) => {
total_read += n;
if n < chunk.len {
break;
}
}
Err(e) => {
first_err = Some(Error::Io(e));
break;
}
}
}
unsafe { buf.set_len_unchecked(total_read) };
match first_err {
Some(e) => (Err(e), buf),
None => (Ok(total_read), buf),
}
}
}
impl Device for SegmentedDevice {
#[inline]
fn sector_size(&self) -> usize {
SegmentedDevice::sector_size(self)
}
#[inline]
fn segment_size(&self) -> Option<u64> {
SegmentedDevice::segment_size(self)
}
#[inline]
fn direct_io(&self) -> bool {
SegmentedDevice::direct_io(self)
}
#[inline]
fn recover(&self) -> Result<()> {
SegmentedDevice::recover(self)
}
#[inline]
fn start_segment(&self) -> u32 {
self.start_segment()
}
#[inline]
fn end_segment(&self) -> Option<u32> {
self.end_segment()
}
#[inline]
fn capacity(&self) -> Option<u64> {
self.capacity
}
#[inline]
fn pool(&self) -> &Arc<BufferPool> {
&self.pool
}
async fn write_aligned(&self, offset: u64, mut buf: AlignedBuf) -> (Result<usize>, AlignedBuf) {
let sector_size = self.sector_size;
let total_len = buf.len();
if self.read_only {
return (
Err(Error::ReadOnly {
offset,
len: total_len,
}),
buf,
);
}
if let Some(cap) = self.capacity
&& self.segment_size.is_none()
&& offset.saturating_add(total_len as u64) > cap
{
return (
Err(Error::OutOfBounds {
offset,
len: total_len,
}),
buf,
);
}
if let Err(e) = validate_aligned_io(offset, total_len, &buf, sector_size) {
return (Err(e), buf);
}
if total_len == 0 {
return (Ok(0), buf);
}
if self.within_single_segment(offset, total_len) {
let (seg_id, start_off) = match self.get_segment_and_offset(offset) {
Ok(v) => v,
Err(e) => return (Err(e), buf),
};
if let Err(e) = self.handle_capacity(seg_id).await {
return (Err(e), buf);
}
let file = match self.get_or_open_file(seg_id, true).await {
Ok(f) => f,
Err(e) => return (Err(e), buf),
};
let mut file_ref = &*file;
let BufResult(res, mut cur_buf) = file_ref.write_at(buf, start_off).await;
match res {
Ok(n) if n == total_len => {
#[cfg(debug_assertions)]
self.debug_mark_dirty(seg_id);
return (Ok(n), cur_buf);
}
Ok(0) => {
return (
Err(Error::Io(IoError::new(ErrorKind::WriteZero, "零字节写入"))),
cur_buf,
);
}
Ok(mut written) => {
while written < total_len {
let slice = cur_buf.slice(written..total_len);
let mut file_ref = &*file;
let BufResult(res, slice) = file_ref.write_at(slice, start_off + written as u64).await;
cur_buf = slice.into_inner();
match res {
Ok(0) => {
return (
Err(Error::Io(IoError::new(ErrorKind::WriteZero, "零字节写入"))),
cur_buf,
);
}
Ok(n) => written += n,
Err(e) => return (Err(Error::Io(e)), cur_buf),
}
}
#[cfg(debug_assertions)]
self.debug_mark_dirty(seg_id);
return (Ok(written), cur_buf);
}
Err(e) => return (Err(Error::from(e)), cur_buf),
}
}
let mut total_written = 0;
for chunk in SegmentChunks::new(offset, total_len, self.segment_size) {
let chunk = match chunk {
Ok(c) => c,
Err(e) => return (Err(e), buf),
};
if let Err(e) = self.handle_capacity(chunk.seg_id).await {
return (Err(e), buf);
}
let file = match self.get_or_open_file(chunk.seg_id, true).await {
Ok(f) => f,
Err(e) => return (Err(e), buf),
};
let mut chunk_written = 0;
while chunk_written < chunk.len {
let slice = buf.slice(chunk.buf_pos + chunk_written..chunk.buf_pos + chunk.len);
let mut file_ref = &*file;
let BufResult(res, slice) = file_ref
.write_at(slice, chunk.off_in_seg + chunk_written as u64)
.await;
buf = slice.into_inner();
match res {
Ok(0) => {
return (
Err(Error::Io(IoError::new(ErrorKind::WriteZero, "零字节写入"))),
buf,
);
}
Ok(n) => {
chunk_written += n;
total_written += n;
}
Err(e) => return (Err(Error::Io(e)), buf),
}
}
#[cfg(debug_assertions)]
self.debug_mark_dirty(chunk.seg_id);
}
(Ok(total_written), buf)
}
#[inline]
async fn read_aligned(&self, offset: u64, buf: AlignedBuf) -> (Result<usize>, AlignedBuf) {
self.read_impl(offset, buf, true).await
}
#[inline]
async fn read_raw(&self, offset: u64, buf: AlignedBuf) -> (Result<usize>, AlignedBuf) {
self.read_impl(offset, buf, false).await
}
#[inline]
fn sync(&self) -> impl Future<Output = Result<()>> {
SegmentedDevice::sync(self)
}
#[inline]
fn sync_data(&self) -> impl Future<Output = Result<()>> {
SegmentedDevice::sync_data(self)
}
async fn truncate_until_segment(&self, segment_id: u32) -> Result<()> {
if self.segment_size.is_none() {
return Ok(());
}
#[cfg(windows)]
self.retry_pending_removes().await;
let old_start = self.start_segment.fetch_max(segment_id, Ordering::SeqCst);
if old_start >= segment_id {
return Ok(());
}
#[cfg(debug_assertions)]
{
let clear_end = segment_id.min(SYNC_GUARD_SEGMENTS as u32);
for seg in old_start..clear_end {
self.debug_clear_segment(seg);
}
}
LOCAL_FILES.with(|m| {
m.borrow_mut()
.retain(|&(dev_id, sid), _| dev_id != self.device_id || sid >= segment_id);
});
if let Some(entries) = self.segment_entries()? {
for item in entries {
let (id, entry) = item?;
if id >= segment_id {
continue;
}
match remove_file(entry.path()).await {
Ok(()) => {}
Err(e) if e.kind() == ErrorKind::NotFound => {}
#[cfg(windows)]
Err(e) => {
self.pending_removes.pin().insert(id, ());
log::warn!("段 {id} 删除失败({e}),已记入延迟删除队列");
}
#[cfg(not(windows))]
Err(e) => return Err(Error::Io(e)),
}
}
}
Ok(())
}
#[inline]
fn get_file_size(&self, segment_id: u32) -> Result<u64> {
SegmentedDevice::get_file_size(self, segment_id)
}
#[inline]
fn remove_segment(&self, segment_id: u32) -> impl Future<Output = Result<()>> {
SegmentedDevice::remove_segment(self, segment_id)
}
#[inline]
fn reset(&self) {
SegmentedDevice::reset(self);
}
}
impl Drop for SegmentedDevice {
fn drop(&mut self) {
if !self.delete_on_close {
return;
}
if self.segment_size.is_none() {
let _ = sync_remove_file(&self.base_path);
return;
}
if let Ok(Some(entries)) = self.segment_entries() {
for (_, entry) in entries.flatten() {
let _ = sync_remove_file(entry.path());
}
}
}
}
#[cfg(test)]
mod tests {
use wbase::base32::encode_u64;
use super::parse_segment_suffix;
#[test]
fn test_segment_name_order_preserving() {
let samples = [
0u32,
1,
2,
9,
10,
11,
99,
100,
999,
1000,
u32::MAX - 1,
u32::MAX,
];
for pair in samples.windows(2) {
let prev = encode_u64(u64::from(pair[0]));
let next = encode_u64(u64::from(pair[1]));
assert!(
prev.as_str() < next.as_str(),
"段 {} 与 {} 的文件名字典序应随段号单调递增({} < {})",
pair[0],
pair[1],
prev.as_str(),
next.as_str()
);
}
}
#[test]
fn test_parse_segment_suffix() {
for seg in [0u32, 1, 2, 9, 10, 12, 1000, u32::MAX] {
assert_eq!(
parse_segment_suffix(encode_u64(u64::from(seg)).as_bytes()),
Some(seg)
);
}
assert_eq!(parse_segment_suffix(b""), None);
assert_eq!(parse_segment_suffix(b"0"), None);
assert_eq!(parse_segment_suffix(b"12"), None);
assert_eq!(parse_segment_suffix(b"txt"), None);
assert_eq!(parse_segment_suffix(b"000000000000"), None);
assert_eq!(parse_segment_suffix(b"00000000000000"), None);
assert_eq!(parse_segment_suffix(b"00000000000uv"), Some(30 * 32 + 31));
assert_eq!(parse_segment_suffix(b"00000000000+"), None);
assert_eq!(parse_segment_suffix(b"00000000000.0"), None);
assert_eq!(parse_segment_suffix(b"00000000000w"), None);
assert_eq!(parse_segment_suffix(b"000000000000A"), None);
assert_eq!(parse_segment_suffix(encode_u64(1 << 40).as_bytes()), None);
assert_eq!(parse_segment_suffix(b"0000000000\xff00"), None);
}
}