use crate::cache::lru_cache::LruCache;
use bytes::{Bytes, BytesMut};
use dragonfly_api::common::v2::Range;
use dragonfly_client_config::dfdaemon::Config;
use dragonfly_client_core::{Error, Result};
use std::cmp::{max, min};
use std::fs::{File, OpenOptions};
use std::future::Future;
use std::io;
use std::os::unix::fs::FileExt;
use std::path::{Path, PathBuf};
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{ready, Context, Poll};
use tokio::io::{AsyncBufRead, AsyncRead, AsyncReadExt, ReadBuf};
use tokio::task::JoinHandle;
#[cfg(target_os = "linux")]
pub type Content = super::content_linux::Content;
#[cfg(target_os = "macos")]
pub type Content = super::content_macos::Content;
pub const DEFAULT_CONTENT_DIR: &str = "content";
pub const DEFAULT_TASK_DIR: &str = "tasks";
pub const DEFAULT_PERSISTENT_TASK_DIR: &str = "persistent-tasks";
pub const DEFAULT_PERSISTENT_CACHE_TASK_DIR: &str = "persistent-cache-tasks";
pub const DEFAULT_FD_CACHE_CAPACITY: usize = 1024;
pub struct WritePieceResponse {
pub length: u64,
pub hash: String,
}
pub struct WritePersistentTaskResponse {
pub length: u64,
pub hash: String,
}
pub struct WritePersistentCacheTaskResponse {
pub length: u64,
pub hash: String,
}
pub async fn new_content(config: Arc<Config>, dir: &Path) -> Result<Content> {
Content::new(config, dir).await
}
pub fn calculate_piece_range(offset: u64, length: u64, range: Option<Range>) -> (u64, u64) {
if let Some(range) = range {
let target_offset = max(offset, range.start);
let target_length =
min(offset + length - 1, range.start + range.length - 1) - target_offset + 1;
(target_offset, target_length)
} else {
(offset, length)
}
}
pub struct FDCache {
read_fds: Mutex<LruCache<PathBuf, Arc<File>>>,
write_fds: Mutex<LruCache<PathBuf, Arc<File>>>,
}
impl FDCache {
pub fn new(capacity: usize) -> Self {
Self {
read_fds: Mutex::new(LruCache::new(capacity)),
write_fds: Mutex::new(LruCache::new(capacity)),
}
}
pub async fn open(&self, path: &Path) -> Result<Arc<File>> {
if let Some(fd) = self.read_fds.lock()?.get(path) {
return Ok(fd.clone());
}
let path = path.to_path_buf();
let fd = Arc::new(
tokio::task::spawn_blocking({
let path = path.clone();
move || File::open(path)
})
.await
.map_err(io::Error::other)??,
);
self.read_fds.lock()?.put(path, fd.clone());
Ok(fd)
}
pub async fn open_write(&self, path: &Path) -> Result<Arc<File>> {
if let Some(fd) = self.write_fds.lock()?.get(path) {
return Ok(fd.clone());
}
let path = path.to_path_buf();
let fd = Arc::new(
tokio::task::spawn_blocking({
let path = path.clone();
move || {
OpenOptions::new()
.truncate(false)
.write(true)
.open(path.as_path())
}
})
.await
.map_err(io::Error::other)??,
);
self.write_fds.lock()?.put(path, fd.clone());
Ok(fd)
}
pub fn remove(&self, path: &Path) -> Result<()> {
self.read_fds.lock()?.pop(path);
self.write_fds.lock()?.pop(path);
Ok(())
}
}
const MAX_BUFFER_POOL_IDLE_BYTES: usize = 64 * 1024 * 1024;
struct BufferPool {
idle: Mutex<(Vec<BytesMut>, usize)>,
max_idle_bytes: usize,
}
static BUFFER_POOL: BufferPool = BufferPool {
idle: Mutex::new((Vec::new(), 0)),
max_idle_bytes: MAX_BUFFER_POOL_IDLE_BYTES,
};
impl BufferPool {
fn checkout(&self, capacity: usize) -> BytesMut {
let mut idle = self.idle.lock().unwrap();
while let Some(buffer) = idle.0.pop() {
idle.1 -= buffer.capacity();
if buffer.capacity() >= capacity {
return buffer;
}
}
drop(idle);
let mut buffer = BytesMut::zeroed(capacity);
buffer.clear();
buffer
}
fn checkout_for_read(&self, len: usize) -> BytesMut {
let mut buffer = self.checkout(len);
unsafe { buffer.set_len(len) };
buffer
}
fn give_back(&self, mut buffer: BytesMut) {
buffer.clear();
let mut idle = self.idle.lock().unwrap();
if idle.1 + buffer.capacity() <= self.max_idle_bytes {
idle.1 += buffer.capacity();
idle.0.push(buffer);
}
}
}
struct PooledChunk(BytesMut);
impl AsRef<[u8]> for PooledChunk {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Drop for PooledChunk {
fn drop(&mut self) {
BUFFER_POOL.give_back(std::mem::take(&mut self.0));
}
}
enum RangeReaderState {
Idle,
Reading(JoinHandle<io::Result<(Vec<u8>, usize)>>),
}
pub struct RangeReader {
fd: Arc<File>,
offset: u64,
remaining: u64,
capacity: usize,
buf: Vec<u8>,
pos: usize,
filled: usize,
state: RangeReaderState,
}
impl RangeReader {
pub fn new(fd: Arc<File>, offset: u64, length: u64, buffer_size: usize) -> Self {
let capacity = min(max(buffer_size, 1) as u64, length) as usize;
Self {
fd,
offset,
remaining: length,
capacity,
buf: Vec::new(),
pos: 0,
filled: 0,
state: RangeReaderState::Idle,
}
}
pub fn into_parts(self) -> (Arc<File>, u64, u64) {
let buffered = (self.filled - self.pos) as u64;
(self.fd, self.offset - buffered, self.remaining + buffered)
}
pub async fn read_chunk(&mut self) -> Result<Bytes> {
if self.pos < self.filled {
let chunk = Bytes::copy_from_slice(&self.buf[self.pos..self.filled]);
self.pos = self.filled;
return Ok(chunk);
}
if self.remaining == 0 {
return Ok(Bytes::new());
}
let len = min(self.capacity as u64, self.remaining) as usize;
let mut buf = BUFFER_POOL.checkout_for_read(len);
let fd = self.fd.clone();
let offset = self.offset;
let (mut buf, n) = tokio::task::spawn_blocking(move || {
let n = fd.read_at(&mut buf, offset)?;
Ok::<_, io::Error>((buf, n))
})
.await
.map_err(io::Error::other)??;
self.offset += n as u64;
self.remaining = if n == 0 { 0 } else { self.remaining - n as u64 };
buf.truncate(n);
Ok(Bytes::from_owner(PooledChunk(buf)))
}
}
impl AsyncBufRead for RangeReader {
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
let this = self.get_mut();
loop {
match &mut this.state {
RangeReaderState::Idle => {
if this.pos < this.filled || this.remaining == 0 {
break;
}
let len = min(this.capacity as u64, this.remaining) as usize;
let mut buf = std::mem::take(&mut this.buf);
buf.resize(len, 0);
let fd = this.fd.clone();
let offset = this.offset;
this.state =
RangeReaderState::Reading(tokio::task::spawn_blocking(move || {
let n = fd.read_at(&mut buf, offset)?;
Ok((buf, n))
}));
}
RangeReaderState::Reading(handle) => {
let result = ready!(Pin::new(handle).poll(cx));
this.state = RangeReaderState::Idle;
let (buf, n) = result.map_err(io::Error::other)??;
this.buf = buf;
this.pos = 0;
this.filled = n;
this.offset += n as u64;
this.remaining = if n == 0 { 0 } else { this.remaining - n as u64 };
}
}
}
Poll::Ready(Ok(&this.buf[this.pos..this.filled]))
}
fn consume(self: Pin<&mut Self>, amt: usize) {
let this = self.get_mut();
this.pos = min(this.pos + amt, this.filled);
}
}
impl AsyncRead for RangeReader {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let inner = ready!(self.as_mut().poll_fill_buf(cx))?;
let amt = min(inner.len(), buf.remaining());
buf.put_slice(&inner[..amt]);
self.consume(amt);
Poll::Ready(Ok(()))
}
}
pub async fn write_range<R: AsyncRead + Unpin + ?Sized>(
fd: Arc<File>,
offset: u64,
expected_length: u64,
buffer_size: usize,
reader: &mut R,
) -> Result<WritePieceResponse> {
let buffer_size = max(buffer_size, 1);
let mut reader = reader.take(expected_length);
let mut buffer = BUFFER_POOL.checkout(buffer_size);
let mut in_flight: Option<JoinHandle<io::Result<BytesMut>>> = None;
let mut hasher = crc32fast::Hasher::new();
let mut write_offset = offset;
let mut length: u64 = 0;
loop {
while buffer.len() < buffer_size {
if reader.read_buf(&mut buffer).await? == 0 {
break;
}
}
if buffer.is_empty() {
break;
}
hasher.update(&buffer);
let recycled = match in_flight.take() {
Some(handle) => handle.await.map_err(io::Error::other)??,
None => BUFFER_POOL.checkout(buffer_size),
};
let full = std::mem::replace(&mut buffer, recycled);
length += full.len() as u64;
let fd = fd.clone();
let full_offset = write_offset;
write_offset += full.len() as u64;
in_flight = Some(tokio::task::spawn_blocking(move || {
fd.write_all_at(&full, full_offset)?;
let mut recycled = full;
recycled.clear();
Ok(recycled)
}));
}
if let Some(handle) = in_flight.take() {
BUFFER_POOL.give_back(handle.await.map_err(io::Error::other)??);
}
BUFFER_POOL.give_back(buffer);
if length != expected_length {
return Err(Error::Unknown(format!(
"expected length {expected_length} but got {length}"
)));
}
Ok(WritePieceResponse {
length,
hash: hasher.finalize().to_string(),
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
use tempfile::tempdir;
use tokio::io::{AsyncBufReadExt, AsyncReadExt};
fn pattern(length: usize) -> Vec<u8> {
(0..length).map(|i| (i % 251) as u8).collect()
}
#[tokio::test]
async fn test_fd_cache() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
tokio::fs::write(&path, b"hello, world!").await.unwrap();
let cache = FDCache::new(DEFAULT_FD_CACHE_CAPACITY);
let first = cache.open(&path).await.unwrap();
let second = cache.open(&path).await.unwrap();
assert!(Arc::ptr_eq(&first, &second));
let first_write = cache.open_write(&path).await.unwrap();
let second_write = cache.open_write(&path).await.unwrap();
assert!(Arc::ptr_eq(&first_write, &second_write));
assert!(!Arc::ptr_eq(&first, &first_write));
let _ = cache.remove(&path);
let third = cache.open(&path).await.unwrap();
assert!(!Arc::ptr_eq(&first, &third));
let third_write = cache.open_write(&path).await.unwrap();
assert!(!Arc::ptr_eq(&first_write, &third_write));
let _ = cache.remove(&path);
tokio::fs::remove_file(&path).await.unwrap();
assert!(cache.open(&path).await.is_err());
assert!(cache.open_write(&path).await.is_err());
}
#[tokio::test]
async fn test_fd_cache_open_write_read_only_file() {
use std::os::unix::fs::PermissionsExt;
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
tokio::fs::write(&path, b"hello, world!").await.unwrap();
let mut permissions = tokio::fs::metadata(&path).await.unwrap().permissions();
permissions.set_readonly(true);
tokio::fs::set_permissions(&path, permissions)
.await
.unwrap();
let cache = FDCache::new(DEFAULT_FD_CACHE_CAPACITY);
let fd = cache.open(&path).await.unwrap();
let mut buffer = vec![0u8; 13];
fd.read_at(&mut buffer, 0).unwrap();
assert_eq!(buffer, b"hello, world!");
assert!(cache.open_write(&path).await.is_err());
tokio::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o644))
.await
.unwrap();
let fd = cache.open_write(&path).await.unwrap();
fd.write_all_at(b"HELLO", 0).unwrap();
assert_eq!(&tokio::fs::read(&path).await.unwrap()[..5], b"HELLO");
}
#[tokio::test]
async fn test_range_reader_poll_after_read_error() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
tokio::fs::write(&path, b"hello, world!").await.unwrap();
let fd = Arc::new(
OpenOptions::new()
.truncate(false)
.write(true)
.open(&path)
.unwrap(),
);
let mut reader = RangeReader::new(fd, 0, 13, 4);
let mut buffer = Vec::new();
assert!(reader.read_to_end(&mut buffer).await.is_err());
let mut buffer = Vec::new();
let _ = reader.read_to_end(&mut buffer).await;
}
#[tokio::test]
async fn test_write_range_write_error() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
tokio::fs::write(&path, b"hello, world!").await.unwrap();
let fd = Arc::new(File::open(&path).unwrap());
let data = pattern(16 * 1024);
assert!(
write_range(fd, 0, data.len() as u64, 4, &mut data.as_slice())
.await
.is_err()
);
}
#[tokio::test]
async fn test_write_range_then_range_reader_shared_cache() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
tokio::fs::write(&path, b"").await.unwrap();
let cache = FDCache::new(DEFAULT_FD_CACHE_CAPACITY);
let data = pattern(16 * 1024);
write_range(
cache.open_write(&path).await.unwrap(),
0,
data.len() as u64,
4 * 1024,
&mut data.as_slice(),
)
.await
.unwrap();
let mut reader = RangeReader::new(
cache.open(&path).await.unwrap(),
0,
data.len() as u64,
4 * 1024,
);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).await.unwrap();
assert_eq!(buffer, data);
}
#[tokio::test]
async fn test_range_reader() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
tokio::fs::write(&path, b"hello, world!").await.unwrap();
let fd = Arc::new(File::open(&path).unwrap());
let mut reader = RangeReader::new(fd.clone(), 0, 13, 512);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).await.unwrap();
assert_eq!(buffer, b"hello, world!");
let mut reader = RangeReader::new(fd.clone(), 7, 5, 2);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).await.unwrap();
assert_eq!(buffer, b"world");
let mut reader = RangeReader::new(fd.clone(), 7, 100, 512);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).await.unwrap();
assert_eq!(buffer, b"world!");
let mut reader = RangeReader::new(fd, 0, 0, 512);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).await.unwrap();
assert!(buffer.is_empty());
}
#[tokio::test]
async fn test_range_reader_multiple_fills() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
let data = pattern(256 * 1024);
tokio::fs::write(&path, &data).await.unwrap();
let fd = Arc::new(File::open(&path).unwrap());
let mut reader = RangeReader::new(fd.clone(), 0, data.len() as u64, 4 * 1024);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).await.unwrap();
assert_eq!(buffer, data);
let mut reader = RangeReader::new(fd, 12_345, 30_000, 4 * 1024);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).await.unwrap();
assert_eq!(buffer, &data[12_345..42_345]);
}
#[tokio::test]
async fn test_range_reader_into_parts() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
tokio::fs::write(&path, b"hello, world!").await.unwrap();
let fd = Arc::new(File::open(&path).unwrap());
let reader = RangeReader::new(fd.clone(), 2, 11, 5);
let (parts_fd, offset, remaining) = reader.into_parts();
assert!(Arc::ptr_eq(&parts_fd, &fd));
assert_eq!(offset, 2);
assert_eq!(remaining, 11);
let mut reader = RangeReader::new(fd, 2, 11, 5);
assert_eq!(reader.fill_buf().await.unwrap(), b"llo, ");
reader.consume(2);
let (_, offset, remaining) = reader.into_parts();
assert_eq!(offset, 4);
assert_eq!(remaining, 9);
}
#[tokio::test]
async fn test_range_reader_read_chunk() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
let data = pattern(64 * 1024);
tokio::fs::write(&path, &data).await.unwrap();
let fd = Arc::new(File::open(&path).unwrap());
let mut reader = RangeReader::new(fd.clone(), 0, data.len() as u64, 4 * 1024);
let mut buffer = Vec::new();
loop {
let chunk = reader.read_chunk().await.unwrap();
if chunk.is_empty() {
break;
}
buffer.extend_from_slice(&chunk);
}
assert_eq!(buffer, data);
let mut reader = RangeReader::new(fd.clone(), 60 * 1024, 100 * 1024, 4 * 1024);
let mut buffer = Vec::new();
loop {
let chunk = reader.read_chunk().await.unwrap();
if chunk.is_empty() {
break;
}
buffer.extend_from_slice(&chunk);
}
assert_eq!(buffer, &data[60 * 1024..]);
let mut reader = RangeReader::new(fd, 0, 0, 4 * 1024);
assert!(reader.read_chunk().await.unwrap().is_empty());
}
#[tokio::test]
async fn test_range_reader_read_chunk_after_fill_buf() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
tokio::fs::write(&path, b"hello, world!").await.unwrap();
let fd = Arc::new(File::open(&path).unwrap());
let mut reader = RangeReader::new(fd, 0, 13, 5);
assert_eq!(reader.fill_buf().await.unwrap(), b"hello");
reader.consume(2);
assert_eq!(&reader.read_chunk().await.unwrap()[..], b"llo");
assert_eq!(&reader.read_chunk().await.unwrap()[..], b", wor");
assert_eq!(&reader.read_chunk().await.unwrap()[..], b"ld!");
assert!(reader.read_chunk().await.unwrap().is_empty());
}
#[tokio::test]
async fn test_range_reader_read_chunk_error() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
tokio::fs::write(&path, b"hello, world!").await.unwrap();
let fd = Arc::new(
OpenOptions::new()
.truncate(false)
.write(true)
.open(&path)
.unwrap(),
);
let mut reader = RangeReader::new(fd, 0, 13, 4);
assert!(reader.read_chunk().await.is_err());
}
#[tokio::test]
async fn test_range_reader_fill_buf_and_consume() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
tokio::fs::write(&path, b"hello, world!").await.unwrap();
let fd = Arc::new(File::open(&path).unwrap());
let mut reader = RangeReader::new(fd, 0, 13, 5);
assert_eq!(reader.fill_buf().await.unwrap(), b"hello");
reader.consume(2);
assert_eq!(reader.fill_buf().await.unwrap(), b"llo");
reader.consume(3);
assert_eq!(reader.fill_buf().await.unwrap(), b", wor");
reader.consume(5);
assert_eq!(reader.fill_buf().await.unwrap(), b"ld!");
reader.consume(100);
assert!(reader.fill_buf().await.unwrap().is_empty());
}
#[tokio::test]
async fn test_range_reader_copy_buf() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
let data = pattern(64 * 1024);
tokio::fs::write(&path, &data).await.unwrap();
let fd = Arc::new(File::open(&path).unwrap());
let mut reader = RangeReader::new(fd, 1_000, 50_000, 8 * 1024);
let mut writer = Cursor::new(Vec::new());
let copied = tokio::io::copy_buf(&mut reader, &mut writer).await.unwrap();
assert_eq!(copied, 50_000);
assert_eq!(writer.into_inner(), &data[1_000..51_000]);
}
#[tokio::test]
async fn test_range_reader_concurrent_readers() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
let data = pattern(64 * 1024);
tokio::fs::write(&path, &data).await.unwrap();
let fd = Arc::new(File::open(&path).unwrap());
let range_length: u64 = 8 * 1024;
let handles: Vec<_> = (0..8u64)
.map(|i| {
let fd = fd.clone();
tokio::spawn(async move {
let mut reader = RangeReader::new(fd, i * range_length, range_length, 1024);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).await.unwrap();
(i, buffer)
})
})
.collect();
for handle in handles {
let (i, buffer) = handle.await.unwrap();
let start = (i * range_length) as usize;
assert_eq!(buffer, &data[start..start + range_length as usize]);
}
}
#[tokio::test]
async fn test_range_reader_buffer_sizes() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
tokio::fs::write(&path, b"hello, world!").await.unwrap();
let fd = Arc::new(File::open(&path).unwrap());
for buffer_size in [13, 512, 1, 0] {
let mut reader = RangeReader::new(fd.clone(), 0, 13, buffer_size);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).await.unwrap();
assert_eq!(buffer, b"hello, world!");
}
}
#[tokio::test]
async fn test_range_reader_offset_beyond_eof() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
tokio::fs::write(&path, b"hello, world!").await.unwrap();
let fd = Arc::new(File::open(&path).unwrap());
let mut reader = RangeReader::new(fd.clone(), 13, 5, 512);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).await.unwrap();
assert!(buffer.is_empty());
let mut reader = RangeReader::new(fd, 100, 5, 512);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).await.unwrap();
assert!(buffer.is_empty());
}
#[tokio::test]
async fn test_range_reader_small_chunk_reads() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
tokio::fs::write(&path, b"hello, world!").await.unwrap();
let fd = Arc::new(File::open(&path).unwrap());
let mut reader = RangeReader::new(fd, 0, 13, 512);
let mut buffer = Vec::new();
let mut chunk = [0u8; 3];
loop {
let n = reader.read(&mut chunk).await.unwrap();
if n == 0 {
break;
}
buffer.extend_from_slice(&chunk[..n]);
}
assert_eq!(buffer, b"hello, world!");
}
#[tokio::test]
async fn test_write_range() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
tokio::fs::write(&path, b"").await.unwrap();
let fd = Arc::new(
OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.unwrap(),
);
let data = b"hello, world!";
let response = write_range(fd, 0, data.len() as u64, 512, &mut data.as_slice())
.await
.unwrap();
assert_eq!(response.length, data.len() as u64);
assert_eq!(response.hash, crc32fast::hash(data).to_string());
assert_eq!(tokio::fs::read(&path).await.unwrap(), data);
}
#[tokio::test]
async fn test_write_range_multiple_fills() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
tokio::fs::write(&path, b"").await.unwrap();
let fd = Arc::new(
OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.unwrap(),
);
let data = pattern(256 * 1024);
let response = write_range(fd, 0, data.len() as u64, 4 * 1024, &mut data.as_slice())
.await
.unwrap();
assert_eq!(response.length, data.len() as u64);
assert_eq!(response.hash, crc32fast::hash(&data).to_string());
assert_eq!(tokio::fs::read(&path).await.unwrap(), data);
}
#[tokio::test]
async fn test_write_range_offset() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
tokio::fs::write(&path, vec![0u8; 64]).await.unwrap();
let fd = Arc::new(
OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.unwrap(),
);
write_range(fd, 7, 5, 2, &mut b"world".as_slice())
.await
.unwrap();
let content = tokio::fs::read(&path).await.unwrap();
assert_eq!(&content[7..12], b"world");
assert_eq!(content.len(), 64);
}
#[tokio::test]
async fn test_write_range_length_mismatch() {
let temp_dir = tempdir().unwrap();
let data = pattern(1024);
let path = temp_dir.path().join("short");
tokio::fs::write(&path, b"").await.unwrap();
let fd = Arc::new(
OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.unwrap(),
);
assert!(write_range(fd, 0, 2048, 512, &mut data.as_slice())
.await
.is_err());
let path = temp_dir.path().join("long");
tokio::fs::write(&path, b"").await.unwrap();
let fd = Arc::new(
OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.unwrap(),
);
let response = write_range(fd, 0, 512, 512, &mut data.as_slice())
.await
.unwrap();
assert_eq!(response.length, 512);
assert_eq!(response.hash, crc32fast::hash(&data[..512]).to_string());
assert_eq!(tokio::fs::read(&path).await.unwrap(), &data[..512]);
}
#[tokio::test]
async fn test_write_range_buffer_sizes() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
for buffer_size in [13, 512, 1, 0] {
tokio::fs::write(&path, b"").await.unwrap();
let fd = Arc::new(
OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.unwrap(),
);
let data = b"hello, world!";
let response = write_range(fd, 0, data.len() as u64, buffer_size, &mut data.as_slice())
.await
.unwrap();
assert_eq!(response.hash, crc32fast::hash(data).to_string());
assert_eq!(tokio::fs::read(&path).await.unwrap(), data);
}
}
#[tokio::test]
async fn test_write_range_concurrent_writers() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
let data = pattern(64 * 1024);
tokio::fs::write(&path, vec![0u8; data.len()])
.await
.unwrap();
let fd = Arc::new(
OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.unwrap(),
);
let range_length: usize = 8 * 1024;
let handles: Vec<_> = (0..8usize)
.map(|i| {
let fd = fd.clone();
let piece = data[i * range_length..(i + 1) * range_length].to_vec();
tokio::spawn(async move {
write_range(
fd,
(i * range_length) as u64,
piece.len() as u64,
1024,
&mut piece.as_slice(),
)
.await
.unwrap();
})
})
.collect();
for handle in handles {
handle.await.unwrap();
}
assert_eq!(tokio::fs::read(&path).await.unwrap(), data);
}
#[test]
fn test_buffer_pool_reuse() {
let pool = BufferPool {
idle: Mutex::new((Vec::new(), 0)),
max_idle_bytes: 4096,
};
let buffer = pool.checkout(1024);
assert_eq!(buffer.capacity(), 1024);
assert!(buffer.is_empty());
let ptr = buffer.as_ptr();
pool.give_back(buffer);
let buffer = pool.checkout(512);
assert_eq!(buffer.as_ptr(), ptr);
assert_eq!(buffer.capacity(), 1024);
pool.give_back(buffer);
let buffer = pool.checkout(2048);
assert_eq!(buffer.capacity(), 2048);
assert!(pool.idle.lock().unwrap().0.is_empty());
pool.give_back(buffer);
pool.give_back(BytesMut::zeroed(4096));
let idle = pool.idle.lock().unwrap();
assert_eq!(idle.0.len(), 1);
assert_eq!(idle.1, 2048);
}
#[test]
fn test_buffer_pool_checkout_for_read() {
let pool = BufferPool {
idle: Mutex::new((Vec::new(), 0)),
max_idle_bytes: 4096,
};
let mut buffer = pool.checkout_for_read(256);
assert_eq!(buffer.len(), 256);
buffer.fill(0xAB);
pool.give_back(buffer);
let buffer = pool.checkout_for_read(128);
assert_eq!(buffer.len(), 128);
assert!(buffer.iter().all(|&b| b == 0xAB));
}
#[tokio::test]
async fn test_calculate_piece_range() {
let test_cases = vec![
(1, 4, None, 1, 4),
(
1,
4,
Some(Range {
start: 1,
length: 4,
}),
1,
4,
),
(
1,
4,
Some(Range {
start: 2,
length: 1,
}),
2,
1,
),
(
1,
4,
Some(Range {
start: 1,
length: 1,
}),
1,
1,
),
(
1,
4,
Some(Range {
start: 4,
length: 1,
}),
4,
1,
),
(
1,
4,
Some(Range {
start: 0,
length: 2,
}),
1,
1,
),
(
1,
4,
Some(Range {
start: 4,
length: 3,
}),
4,
1,
),
];
for (piece_offset, piece_length, range, expected_offset, expected_length) in test_cases {
let (target_offset, target_length) =
calculate_piece_range(piece_offset, piece_length, range);
assert_eq!(target_offset, expected_offset);
assert_eq!(target_length, expected_length);
}
}
}