use std::mem::ManuallyDrop;
use std::io::{Error, Result, ErrorKind};
#[cfg(feature = "wal")]
use log::{warn, info, debug};
use tokio::sync::{mpsc, oneshot, OwnedSemaphorePermit};
#[cfg(feature = "wal")]
use tokio::sync::OwnedMutexGuard;
use hyperfile_reactor::{Capacity, Channel, Task, TaskBuilder, TaskHandler};
use crate::SegmentId;
#[cfg(feature = "wal")]
use crate::inode::OnDiskState;
use crate::buffer::{DataBlock, AlignedDataBlockWrapper, BatchDataBlockWrapper};
use super::hyper::Hyper;
use super::HyperTrait;
pub struct ChannelGroup<Ctx> {
inner: TaskHandler<Ctx>,
highprio: Channel<Ctx>,
cb: Channel<Ctx>,
user: Channel<Ctx>,
}
impl<Ctx> Clone for ChannelGroup<Ctx> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
highprio: self.highprio,
cb: self.cb,
user: self.user,
}
}
}
impl<Ctx> ChannelGroup<Ctx> {
pub fn send(&self, ctx: Ctx) -> Result<()> {
self.inner.send(self.user, ctx).map_err(|_| {
Error::new(ErrorKind::BrokenPipe, "reactor handler task died")
})
}
pub fn send_cb(&self, ctx: Ctx) -> Result<()> {
self.inner.send(self.cb, ctx).map_err(|_| {
Error::new(ErrorKind::BrokenPipe, "reactor handler task died")
})
}
pub fn send_highprio(&self, ctx: Ctx) -> Result<()> {
self.inner.send(self.highprio, ctx).map_err(|_| {
Error::new(ErrorKind::BrokenPipe, "reactor handler task died")
})
}
}
pub fn build_channel_group<Ctx>() -> (TaskBuilder<Ctx>, impl FnOnce(TaskHandler<Ctx>) -> ChannelGroup<Ctx>)
where
Ctx: Send + 'static,
{
let mut builder = TaskBuilder::<Ctx>::new();
let highprio = builder.add_channel(0, Capacity::Unbounded);
let cb = builder.add_channel(1, Capacity::Unbounded);
let user = builder.add_channel(2, Capacity::Unbounded);
(builder, move |inner| ChannelGroup { inner, highprio, cb, user })
}
pub type FileRespGetAttr = Result<libc::stat>;
pub type FileRespSetAttr = Result<libc::stat>;
pub type FileRespRead = Result<usize>;
pub type FileRespWrite = Result<usize>;
pub type FileRespWriteZero = Result<usize>;
pub type FileRespTrunc = Result<()>;
pub type FileRespFlush = Result<SegmentId>;
pub type FileRespRelease = Result<SegmentId>;
pub type FileRespLastCno = u64;
pub enum FileResp {
GetAttr(oneshot::Sender<FileRespGetAttr>),
SetAttr(oneshot::Sender<FileRespSetAttr>),
Read(mpsc::Sender<FileRespRead>),
Write(mpsc::Sender<FileRespWrite>),
WriteZero(mpsc::Sender<FileRespWriteZero>),
WriteAlignedBatch(mpsc::Sender<FileRespWrite>),
WriteBatch(mpsc::Sender<FileRespWrite>),
Trunc(oneshot::Sender<FileRespTrunc>),
Flush(oneshot::Sender<FileRespFlush>),
#[cfg(feature = "wal")]
WalFlush,
#[cfg(feature = "wal")]
WalFlushDone,
#[cfg(feature = "wal")]
WalFlushRecovery,
Release(oneshot::Sender<FileRespRelease>),
LastCno(oneshot::Sender<FileRespLastCno>),
}
impl FileResp {
pub fn to_getattr(self) -> oneshot::Sender<FileRespGetAttr> {
match self {
Self::GetAttr(tx) => tx,
_ => panic!("FileResp::to_getattr called on wrong variant"),
}
}
pub fn to_setattr(self) -> oneshot::Sender<FileRespSetAttr> {
match self {
Self::SetAttr(tx) => tx,
_ => panic!("FileResp::to_setattr called on wrong variant"),
}
}
pub fn to_read(self) -> mpsc::Sender<FileRespRead> {
match self {
Self::Read(tx) => tx,
_ => panic!("FileResp::to_read called on wrong variant"),
}
}
pub fn to_write(self) -> mpsc::Sender<FileRespWrite> {
match self {
Self::Write(tx) | Self::WriteAlignedBatch(tx) | Self::WriteBatch(tx) => tx,
_ => panic!("FileResp::to_write called on wrong variant"),
}
}
pub fn to_write_zero(self) -> mpsc::Sender<FileRespWriteZero> {
match self {
Self::WriteZero(tx) => tx,
_ => panic!("FileResp::to_write_zero called on wrong variant"),
}
}
pub fn to_trunc(self) -> oneshot::Sender<FileRespTrunc> {
match self {
Self::Trunc(tx) => tx,
_ => panic!("FileResp::to_trunc called on wrong variant"),
}
}
pub fn to_flush(self) -> oneshot::Sender<FileRespFlush> {
match self {
Self::Flush(tx) => tx,
_ => panic!("FileResp::to_flush called on wrong variant"),
}
}
#[cfg(feature = "wal")]
pub fn to_wal_flush(self) {
match self {
Self::WalFlush => {}
_ => panic!("FileResp::to_wal_flush called on wrong variant"),
}
}
#[cfg(feature = "wal")]
pub fn to_wal_flush_done(self) {
match self {
Self::WalFlushDone => {}
_ => panic!("FileResp::to_wal_flush_done called on wrong variant"),
}
}
#[cfg(feature = "wal")]
pub fn to_wal_flush_recovery(self) {
match self {
Self::WalFlushRecovery => {}
_ => panic!("FileResp::to_wal_flush_recovery called on wrong variant"),
}
}
pub fn to_release(self) -> oneshot::Sender<FileRespRelease> {
match self {
Self::Release(tx) => tx,
_ => panic!("FileResp::to_release called on wrong variant"),
}
}
pub fn to_last_cno(self) -> oneshot::Sender<FileRespLastCno> {
match self {
Self::LastCno(tx) => tx,
_ => panic!("FileResp::to_last_cno called on wrong variant"),
}
}
pub fn clone_write_resp(&self) -> mpsc::Sender<FileRespWrite> {
match self {
Self::Write(tx) | Self::WriteAlignedBatch(tx) | Self::WriteBatch(tx) => tx.clone(),
_ => panic!("FileResp::clone_write_resp called on wrong variant"),
}
}
pub fn clone_write_zero_resp(&self) -> mpsc::Sender<FileRespWriteZero> {
match self {
Self::WriteZero(tx) => tx.clone(),
_ => panic!("FileResp::clone_write_zero_resp called on wrong variant"),
}
}
}
pub struct FileReqRead<'a> {
pub buf: &'a mut [u8],
pub offset: usize,
pub fh: ChannelGroup<FileContext<'a>>,
}
pub struct FileReqWrite<'a> {
pub buf: &'a [u8],
pub offset: usize,
pub fetched: Vec<DataBlock>,
pub spawn_write_permit: Option<OwnedSemaphorePermit>, pub fh: ChannelGroup<FileContext<'a>>,
}
pub struct FileReqWriteZero<'a> {
pub offset: usize,
pub len: usize,
pub fetched: Vec<DataBlock>,
pub spawn_write_permit: Option<OwnedSemaphorePermit>, pub fh: ChannelGroup<FileContext<'a>>,
}
pub struct FileReqWriteAlignedBatch {
pub data_blocks: Vec<AlignedDataBlockWrapper>,
}
pub struct FileReqWriteBatch {
pub data_blocks: Vec<BatchDataBlockWrapper>,
}
pub struct FileReqTrunc {
pub offset: usize,
}
pub struct FileReqGetAttr {}
pub struct FileReqSetAttr {
pub stat: libc::stat,
}
pub struct FileReqFlush<'a> {
pub fh: ChannelGroup<FileContext<'a>>,
}
#[cfg(feature = "wal")]
pub struct FileReqWalFlush<'a> {
pub fh: ChannelGroup<FileContext<'a>>,
}
#[cfg(feature = "wal")]
pub struct FileReqWalFlushDone {
pub lock: OwnedMutexGuard<()>,
pub segid: SegmentId,
pub od_state: OnDiskState,
pub bmap_cache_limit: usize,
}
#[cfg(feature = "wal")]
pub struct FileReqWalFlushRecovery {
pub lock: OwnedMutexGuard<()>,
}
pub struct FileReqRelease<'a> {
pub fh: ChannelGroup<FileContext<'a>>,
}
pub struct FileReqLastCno {}
pub enum FileReqOp {
GetAttr,
SetAttr,
Read,
Write,
WriteAbsorb,
WriteAbsorbBh,
WriteZero,
WriteZeroAbsorb,
WriteZeroAbsorbBh,
WriteAlignedBatch,
WriteBatch,
Trunc,
Flush,
FlushData,
#[cfg(feature = "wal")]
WalFlush,
#[cfg(feature = "wal")]
WalFlushDone,
#[cfg(feature = "wal")]
WalFlushRecovery,
Release,
LastCno,
#[cfg(feature = "wal")]
WriteWal,
#[cfg(feature = "wal")]
WriteZeroWal,
}
#[repr(C)]
pub union FileReqBody<'a> {
getattr: ManuallyDrop<FileReqGetAttr>,
setattr: ManuallyDrop<FileReqSetAttr>,
read: ManuallyDrop<FileReqRead<'a>>,
write: ManuallyDrop<FileReqWrite<'a>>,
write_zero: ManuallyDrop<FileReqWriteZero<'a>>,
write_aligned_batch: ManuallyDrop<FileReqWriteAlignedBatch>,
write_batch: ManuallyDrop<FileReqWriteBatch>,
trunc: ManuallyDrop<FileReqTrunc>,
#[cfg(feature = "wal")]
wal_flush: ManuallyDrop<FileReqWalFlush<'a>>,
#[cfg(feature = "wal")]
wal_flush_done: ManuallyDrop<FileReqWalFlushDone>,
#[cfg(feature = "wal")]
wal_flush_recovery: ManuallyDrop<FileReqWalFlushRecovery>,
flush: ManuallyDrop<FileReqFlush<'a>>,
release: ManuallyDrop<FileReqRelease<'a>>,
last_cno: ManuallyDrop<FileReqLastCno>,
}
pub struct FileReq<'a> {
op: FileReqOp,
body: FileReqBody<'a>,
}
pub struct FileContext<'a> {
req: Option<FileReq<'a>>,
resp: Option<FileResp>,
}
impl<'a> FileContext<'a> {
pub fn take(mut self) -> (FileReq<'a>, FileResp) {
(
self.req.take().unwrap(),
self.resp.take().unwrap()
)
}
pub fn new_getattr() -> (Self, oneshot::Receiver<FileRespGetAttr>) {
let (tx, rx) = oneshot::channel::<FileRespGetAttr>();
let req = FileReq {
op: FileReqOp::GetAttr,
body: FileReqBody { getattr: ManuallyDrop::new(FileReqGetAttr {}), },
};
let resp = FileResp::GetAttr(tx);
(Self { req: Some(req), resp: Some(resp), }, rx)
}
pub fn new_setattr(stat: libc::stat) -> (Self, oneshot::Receiver<FileRespSetAttr>) {
let (tx, rx) = oneshot::channel::<FileRespSetAttr>();
let req = FileReq {
op: FileReqOp::SetAttr,
body: FileReqBody { setattr: ManuallyDrop::new(FileReqSetAttr { stat: stat }), },
};
let resp = FileResp::SetAttr(tx);
(Self { req: Some(req), resp: Some(resp), }, rx)
}
pub fn new_read(buf: &'a mut [u8], offset: usize, fh: ChannelGroup<FileContext<'a>>) -> (Self, mpsc::Sender<FileRespRead>, mpsc::Receiver<FileRespRead>) {
let (tx, rx) = mpsc::channel::<FileRespRead>(1);
let req = FileReq {
op: FileReqOp::Read,
body: FileReqBody { read: ManuallyDrop::new(FileReqRead { buf: buf, offset: offset, fh: fh }), },
};
let resp = FileResp::Read(tx.clone());
(Self { req: Some(req), resp: Some(resp), }, tx, rx)
}
pub fn new_write(buf: &'a [u8], offset: usize, fh: ChannelGroup<FileContext<'a>>) -> (Self, mpsc::Sender<FileRespWrite>, mpsc::Receiver<FileRespWrite>) {
let (tx, rx) = mpsc::channel::<FileRespWrite>(1);
let req = FileReq {
op: FileReqOp::Write,
body: FileReqBody { write: ManuallyDrop::new(FileReqWrite { buf: buf, offset: offset, spawn_write_permit: None, fh: fh, fetched: Vec::new(), }), },
};
let resp = FileResp::Write(tx.clone());
(Self { req: Some(req), resp: Some(resp), }, tx, rx)
}
#[cfg(feature = "wal")]
pub fn write_wal(req: FileReqWrite<'a>, resp: FileResp) -> Self {
let new_req = FileReq {
op: FileReqOp::WriteWal,
body: FileReqBody { write: ManuallyDrop::new(req), },
};
Self { req: Some(new_req), resp: Some(resp), }
}
pub fn write_absorb(req: FileReqWrite<'a>, resp: FileResp) -> Self {
let new_req = FileReq {
op: FileReqOp::WriteAbsorb,
body: FileReqBody { write: ManuallyDrop::new(req), },
};
Self { req: Some(new_req), resp: Some(resp), }
}
pub fn write_absorb_bh(req: FileReqWrite<'a>, resp: FileResp) -> Self {
let new_req = FileReq {
op: FileReqOp::WriteAbsorbBh,
body: FileReqBody { write: ManuallyDrop::new(req), },
};
Self { req: Some(new_req), resp: Some(resp), }
}
pub fn new_write_zero(offset: usize, len: usize, fh: ChannelGroup<FileContext<'a>>) -> (Self, mpsc::Sender<FileRespWrite>, mpsc::Receiver<FileRespWrite>) {
let (tx, rx) = mpsc::channel::<FileRespWriteZero>(1);
let req = FileReq {
op: FileReqOp::WriteZero,
body: FileReqBody { write_zero: ManuallyDrop::new(FileReqWriteZero { offset: offset, len: len, spawn_write_permit: None, fh: fh, fetched: Vec::new(), }), },
};
let resp = FileResp::WriteZero(tx.clone());
(Self { req: Some(req), resp: Some(resp), }, tx, rx)
}
#[cfg(feature = "wal")]
pub fn write_zero_wal(req: FileReqWriteZero<'a>, resp: FileResp) -> Self {
let new_req = FileReq {
op: FileReqOp::WriteZeroWal,
body: FileReqBody { write_zero: ManuallyDrop::new(req), },
};
Self { req: Some(new_req), resp: Some(resp), }
}
pub fn write_zero_absorb(req: FileReqWriteZero<'a>, resp: FileResp) -> Self {
let new_req = FileReq {
op: FileReqOp::WriteZeroAbsorb,
body: FileReqBody { write_zero: ManuallyDrop::new(req), },
};
Self { req: Some(new_req), resp: Some(resp), }
}
pub fn write_zero_absorb_bh(req: FileReqWriteZero<'a>, resp: FileResp) -> Self {
let new_req = FileReq {
op: FileReqOp::WriteZeroAbsorbBh,
body: FileReqBody { write_zero: ManuallyDrop::new(req), },
};
Self { req: Some(new_req), resp: Some(resp), }
}
pub fn new_write_aligned_batch(v: Vec<AlignedDataBlockWrapper>) -> (Self, mpsc::Receiver<FileRespWrite>) {
let (tx, rx) = mpsc::channel::<FileRespWrite>(1);
let new_req = FileReq {
op: FileReqOp::WriteAlignedBatch,
body: FileReqBody { write_aligned_batch: ManuallyDrop::new(FileReqWriteAlignedBatch { data_blocks: v }), },
};
let resp = FileResp::WriteAlignedBatch(tx);
(Self { req: Some(new_req), resp: Some(resp), }, rx)
}
pub fn new_write_batch(v: Vec<BatchDataBlockWrapper>) -> (Self, mpsc::Receiver<FileRespWrite>) {
let (tx, rx) = mpsc::channel::<FileRespWrite>(1);
let new_req = FileReq {
op: FileReqOp::WriteBatch,
body: FileReqBody { write_batch: ManuallyDrop::new(FileReqWriteBatch { data_blocks: v }), },
};
let resp = FileResp::WriteBatch(tx);
(Self { req: Some(new_req), resp: Some(resp), }, rx)
}
pub fn new_trunc(offset: usize) -> (Self, oneshot::Receiver<FileRespTrunc>) {
let (tx, rx) = oneshot::channel::<FileRespTrunc>();
let req = FileReq {
op: FileReqOp::Trunc,
body: FileReqBody { trunc: ManuallyDrop::new(FileReqTrunc { offset: offset }), },
};
let resp = FileResp::Trunc(tx);
(Self { req: Some(req), resp: Some(resp), }, rx)
}
pub fn new_flush(fh: ChannelGroup<FileContext<'a>>) -> (Self, oneshot::Receiver<FileRespFlush>) {
let (tx, rx) = oneshot::channel::<FileRespFlush>();
let req = FileReq {
op: FileReqOp::Flush,
body: FileReqBody { flush: ManuallyDrop::new(FileReqFlush { fh, }), },
};
let resp = FileResp::Flush(tx);
(Self { req: Some(req), resp: Some(resp), }, rx)
}
pub fn new_flush_data(fh: ChannelGroup<FileContext<'a>>) -> (Self, oneshot::Receiver<FileRespFlush>) {
let (tx, rx) = oneshot::channel::<FileRespFlush>();
let req = FileReq {
op: FileReqOp::FlushData,
body: FileReqBody { flush: ManuallyDrop::new(FileReqFlush { fh, }), },
};
let resp = FileResp::Flush(tx);
(Self { req: Some(req), resp: Some(resp), }, rx)
}
#[cfg(feature = "wal")]
pub fn new_wal_flush(fh: ChannelGroup<FileContext<'a>>) -> Self {
let req = FileReq {
op: FileReqOp::WalFlush,
body: FileReqBody { wal_flush: ManuallyDrop::new(FileReqWalFlush { fh, }), },
};
let resp = FileResp::WalFlush;
Self { req: Some(req), resp: Some(resp) }
}
#[cfg(feature = "wal")]
pub fn new_wal_flush_done(lock: OwnedMutexGuard<()>, segid: SegmentId, od_state: OnDiskState, bmap_cache_limit: usize) -> Self {
let req = FileReq {
op: FileReqOp::WalFlushDone,
body: FileReqBody { wal_flush_done: ManuallyDrop::new(FileReqWalFlushDone { lock, segid, od_state, bmap_cache_limit, }), },
};
let resp = FileResp::WalFlushDone;
Self { req: Some(req), resp: Some(resp) }
}
#[cfg(feature = "wal")]
pub fn new_wal_flush_recovery(lock: OwnedMutexGuard<()>) -> Self {
let req = FileReq {
op: FileReqOp::WalFlushRecovery,
body: FileReqBody { wal_flush_recovery: ManuallyDrop::new(FileReqWalFlushRecovery { lock, }), },
};
let resp = FileResp::WalFlushRecovery;
Self { req: Some(req), resp: Some(resp) }
}
pub fn new_release(fh: ChannelGroup<FileContext<'a>>) -> (Self, oneshot::Receiver<FileRespRelease>) {
let (tx, rx) = oneshot::channel::<FileRespRelease>();
let req = FileReq {
op: FileReqOp::Release,
body: FileReqBody { release: ManuallyDrop::new(FileReqRelease { fh, }), },
};
let resp = FileResp::Release(tx);
(Self { req: Some(req), resp: Some(resp), }, rx)
}
pub fn new_last_cno() -> (Self, oneshot::Receiver<FileRespLastCno>) {
let (tx, rx) = oneshot::channel::<FileRespLastCno>();
let req = FileReq {
op: FileReqOp::LastCno,
body: FileReqBody { last_cno: ManuallyDrop::new(FileReqLastCno {}), },
};
let resp = FileResp::LastCno(tx);
(Self { req: Some(req), resp: Some(resp), }, rx)
}
pub fn reform_read(req: FileReqRead<'a>, resp: FileResp) -> Self {
let req = FileReq {
op: FileReqOp::Read,
body: FileReqBody { read: ManuallyDrop::new(req) },
};
Self { req: Some(req), resp: Some(resp) }
}
pub fn reform_write(req: FileReqWrite<'a>, resp: FileResp) -> Self {
let req = FileReq {
op: FileReqOp::Write,
body: FileReqBody { write: ManuallyDrop::new(req) },
};
Self { req: Some(req), resp: Some(resp) }
}
pub fn reform_write_zero(req: FileReqWriteZero<'a>, resp: FileResp) -> Self {
let req = FileReq {
op: FileReqOp::WriteZero,
body: FileReqBody { write_zero: ManuallyDrop::new(req) },
};
Self { req: Some(req), resp: Some(resp) }
}
pub fn reform_release(req: FileReqRelease<'a>, resp: FileResp) -> Self {
let req = FileReq {
op: FileReqOp::Release,
body: FileReqBody { release: ManuallyDrop::new(req) },
};
Self { req: Some(req), resp: Some(resp) }
}
pub fn reform_flush(req: FileReqFlush<'a>, resp: FileResp) -> Self {
let req = FileReq {
op: FileReqOp::Flush,
body: FileReqBody { flush: ManuallyDrop::new(req) },
};
Self { req: Some(req), resp: Some(resp) }
}
pub fn reform_flush_data(req: FileReqFlush<'a>, resp: FileResp) -> Self {
let req = FileReq {
op: FileReqOp::FlushData,
body: FileReqBody { flush: ManuallyDrop::new(req) },
};
Self { req: Some(req), resp: Some(resp) }
}
#[cfg(feature = "wal")]
pub fn reform_wal_flush(req: FileReqWalFlush<'a>, resp: FileResp) -> Self {
let req = FileReq {
op: FileReqOp::WalFlush,
body: FileReqBody { wal_flush: ManuallyDrop::new(req) },
};
Self { req: Some(req), resp: Some(resp) }
}
}
impl<'a: 'static> Task<FileContext<'a>> for Hyper<'a>
{
async fn handle(&mut self, ctx: FileContext<'a>) {
let (req, resp) = ctx.take();
match req.op {
FileReqOp::GetAttr => {
let md = unsafe { req.body.getattr };
let _ = ManuallyDrop::into_inner(md);
let stat = self.inner.stat();
let resp = resp.to_getattr();
let _ = resp.send(Ok(stat));
},
FileReqOp::SetAttr => {
let md = unsafe { req.body.setattr };
let req = ManuallyDrop::into_inner(md);
let stat = req.stat;
let res = self.inner.update_stat(&stat).await;
let _ = resp.to_setattr().send(res);
},
FileReqOp::Read => {
let md = unsafe { req.body.read };
let req = ManuallyDrop::into_inner(md);
#[cfg(feature = "range-lock")]
let range = req.offset as u64..(req.offset + req.buf.len()) as u64;
let _resp_read = resp.to_read();
let resp_read = _resp_read.clone();
let resp = FileResp::Read(_resp_read);
let res = self.inner.spawn_read(req, resp).await;
match res {
Ok(_) => {}
Err(ref e) => {
if e.kind() != ErrorKind::ResourceBusy {
#[cfg(feature = "range-lock")]
self.inner.range_lock.try_unlock(range);
let _ = resp_read.try_send(res);
}
},
}
},
FileReqOp::Write => {
let md = unsafe { req.body.write };
let req = ManuallyDrop::into_inner(md);
#[cfg(feature = "range-lock")]
let range = req.offset as u64..(req.offset + req.buf.len()) as u64;
let _resp_write = resp.to_write();
let resp_write = _resp_write.clone();
let resp = FileResp::Write(_resp_write);
let res = self.inner.spawn_write(req, resp).await;
match res {
Ok(bytes) => {
let _ = resp_write.try_send(Ok(bytes));
},
Err(ref e) => {
if e.kind() != ErrorKind::ResourceBusy {
#[cfg(feature = "range-lock")]
self.inner.range_lock.try_unlock(range);
let _ = resp_write.try_send(res);
}
},
}
},
#[cfg(feature = "wal")]
FileReqOp::WriteWal => {
let md = unsafe { req.body.write };
let req = ManuallyDrop::into_inner(md);
#[cfg(feature = "range-lock")]
let range = req.offset as u64..(req.offset + req.buf.len()) as u64;
let _resp_write = resp.to_write();
let resp_write = _resp_write.clone();
let resp = FileResp::Write(_resp_write);
let res = self.inner.spawn_write_wal(req, resp).await;
match res {
Ok(_) => {},
Err(ref e) => {
if e.kind() != ErrorKind::ResourceBusy {
#[cfg(feature = "range-lock")]
self.inner.range_lock.try_unlock(range);
let _ = resp_write.try_send(res);
}
},
}
},
FileReqOp::WriteAbsorb => {
let md = unsafe { req.body.write };
let mut req = ManuallyDrop::into_inner(md);
#[cfg(feature = "range-lock")]
let range = req.offset as u64..(req.offset + req.buf.len()) as u64;
let _resp_write = resp.to_write();
let resp_write = _resp_write.clone();
let resp = FileResp::Write(_resp_write);
let mut fetched = Vec::new();
fetched.append(&mut req.fetched);
let res = self.inner.absorb_write(req, resp, fetched).await;
match res {
Ok(bytes) => {
let _ = resp_write.try_send(Ok(bytes));
},
Err(ref e) => {
if e.kind() != ErrorKind::ResourceBusy {
#[cfg(feature = "range-lock")]
self.inner.range_lock.try_unlock(range);
let _ = resp_write.try_send(res);
}
},
}
},
FileReqOp::WriteAbsorbBh => {
let md = unsafe { req.body.write };
let req = ManuallyDrop::into_inner(md);
let _resp_write = resp.to_write();
let resp_write = _resp_write.clone();
let resp = FileResp::Write(_resp_write);
let res = self.inner.absorb_write_bh(req, resp).await;
let _ = resp_write.try_send(res);
},
FileReqOp::WriteZero => {
let md = unsafe { req.body.write_zero };
let req = ManuallyDrop::into_inner(md);
#[cfg(feature = "range-lock")]
let range = req.offset as u64..(req.offset + req.len) as u64;
let _resp_write = resp.to_write_zero();
let resp_write = _resp_write.clone();
let resp = FileResp::WriteZero(_resp_write);
let res = self.inner.spawn_write_zero(req, resp).await;
match res {
Ok(bytes) => {
let _ = resp_write.try_send(Ok(bytes));
},
Err(ref e) => {
if e.kind() != ErrorKind::ResourceBusy {
#[cfg(feature = "range-lock")]
self.inner.range_lock.try_unlock(range);
let _ = resp_write.try_send(res);
}
},
}
},
#[cfg(feature = "wal")]
FileReqOp::WriteZeroWal => {
let md = unsafe { req.body.write_zero };
let req = ManuallyDrop::into_inner(md);
#[cfg(feature = "range-lock")]
let range = req.offset as u64..(req.offset + req.len) as u64;
let _resp_write = resp.to_write_zero();
let resp_write = _resp_write.clone();
let resp = FileResp::WriteZero(_resp_write);
let res = self.inner.spawn_write_zero_wal(req, resp).await;
match res {
Ok(_) => {},
Err(ref e) => {
if e.kind() != ErrorKind::ResourceBusy {
#[cfg(feature = "range-lock")]
self.inner.range_lock.try_unlock(range);
let _ = resp_write.try_send(res);
}
},
}
},
FileReqOp::WriteZeroAbsorb => {
let md = unsafe { req.body.write_zero };
let mut req = ManuallyDrop::into_inner(md);
#[cfg(feature = "range-lock")]
let range = req.offset as u64..(req.offset + req.len) as u64;
let _resp_write = resp.to_write_zero();
let resp_write = _resp_write.clone();
let resp = FileResp::WriteZero(_resp_write);
let mut fetched = Vec::new();
fetched.append(&mut req.fetched);
let res = self.inner.absorb_write_zero(req, resp, fetched).await;
match res {
Ok(bytes) => {
let _ = resp_write.try_send(Ok(bytes));
},
Err(ref e) => {
if e.kind() != ErrorKind::ResourceBusy {
#[cfg(feature = "range-lock")]
self.inner.range_lock.try_unlock(range);
let _ = resp_write.try_send(res);
}
},
}
},
FileReqOp::WriteZeroAbsorbBh => {
let md = unsafe { req.body.write_zero };
let req = ManuallyDrop::into_inner(md);
let _resp_write = resp.to_write_zero();
let resp_write = _resp_write.clone();
let resp = FileResp::WriteZero(_resp_write);
let res = self.inner.absorb_write_zero_bh(req, resp).await;
let _ = resp_write.try_send(res);
},
FileReqOp::WriteAlignedBatch => {
let md = unsafe { req.body.write_aligned_batch };
let req = ManuallyDrop::into_inner(md);
let blocks = req.data_blocks;
let res = self.inner.write_aligned_batch(blocks).await;
let _ = resp.to_write().try_send(res);
},
FileReqOp::WriteBatch => {
let md = unsafe { req.body.write_batch };
let req = ManuallyDrop::into_inner(md);
let blocks = req.data_blocks;
let res = self.inner.write_batch(blocks).await;
let _ = resp.to_write().try_send(res);
},
FileReqOp::Trunc => {
let md = unsafe { req.body.trunc };
let req = ManuallyDrop::into_inner(md);
let offset = req.offset;
let res = self.inner.truncate(offset).await;
let _ = resp.to_trunc().send(res);
},
#[cfg(not(feature = "wal"))]
FileReqOp::Flush => {
let md = unsafe { req.body.flush };
let _req = ManuallyDrop::into_inner(md);
#[cfg(feature = "range-lock")]
if self.inner.range_lock.is_locked() {
let fh = _req.fh.clone();
let ctx = FileContext::reform_flush(_req, resp);
let _ = fh.send_cb(ctx);
return;
}
let res = self.inner.flush().await;
let _ = resp.to_flush().send(res);
},
#[cfg(not(feature = "wal"))]
FileReqOp::FlushData => {
let md = unsafe { req.body.flush };
let _req = ManuallyDrop::into_inner(md);
#[cfg(feature = "range-lock")]
if self.inner.range_lock.is_locked() {
let fh = _req.fh.clone();
let ctx = FileContext::reform_flush_data(_req, resp);
let _ = fh.send_cb(ctx);
return;
}
let res = self.inner.flush_data().await;
let _ = resp.to_flush().send(res);
},
#[cfg(feature = "wal")]
FileReqOp::Flush => {
let md = unsafe { req.body.flush };
let req = ManuallyDrop::into_inner(md);
#[cfg(feature = "range-lock")]
if self.inner.range_lock.is_locked() {
let fh = req.fh.clone();
let ctx = FileContext::reform_flush(req, resp);
let _ = fh.send_cb(ctx);
return;
}
let res = if self.inner.wal.is_none() {
let res = self.inner.flush().await;
if res.is_err() {
warn!("kick flush failed {:?}", res);
}
res
} else {
let res = self.inner.kick_wal_protected_flush_reactor(req.fh.clone()).await;
if res.is_err() {
debug!("kick wal flush failed {:?}, requeue this request", res);
let fh = req.fh.clone();
let ctx = FileContext::reform_flush(req, resp);
let _ = fh.send_cb(ctx);
return;
}
res
};
let _ = resp.to_flush().send(res);
},
#[cfg(feature = "wal")]
FileReqOp::FlushData => {
let md = unsafe { req.body.flush };
let req = ManuallyDrop::into_inner(md);
#[cfg(feature = "range-lock")]
if self.inner.range_lock.is_locked() {
let fh = req.fh.clone();
let ctx = FileContext::reform_flush_data(req, resp);
let _ = fh.send_cb(ctx);
return;
}
if self.inner.dirty_block_count() == 0 && !self.inner.is_bmap_dirty() {
let cno = self.inner.in_memory_last_ondisk_cno();
let _ = resp.to_flush().send(Ok(cno));
return;
}
let res = if self.inner.wal.is_none() {
self.inner.flush().await
} else {
let res = self.inner.kick_wal_protected_flush_reactor(req.fh.clone()).await;
if res.is_err() {
debug!("kick wal flush_data failed {:?}, requeue this request", res);
let fh = req.fh.clone();
let ctx = FileContext::reform_flush_data(req, resp);
let _ = fh.send_cb(ctx);
return;
}
res
};
let _ = resp.to_flush().send(res);
},
#[cfg(feature = "wal")]
FileReqOp::WalFlush => {
let md = unsafe { req.body.wal_flush };
let req = ManuallyDrop::into_inner(md);
#[cfg(feature = "range-lock")]
if self.inner.range_lock.is_locked() {
let fh = req.fh.clone();
let ctx = FileContext::reform_wal_flush(req, resp);
let _ = fh.send_cb(ctx);
return;
}
let res = self.inner.kick_wal_protected_flush_reactor(req.fh).await;
if res.is_err() {
debug!("kick wal flush failed {:?}, ignore this flush request", res);
}
let _ = resp.to_wal_flush();
},
#[cfg(feature = "wal")]
FileReqOp::WalFlushDone => {
let md = unsafe { req.body.wal_flush_done };
let req = ManuallyDrop::into_inner(md);
let lock = req.lock;
let segid = req.segid;
let od_state = req.od_state;
let bmap_cache_limit = req.bmap_cache_limit;
self.inner.wal_flush_done(lock, segid, od_state, bmap_cache_limit).await;
info!("wal flush done, segid: {}", segid);
let _ = resp.to_wal_flush_done();
},
#[cfg(feature = "wal")]
FileReqOp::WalFlushRecovery => {
let md = unsafe { req.body.wal_flush_recovery };
let req = ManuallyDrop::into_inner(md);
let lock = req.lock;
let res = self.inner.wal_flush_recovery(lock).await;
if res.is_err() {
panic!("wal flush recovery failed {:?}", res);
}
let _ = resp.to_wal_flush_recovery();
},
FileReqOp::Release => {
let md = unsafe { req.body.release };
let req = ManuallyDrop::into_inner(md);
let res = self.inner.release().await;
match res {
Ok(_) => {
let _ = resp.to_release().send(res);
},
Err(ref e) => {
if e.kind() != ErrorKind::ResourceBusy {
let _ = resp.to_release().send(res);
} else {
let fh = req.fh.clone();
let ctx = FileContext::reform_release(req, resp);
let _ = fh.send_highprio(ctx);
}
},
}
},
FileReqOp::LastCno => {
let md = unsafe { req.body.last_cno };
let _ = ManuallyDrop::into_inner(md);
let res = self.inner.last_cno();
let _ = resp.to_last_cno().send(res);
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem::ManuallyDrop;
use tokio::task::LocalSet;
use hyperfile_reactor::Reactor;
struct DummyTask;
impl Task<FileContext<'static>> for DummyTask {
async fn handle(&mut self, _ctx: FileContext<'static>) {}
}
async fn make_handler() -> ChannelGroup<FileContext<'static>> {
let reactor = Reactor::<FileContext<'static>, DummyTask>::new_current()
.expect("reactor");
let (builder, finish) = build_channel_group();
let handler = reactor.spawn_async(DummyTask, builder).await
.expect("spawn");
std::mem::forget(reactor);
finish(handler)
}
#[test]
fn getattr_construct_take_respond() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let (ctx, rx) = FileContext::new_getattr();
let (req, resp) = ctx.take();
assert!(matches!(req.op, FileReqOp::GetAttr));
let _body = ManuallyDrop::into_inner(unsafe { req.body.getattr });
let stat: libc::stat = unsafe { std::mem::zeroed() };
resp.to_getattr().send(Ok(stat)).unwrap();
assert!(rx.await.unwrap().is_ok());
});
}
#[test]
fn setattr_construct_take_respond() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let stat_in: libc::stat = unsafe { std::mem::zeroed() };
let (ctx, rx) = FileContext::new_setattr(stat_in);
let (req, resp) = ctx.take();
assert!(matches!(req.op, FileReqOp::SetAttr));
let body = ManuallyDrop::into_inner(unsafe { req.body.setattr });
assert_eq!(body.stat.st_size, 0);
let stat_out: libc::stat = unsafe { std::mem::zeroed() };
resp.to_setattr().send(Ok(stat_out)).unwrap();
assert!(rx.await.unwrap().is_ok());
});
}
#[test]
fn trunc_construct_take_respond() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let (ctx, rx) = FileContext::new_trunc(4096);
let (req, resp) = ctx.take();
assert!(matches!(req.op, FileReqOp::Trunc));
let body = ManuallyDrop::into_inner(unsafe { req.body.trunc });
assert_eq!(body.offset, 4096);
resp.to_trunc().send(Ok(())).unwrap();
assert!(rx.await.unwrap().is_ok());
});
}
#[test]
fn last_cno_construct_take_respond() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let (ctx, rx) = FileContext::new_last_cno();
let (req, resp) = ctx.take();
assert!(matches!(req.op, FileReqOp::LastCno));
let _body = ManuallyDrop::into_inner(unsafe { req.body.last_cno });
resp.to_last_cno().send(42).unwrap();
assert_eq!(rx.await.unwrap(), 42);
});
}
#[test]
fn read_construct_take_respond() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let fh = make_handler().await;
let mut buf = vec![0u8; 100];
let buf_ref = unsafe { std::slice::from_raw_parts_mut(buf.as_mut_ptr(), buf.len()) };
let (ctx, _tx, mut rx) = FileContext::new_read(buf_ref, 0, fh);
let (req, resp) = ctx.take();
assert!(matches!(req.op, FileReqOp::Read));
let body = ManuallyDrop::into_inner(unsafe { req.body.read });
assert_eq!(body.buf.len(), 100);
assert_eq!(body.offset, 0);
resp.to_read().send(Ok(100)).await.unwrap();
assert_eq!(rx.recv().await.unwrap().unwrap(), 100);
});
}
#[test]
fn write_construct_take_respond() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let fh = make_handler().await;
let data = [0xABu8; 200];
let data_ref = unsafe { std::slice::from_raw_parts(data.as_ptr(), data.len()) };
let (ctx, _tx, mut rx) = FileContext::new_write(data_ref, 512, fh);
let (req, resp) = ctx.take();
assert!(matches!(req.op, FileReqOp::Write));
let body = ManuallyDrop::into_inner(unsafe { req.body.write });
assert_eq!(body.buf.len(), 200);
assert_eq!(body.buf[0], 0xAB);
assert_eq!(body.offset, 512);
resp.to_write().send(Ok(200)).await.unwrap();
assert_eq!(rx.recv().await.unwrap().unwrap(), 200);
});
}
#[test]
fn write_zero_construct_take_respond() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let fh = make_handler().await;
let (ctx, _tx, mut rx) = FileContext::new_write_zero(1024, 4096, fh);
let (req, resp) = ctx.take();
assert!(matches!(req.op, FileReqOp::WriteZero));
let body = ManuallyDrop::into_inner(unsafe { req.body.write_zero });
assert_eq!(body.offset, 1024);
assert_eq!(body.len, 4096);
resp.to_write_zero().send(Ok(4096)).await.unwrap();
assert_eq!(rx.recv().await.unwrap().unwrap(), 4096);
});
}
#[test]
fn flush_construct_take_respond() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let fh = make_handler().await;
let (ctx, rx) = FileContext::new_flush(fh);
let (req, resp) = ctx.take();
assert!(matches!(req.op, FileReqOp::Flush));
let _body = ManuallyDrop::into_inner(unsafe { req.body.flush });
resp.to_flush().send(Ok(7)).unwrap();
assert_eq!(rx.await.unwrap().unwrap(), 7);
});
}
#[test]
fn release_construct_take_respond() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let fh = make_handler().await;
let (ctx, rx) = FileContext::new_release(fh);
let (req, resp) = ctx.take();
assert!(matches!(req.op, FileReqOp::Release));
let _body = ManuallyDrop::into_inner(unsafe { req.body.release });
resp.to_release().send(Ok(99)).unwrap();
assert_eq!(rx.await.unwrap().unwrap(), 99);
});
}
#[test]
fn write_aligned_batch_construct_take_respond() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let blocks = vec![
AlignedDataBlockWrapper::new(0, 4096, false),
AlignedDataBlockWrapper::new(1, 4096, true),
];
let (ctx, mut rx) = FileContext::new_write_aligned_batch(blocks);
let (req, resp) = ctx.take();
assert!(matches!(req.op, FileReqOp::WriteAlignedBatch));
let body = ManuallyDrop::into_inner(unsafe { req.body.write_aligned_batch });
assert_eq!(body.data_blocks.len(), 2);
assert!(!body.data_blocks[0].is_zero());
assert!(body.data_blocks[1].is_zero());
resp.to_write().send(Ok(8192)).await.unwrap();
assert_eq!(rx.recv().await.unwrap().unwrap(), 8192);
});
}
#[test]
fn write_batch_construct_take_respond() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let blocks = vec![BatchDataBlockWrapper::new(0, 4096, false)];
let (ctx, mut rx) = FileContext::new_write_batch(blocks);
let (req, resp) = ctx.take();
assert!(matches!(req.op, FileReqOp::WriteBatch));
let body = ManuallyDrop::into_inner(unsafe { req.body.write_batch });
assert_eq!(body.data_blocks.len(), 1);
resp.to_write().send(Ok(4096)).await.unwrap();
assert_eq!(rx.recv().await.unwrap().unwrap(), 4096);
});
}
#[test]
fn clone_write_resp_sends_on_cloned_channel() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let fh = make_handler().await;
static BUF: [u8; 10] = [0u8; 10];
let (ctx, _tx, mut rx) = FileContext::new_write(&BUF, 0, fh);
let (_req, resp) = ctx.take();
let cloned = resp.clone_write_resp();
cloned.send(Ok(10)).await.unwrap();
assert_eq!(rx.recv().await.unwrap().unwrap(), 10);
});
}
#[test]
fn clone_write_zero_resp_sends_on_cloned_channel() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let fh = make_handler().await;
let (ctx, _tx, mut rx) = FileContext::new_write_zero(0, 100, fh);
let (_req, resp) = ctx.take();
let cloned = resp.clone_write_zero_resp();
cloned.send(Ok(100)).await.unwrap();
assert_eq!(rx.recv().await.unwrap().unwrap(), 100);
});
}
#[test]
fn reform_read_preserves_data() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let fh = make_handler().await;
let mut buf = vec![0u8; 50];
let buf_ref = unsafe { std::slice::from_raw_parts_mut(buf.as_mut_ptr(), buf.len()) };
let (ctx, _tx, mut rx) = FileContext::new_read(buf_ref, 10, fh);
let (req, resp) = ctx.take();
let body = ManuallyDrop::into_inner(unsafe { req.body.read });
let ctx2 = FileContext::reform_read(body, resp);
let (req2, resp2) = ctx2.take();
assert!(matches!(req2.op, FileReqOp::Read));
let body2 = ManuallyDrop::into_inner(unsafe { req2.body.read });
assert_eq!(body2.offset, 10);
assert_eq!(body2.buf.len(), 50);
resp2.to_read().send(Ok(50)).await.unwrap();
assert_eq!(rx.recv().await.unwrap().unwrap(), 50);
});
}
#[test]
fn reform_write_preserves_data() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let fh = make_handler().await;
let data = [1u8; 30];
let data_ref = unsafe { std::slice::from_raw_parts(data.as_ptr(), data.len()) };
let (ctx, _tx, mut rx) = FileContext::new_write(data_ref, 20, fh);
let (req, resp) = ctx.take();
let body = ManuallyDrop::into_inner(unsafe { req.body.write });
let ctx2 = FileContext::reform_write(body, resp);
let (req2, resp2) = ctx2.take();
assert!(matches!(req2.op, FileReqOp::Write));
let body2 = ManuallyDrop::into_inner(unsafe { req2.body.write });
assert_eq!(body2.offset, 20);
assert_eq!(body2.buf[0], 1);
resp2.to_write().send(Ok(30)).await.unwrap();
assert_eq!(rx.recv().await.unwrap().unwrap(), 30);
});
}
#[test]
fn reform_write_zero_preserves_data() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let fh = make_handler().await;
let (ctx, _tx, mut rx) = FileContext::new_write_zero(100, 200, fh);
let (req, resp) = ctx.take();
let body = ManuallyDrop::into_inner(unsafe { req.body.write_zero });
let ctx2 = FileContext::reform_write_zero(body, resp);
let (req2, resp2) = ctx2.take();
assert!(matches!(req2.op, FileReqOp::WriteZero));
let body2 = ManuallyDrop::into_inner(unsafe { req2.body.write_zero });
assert_eq!(body2.offset, 100);
assert_eq!(body2.len, 200);
resp2.to_write_zero().send(Ok(200)).await.unwrap();
assert_eq!(rx.recv().await.unwrap().unwrap(), 200);
});
}
#[test]
fn reform_flush_preserves_channel() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let fh = make_handler().await;
let (ctx, rx) = FileContext::new_flush(fh);
let (req, resp) = ctx.take();
let body = ManuallyDrop::into_inner(unsafe { req.body.flush });
let ctx2 = FileContext::reform_flush(body, resp);
let (req2, resp2) = ctx2.take();
assert!(matches!(req2.op, FileReqOp::Flush));
let _body2 = ManuallyDrop::into_inner(unsafe { req2.body.flush });
resp2.to_flush().send(Ok(5)).unwrap();
assert_eq!(rx.await.unwrap().unwrap(), 5);
});
}
#[test]
fn reform_release_preserves_channel() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let fh = make_handler().await;
let (ctx, rx) = FileContext::new_release(fh);
let (req, resp) = ctx.take();
let body = ManuallyDrop::into_inner(unsafe { req.body.release });
let ctx2 = FileContext::reform_release(body, resp);
let (req2, resp2) = ctx2.take();
assert!(matches!(req2.op, FileReqOp::Release));
let _body2 = ManuallyDrop::into_inner(unsafe { req2.body.release });
resp2.to_release().send(Ok(11)).unwrap();
assert_eq!(rx.await.unwrap().unwrap(), 11);
});
}
#[test]
fn write_absorb_sets_correct_op() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let fh = make_handler().await;
static BUF: [u8; 10] = [0u8; 10];
let (ctx, _tx, mut rx) = FileContext::new_write(&BUF, 0, fh);
let (req, resp) = ctx.take();
let body = ManuallyDrop::into_inner(unsafe { req.body.write });
let ctx2 = FileContext::write_absorb(body, resp);
let (req2, resp2) = ctx2.take();
assert!(matches!(req2.op, FileReqOp::WriteAbsorb));
let _body2 = ManuallyDrop::into_inner(unsafe { req2.body.write });
resp2.to_write().send(Ok(10)).await.unwrap();
assert_eq!(rx.recv().await.unwrap().unwrap(), 10);
});
}
#[test]
fn write_absorb_bh_sets_correct_op() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let fh = make_handler().await;
static BUF: [u8; 10] = [0u8; 10];
let (ctx, _tx, _rx) = FileContext::new_write(&BUF, 0, fh);
let (req, resp) = ctx.take();
let body = ManuallyDrop::into_inner(unsafe { req.body.write });
let ctx2 = FileContext::write_absorb_bh(body, resp);
let (req2, _resp2) = ctx2.take();
assert!(matches!(req2.op, FileReqOp::WriteAbsorbBh));
});
}
#[test]
fn write_zero_absorb_sets_correct_op() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let fh = make_handler().await;
let (ctx, _tx, mut rx) = FileContext::new_write_zero(0, 100, fh);
let (req, resp) = ctx.take();
let body = ManuallyDrop::into_inner(unsafe { req.body.write_zero });
let ctx2 = FileContext::write_zero_absorb(body, resp);
let (req2, resp2) = ctx2.take();
assert!(matches!(req2.op, FileReqOp::WriteZeroAbsorb));
let _body2 = ManuallyDrop::into_inner(unsafe { req2.body.write_zero });
resp2.to_write_zero().send(Ok(100)).await.unwrap();
assert_eq!(rx.recv().await.unwrap().unwrap(), 100);
});
}
#[test]
fn write_zero_absorb_bh_sets_correct_op() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let fh = make_handler().await;
let (ctx, _tx, _rx) = FileContext::new_write_zero(0, 100, fh);
let (req, resp) = ctx.take();
let body = ManuallyDrop::into_inner(unsafe { req.body.write_zero });
let ctx2 = FileContext::write_zero_absorb_bh(body, resp);
let (req2, _resp2) = ctx2.take();
assert!(matches!(req2.op, FileReqOp::WriteZeroAbsorbBh));
});
}
#[test]
fn getattr_error_response() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let (ctx, rx) = FileContext::new_getattr();
let (_req, resp) = ctx.take();
resp.to_getattr().send(Err(std::io::Error::new(ErrorKind::NotFound, "not found"))).unwrap();
let result = rx.await.unwrap();
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound);
});
}
#[test]
fn write_error_response() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let fh = make_handler().await;
static BUF: [u8; 1] = [0u8; 1];
let (ctx, _tx, mut rx) = FileContext::new_write(&BUF, 0, fh);
let (_req, resp) = ctx.take();
resp.to_write().send(Err(std::io::Error::new(ErrorKind::Other, "fail"))).await.unwrap();
let result = rx.recv().await.unwrap();
assert!(result.is_err());
});
}
}