mirror of
https://github.com/ntex-rs/ntex.git
synced 2025-03-31 11:27:38 +03:00
Add io-uring driver (#515)
This commit is contained in:
parent
47afec7351
commit
60a686b2f6
38 changed files with 1700 additions and 277 deletions
3
.github/workflows/cov.yml
vendored
3
.github/workflows/cov.yml
vendored
|
@ -35,6 +35,9 @@ jobs:
|
|||
- name: Code coverage (neon)
|
||||
run: cargo llvm-cov --no-report --all --no-default-features --features="ntex/neon,ntex/cookie,ntex/url,ntex/compress,ntex/openssl,ntex/rustls,ntex/ws,ntex/brotli"
|
||||
|
||||
- name: Code coverage (neon-uring)
|
||||
run: cargo llvm-cov --no-report --all --no-default-features --features="ntex/neon-uring,ntex/cookie,ntex/url,ntex/compress,ntex/openssl,ntex/rustls,ntex/ws,ntex/brotli"
|
||||
|
||||
- name: Generate coverage report
|
||||
run: cargo llvm-cov report --lcov --output-path lcov.info --ignore-filename-regex="ntex-compio|ntex-tokio"
|
||||
|
||||
|
|
5
.github/workflows/linux.yml
vendored
5
.github/workflows/linux.yml
vendored
|
@ -59,6 +59,11 @@ jobs:
|
|||
run: |
|
||||
cargo test --all --no-default-features --features="ntex/neon,ntex/cookie,ntex/url,ntex/compress,ntex/openssl,ntex/rustls,ntex/ws,ntex/brotli"
|
||||
|
||||
- name: Run tests (neon-uring)
|
||||
timeout-minutes: 40
|
||||
run: |
|
||||
cargo test --all --no-default-features --features="ntex/neon-uring,ntex/cookie,ntex/url,ntex/compress,ntex/openssl,ntex/rustls,ntex/ws,ntex/brotli"
|
||||
|
||||
- name: Install cargo-cache
|
||||
continue-on-error: true
|
||||
run: |
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
## Usage
|
||||
|
||||
ntex supports multiple async runtimes, runtime must be selected as a feature. Available options are `compio`, `tokio`,
|
||||
`glommio` or `async-std`.
|
||||
`neon` or `neon-uring`.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
|
|
|
@ -60,6 +60,10 @@ impl Flags {
|
|||
self.contains(Flags::BUF_R_READY)
|
||||
}
|
||||
|
||||
pub(crate) fn is_waiting_for_read(&self) -> bool {
|
||||
self.contains(Flags::RD_NOTIFY)
|
||||
}
|
||||
|
||||
pub(crate) fn cannot_read(self) -> bool {
|
||||
self.intersects(Flags::RD_PAUSED | Flags::BUF_R_FULL)
|
||||
}
|
||||
|
|
|
@ -437,7 +437,7 @@ impl<F> Io<F> {
|
|||
} else {
|
||||
st.dispatch_task.register(cx.waker());
|
||||
|
||||
let ready = flags.contains(Flags::BUF_R_READY);
|
||||
let ready = flags.is_read_buf_ready();
|
||||
if flags.cannot_read() {
|
||||
flags.cleanup_read_flags();
|
||||
st.read_task.wake();
|
||||
|
@ -558,24 +558,28 @@ impl<F> Io<F> {
|
|||
let st = self.st();
|
||||
let flags = self.flags();
|
||||
|
||||
if flags.is_stopped() {
|
||||
Poll::Ready(Err(st.error_or_disconnected()))
|
||||
} else {
|
||||
let len = st.buffer.write_destination_size();
|
||||
if len > 0 {
|
||||
if full {
|
||||
st.insert_flags(Flags::BUF_W_MUST_FLUSH);
|
||||
st.dispatch_task.register(cx.waker());
|
||||
return Poll::Pending;
|
||||
} else if len >= st.pool.get().write_params_high() << 1 {
|
||||
st.insert_flags(Flags::BUF_W_BACKPRESSURE);
|
||||
st.dispatch_task.register(cx.waker());
|
||||
return Poll::Pending;
|
||||
}
|
||||
let len = st.buffer.write_destination_size();
|
||||
if len > 0 {
|
||||
if full {
|
||||
st.insert_flags(Flags::BUF_W_MUST_FLUSH);
|
||||
st.dispatch_task.register(cx.waker());
|
||||
return if flags.is_stopped() {
|
||||
Poll::Ready(Err(st.error_or_disconnected()))
|
||||
} else {
|
||||
Poll::Pending
|
||||
};
|
||||
} else if len >= st.pool.get().write_params_high() << 1 {
|
||||
st.insert_flags(Flags::BUF_W_BACKPRESSURE);
|
||||
st.dispatch_task.register(cx.waker());
|
||||
return if flags.is_stopped() {
|
||||
Poll::Ready(Err(st.error_or_disconnected()))
|
||||
} else {
|
||||
Poll::Pending
|
||||
};
|
||||
}
|
||||
st.remove_flags(Flags::BUF_W_MUST_FLUSH | Flags::BUF_W_BACKPRESSURE);
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
st.remove_flags(Flags::BUF_W_MUST_FLUSH | Flags::BUF_W_BACKPRESSURE);
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -128,7 +128,7 @@ impl ReadContext {
|
|||
);
|
||||
// dest buffer has new data, wake up dispatcher
|
||||
inner.dispatch_task.wake();
|
||||
} else if inner.flags.get().contains(Flags::RD_NOTIFY) {
|
||||
} else if inner.flags.get().is_waiting_for_read() {
|
||||
// in case of "notify" we must wake up dispatch task
|
||||
// if we read any data from source
|
||||
inner.dispatch_task.wake();
|
||||
|
@ -447,6 +447,280 @@ impl IoContext {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get read buffer
|
||||
pub fn get_read_buf(&self) -> Poll<BytesVec> {
|
||||
let inner = &self.0 .0;
|
||||
|
||||
if let Some(waker) = inner.read_task.take() {
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
|
||||
if let Poll::Ready(ReadStatus::Ready) = self.0.filter().poll_read_ready(&mut cx)
|
||||
{
|
||||
let mut buf = if inner.flags.get().is_read_buf_ready() {
|
||||
// read buffer is still not read by dispatcher
|
||||
// we cannot touch it
|
||||
inner.pool.get().get_read_buf()
|
||||
} else {
|
||||
inner
|
||||
.buffer
|
||||
.get_read_source()
|
||||
.unwrap_or_else(|| inner.pool.get().get_read_buf())
|
||||
};
|
||||
|
||||
// make sure we've got room
|
||||
let (hw, lw) = self.0.memory_pool().read_params().unpack();
|
||||
let remaining = buf.remaining_mut();
|
||||
if remaining < lw {
|
||||
buf.reserve(hw - remaining);
|
||||
}
|
||||
return Poll::Ready(buf);
|
||||
}
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
|
||||
pub fn release_read_buf(&self, buf: BytesVec) {
|
||||
let inner = &self.0 .0;
|
||||
if let Some(mut first_buf) = inner.buffer.get_read_source() {
|
||||
first_buf.extend_from_slice(&buf);
|
||||
inner.buffer.set_read_source(&self.0, first_buf);
|
||||
} else {
|
||||
inner.buffer.set_read_source(&self.0, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set read buffer
|
||||
pub fn set_read_buf(&self, result: io::Result<usize>, buf: BytesVec) -> Poll<()> {
|
||||
let inner = &self.0 .0;
|
||||
let (hw, _) = self.0.memory_pool().read_params().unpack();
|
||||
|
||||
if let Some(mut first_buf) = inner.buffer.get_read_source() {
|
||||
first_buf.extend_from_slice(&buf);
|
||||
inner.buffer.set_read_source(&self.0, first_buf);
|
||||
} else {
|
||||
inner.buffer.set_read_source(&self.0, buf);
|
||||
}
|
||||
|
||||
match result {
|
||||
Ok(0) => {
|
||||
inner.io_stopped(None);
|
||||
Poll::Ready(())
|
||||
}
|
||||
Ok(nbytes) => {
|
||||
let filter = self.0.filter();
|
||||
let res = filter
|
||||
.process_read_buf(&self.0, &inner.buffer, 0, nbytes)
|
||||
.and_then(|status| {
|
||||
if status.nbytes > 0 {
|
||||
// dest buffer has new data, wake up dispatcher
|
||||
if inner.buffer.read_destination_size() >= hw {
|
||||
log::trace!(
|
||||
"{}: Io read buffer is too large {}, enable read back-pressure",
|
||||
self.0.tag(),
|
||||
nbytes
|
||||
);
|
||||
inner.insert_flags(Flags::BUF_R_READY | Flags::BUF_R_FULL);
|
||||
} else {
|
||||
inner.insert_flags(Flags::BUF_R_READY);
|
||||
|
||||
if nbytes >= hw {
|
||||
// read task is paused because of read back-pressure
|
||||
// but there is no new data in top most read buffer
|
||||
// so we need to wake up read task to read more data
|
||||
// otherwise read task would sleep forever
|
||||
inner.read_task.wake();
|
||||
}
|
||||
}
|
||||
log::trace!(
|
||||
"{}: New {} bytes available, wakeup dispatcher",
|
||||
self.0.tag(),
|
||||
nbytes
|
||||
);
|
||||
inner.dispatch_task.wake();
|
||||
} else {
|
||||
if nbytes >= hw {
|
||||
// read task is paused because of read back-pressure
|
||||
// but there is no new data in top most read buffer
|
||||
// so we need to wake up read task to read more data
|
||||
// otherwise read task would sleep forever
|
||||
inner.read_task.wake();
|
||||
}
|
||||
if inner.flags.get().is_waiting_for_read() {
|
||||
// in case of "notify" we must wake up dispatch task
|
||||
// if we read any data from source
|
||||
inner.dispatch_task.wake();
|
||||
}
|
||||
}
|
||||
|
||||
// while reading, filter wrote some data
|
||||
// in that case filters need to process write buffers
|
||||
// and potentialy wake write task
|
||||
if status.need_write {
|
||||
inner.write_task.wake();
|
||||
filter.process_write_buf(&self.0, &inner.buffer, 0)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
});
|
||||
|
||||
if let Err(err) = res {
|
||||
inner.io_stopped(Some(err));
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
self.shutdown_filters();
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
inner.io_stopped(Some(e));
|
||||
Poll::Ready(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get write buffer
|
||||
pub fn get_write_buf(&self) -> Poll<BytesVec> {
|
||||
let inner = &self.0 .0;
|
||||
|
||||
// check write readiness
|
||||
if let Some(waker) = inner.write_task.take() {
|
||||
let ready = self
|
||||
.0
|
||||
.filter()
|
||||
.poll_write_ready(&mut Context::from_waker(&waker));
|
||||
let buf = if matches!(
|
||||
ready,
|
||||
Poll::Ready(WriteStatus::Ready | WriteStatus::Shutdown)
|
||||
) {
|
||||
inner.buffer.get_write_destination().and_then(|buf| {
|
||||
if buf.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(buf)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(buf) = buf {
|
||||
return Poll::Ready(buf);
|
||||
}
|
||||
}
|
||||
Poll::Pending
|
||||
}
|
||||
|
||||
pub fn release_write_buf(&self, mut buf: BytesVec) {
|
||||
let inner = &self.0 .0;
|
||||
|
||||
if let Some(b) = inner.buffer.get_write_destination() {
|
||||
buf.extend_from_slice(&b);
|
||||
self.0.memory_pool().release_write_buf(b);
|
||||
}
|
||||
inner.buffer.set_write_destination(buf);
|
||||
|
||||
// if write buffer is smaller than high watermark value, turn off back-pressure
|
||||
let len = inner.buffer.write_destination_size();
|
||||
let mut flags = inner.flags.get();
|
||||
|
||||
if len == 0 {
|
||||
if flags.is_waiting_for_write() {
|
||||
flags.waiting_for_write_is_done();
|
||||
inner.dispatch_task.wake();
|
||||
}
|
||||
flags.insert(Flags::WR_PAUSED);
|
||||
inner.flags.set(flags);
|
||||
} else if flags.contains(Flags::BUF_W_BACKPRESSURE)
|
||||
&& len < inner.pool.get().write_params_high() << 1
|
||||
{
|
||||
flags.remove(Flags::BUF_W_BACKPRESSURE);
|
||||
inner.flags.set(flags);
|
||||
inner.dispatch_task.wake();
|
||||
}
|
||||
inner.flags.set(flags);
|
||||
}
|
||||
|
||||
/// Set write buffer
|
||||
pub fn set_write_buf(&self, result: io::Result<usize>, mut buf: BytesVec) -> Poll<()> {
|
||||
let result = match result {
|
||||
Ok(0) => {
|
||||
log::trace!("{}: Disconnected during flush", self.tag());
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::WriteZero,
|
||||
"failed to write frame to transport",
|
||||
))
|
||||
}
|
||||
Ok(n) => {
|
||||
if n == buf.len() {
|
||||
buf.clear();
|
||||
Ok(0)
|
||||
} else {
|
||||
buf.advance(n);
|
||||
Ok(buf.len())
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
|
||||
let inner = &self.0 .0;
|
||||
|
||||
// set buffer back
|
||||
let result = match result {
|
||||
Ok(0) => {
|
||||
// log::debug!("{}: WROTE ALL {:?}", self.0.tag(), inner.buffer.write_destination_size());
|
||||
self.0.memory_pool().release_write_buf(buf);
|
||||
Ok(inner.buffer.write_destination_size())
|
||||
}
|
||||
Ok(_) => {
|
||||
if let Some(b) = inner.buffer.get_write_destination() {
|
||||
buf.extend_from_slice(&b);
|
||||
self.0.memory_pool().release_write_buf(b);
|
||||
}
|
||||
let l = buf.len();
|
||||
// log::debug!("{}: WROTE SOME {:?}", self.0.tag(), l);
|
||||
inner.buffer.set_write_destination(buf);
|
||||
Ok(l)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
|
||||
let mut flags = inner.flags.get();
|
||||
match result {
|
||||
Ok(0) => {
|
||||
// all data has been written
|
||||
flags.insert(Flags::WR_PAUSED);
|
||||
|
||||
if flags.is_task_waiting_for_write() {
|
||||
flags.task_waiting_for_write_is_done();
|
||||
inner.write_task.wake();
|
||||
}
|
||||
|
||||
if flags.is_waiting_for_write() {
|
||||
flags.waiting_for_write_is_done();
|
||||
inner.dispatch_task.wake();
|
||||
}
|
||||
inner.flags.set(flags);
|
||||
Poll::Ready(())
|
||||
}
|
||||
Ok(len) => {
|
||||
// if write buffer is smaller than high watermark value, turn off back-pressure
|
||||
if flags.contains(Flags::BUF_W_BACKPRESSURE)
|
||||
&& len < inner.pool.get().write_params_high() << 1
|
||||
{
|
||||
flags.remove(Flags::BUF_W_BACKPRESSURE);
|
||||
inner.flags.set(flags);
|
||||
inner.dispatch_task.wake();
|
||||
}
|
||||
Poll::Pending
|
||||
}
|
||||
Err(e) => {
|
||||
inner.io_stopped(Some(e));
|
||||
Poll::Ready(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get read buffer
|
||||
pub fn with_read_buf<F>(&self, f: F) -> Poll<()>
|
||||
where
|
||||
|
@ -509,7 +783,7 @@ impl IoContext {
|
|||
// otherwise read task would sleep forever
|
||||
inner.read_task.wake();
|
||||
}
|
||||
if inner.flags.get().contains(Flags::RD_NOTIFY) {
|
||||
if inner.flags.get().is_waiting_for_read() {
|
||||
// in case of "notify" we must wake up dispatch task
|
||||
// if we read any data from source
|
||||
inner.dispatch_task.wake();
|
||||
|
|
|
@ -54,13 +54,6 @@ impl DriverType {
|
|||
OpenAt::CODE,
|
||||
Close::CODE,
|
||||
Shutdown::CODE,
|
||||
// Linux kernel 5.19
|
||||
#[cfg(any(
|
||||
feature = "io-uring-sqe128",
|
||||
feature = "io-uring-cqe32",
|
||||
feature = "io-uring-socket"
|
||||
))]
|
||||
Socket::CODE,
|
||||
];
|
||||
|
||||
(|| {
|
||||
|
|
|
@ -160,14 +160,6 @@ impl<T: ?Sized> Key<T> {
|
|||
this.cancelled
|
||||
}
|
||||
|
||||
pub(crate) fn set_flags(&mut self, flags: u32) {
|
||||
self.as_opaque_mut().flags = flags;
|
||||
}
|
||||
|
||||
pub(crate) fn flags(&self) -> u32 {
|
||||
self.as_opaque().flags
|
||||
}
|
||||
|
||||
/// Whether the op is completed.
|
||||
pub(crate) fn has_result(&self) -> bool {
|
||||
self.as_opaque().result.is_ready()
|
||||
|
|
|
@ -37,33 +37,15 @@ pub use asyncify::*;
|
|||
mod driver_type;
|
||||
pub use driver_type::*;
|
||||
|
||||
thread_local! {
|
||||
static LOGGING: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
|
||||
}
|
||||
|
||||
/// enable logging for thread
|
||||
pub fn enable_logging() {
|
||||
LOGGING.with(|v| v.set(true));
|
||||
}
|
||||
|
||||
/// enable logging for thread
|
||||
pub fn log<T: AsRef<str>>(s: T) {
|
||||
LOGGING.with(|_v| {
|
||||
//if _v.get() {
|
||||
println!("{}", s.as_ref());
|
||||
//}
|
||||
});
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
//if #[cfg(windows)] {
|
||||
// #[path = "iocp/mod.rs"]
|
||||
// mod sys;
|
||||
//} else if #[cfg(all(target_os = "linux", feature = "io-uring"))] {
|
||||
// #[path = "iour/mod.rs"]
|
||||
// mod sys;
|
||||
//} else
|
||||
if #[cfg(unix)] {
|
||||
if #[cfg(all(target_os = "linux", feature = "io-uring"))] {
|
||||
#[path = "uring/mod.rs"]
|
||||
mod sys;
|
||||
} else if #[cfg(unix)] {
|
||||
#[path = "poll/mod.rs"]
|
||||
mod sys;
|
||||
}
|
||||
|
@ -248,17 +230,6 @@ impl Proactor {
|
|||
})
|
||||
}
|
||||
|
||||
/// Attach an fd to the driver.
|
||||
///
|
||||
/// ## Platform specific
|
||||
/// * IOCP: it will be attached to the completion port. An fd could only be
|
||||
/// attached to one driver, and could only be attached once, even if you
|
||||
/// `try_clone` it.
|
||||
/// * io-uring & polling: it will do nothing but return `Ok(())`.
|
||||
pub fn attach(&self, fd: RawFd) -> io::Result<()> {
|
||||
self.driver.attach(fd)
|
||||
}
|
||||
|
||||
/// Cancel an operation with the pushed user-defined data.
|
||||
///
|
||||
/// The cancellation is not reliable. The underlying operation may continue,
|
||||
|
@ -305,11 +276,10 @@ impl Proactor {
|
|||
/// # Panics
|
||||
/// This function will panic if the requested operation has not been
|
||||
/// completed.
|
||||
pub fn pop<T>(&self, op: Key<T>) -> PushEntry<Key<T>, ((io::Result<usize>, T), u32)> {
|
||||
pub fn pop<T>(&self, op: Key<T>) -> PushEntry<Key<T>, (io::Result<usize>, T)> {
|
||||
if op.has_result() {
|
||||
let flags = op.flags();
|
||||
// SAFETY: completed.
|
||||
PushEntry::Ready((unsafe { op.into_inner() }, flags))
|
||||
PushEntry::Ready(unsafe { op.into_inner() })
|
||||
} else {
|
||||
PushEntry::Pending(op)
|
||||
}
|
||||
|
@ -327,7 +297,7 @@ impl Proactor {
|
|||
|
||||
pub fn register_handler<F>(&self, f: F)
|
||||
where
|
||||
F: FnOnce(DriverApi) -> Box<dyn op::Handler>,
|
||||
F: FnOnce(DriverApi) -> Box<dyn sys::op::Handler>,
|
||||
{
|
||||
self.driver.register_handler(f)
|
||||
}
|
||||
|
@ -346,23 +316,12 @@ impl AsRawFd for Proactor {
|
|||
pub(crate) struct Entry {
|
||||
user_data: usize,
|
||||
result: io::Result<usize>,
|
||||
flags: u32,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl Entry {
|
||||
pub(crate) fn new(user_data: usize, result: io::Result<usize>) -> Self {
|
||||
Self {
|
||||
user_data,
|
||||
result,
|
||||
flags: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||
// this method only used by in io-uring driver
|
||||
pub(crate) fn set_flags(&mut self, flags: u32) {
|
||||
self.flags = flags;
|
||||
Self { user_data, result }
|
||||
}
|
||||
|
||||
/// The user-defined data returned by [`Proactor::push`].
|
||||
|
@ -370,10 +329,6 @@ impl Entry {
|
|||
self.user_data
|
||||
}
|
||||
|
||||
pub fn flags(&self) -> u32 {
|
||||
self.flags
|
||||
}
|
||||
|
||||
/// The result of the operation.
|
||||
pub fn into_result(self) -> io::Result<usize> {
|
||||
self.result
|
||||
|
@ -383,7 +338,6 @@ impl Entry {
|
|||
pub unsafe fn notify(self) {
|
||||
let user_data = self.user_data();
|
||||
let mut op = Key::<()>::new_unchecked(user_data);
|
||||
op.set_flags(self.flags());
|
||||
if op.set_result(self.into_result()) {
|
||||
// SAFETY: completed and cancelled.
|
||||
let _ = op.into_box();
|
||||
|
@ -428,7 +382,6 @@ impl ThreadPoolBuilder {
|
|||
pub struct ProactorBuilder {
|
||||
capacity: u32,
|
||||
pool_builder: ThreadPoolBuilder,
|
||||
sqpoll_idle: Option<Duration>,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
|
@ -445,7 +398,6 @@ impl ProactorBuilder {
|
|||
Self {
|
||||
capacity: 1024,
|
||||
pool_builder: ThreadPoolBuilder::new(),
|
||||
sqpoll_idle: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -496,19 +448,6 @@ impl ProactorBuilder {
|
|||
self.pool_builder.create_or_reuse()
|
||||
}
|
||||
|
||||
/// Set `io-uring` sqpoll idle milliseconds, when `sqpoll_idle` is set,
|
||||
/// io-uring sqpoll feature will be enabled
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// - Only effective when the `io-uring` feature is enabled
|
||||
/// - `idle` must >= 1ms, otherwise will set sqpoll idle 0ms
|
||||
/// - `idle` will be rounded down
|
||||
pub fn sqpoll_idle(&mut self, idle: Duration) -> &mut Self {
|
||||
self.sqpoll_idle = Some(idle);
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the [`Proactor`].
|
||||
pub fn build(&self) -> io::Result<Proactor> {
|
||||
Proactor::with_builder(self)
|
||||
|
|
|
@ -4,26 +4,10 @@
|
|||
//! The operation itself doesn't perform anything.
|
||||
//! You need to pass them to [`crate::Proactor`], and poll the driver.
|
||||
|
||||
use std::{io, marker::PhantomPinned, mem::ManuallyDrop, net::Shutdown};
|
||||
use std::{marker::PhantomPinned, net::Shutdown};
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use super::sys::op::{CreateSocket, Interest};
|
||||
|
||||
use super::OwnedFd;
|
||||
|
||||
pub trait Handler {
|
||||
/// Submitted interest
|
||||
fn readable(&mut self, id: usize);
|
||||
|
||||
/// Submitted interest
|
||||
fn writable(&mut self, id: usize);
|
||||
|
||||
/// Operation submission has failed
|
||||
fn error(&mut self, id: usize, err: io::Error);
|
||||
|
||||
/// All events are processed, process all updates
|
||||
fn commit(&mut self);
|
||||
}
|
||||
pub use super::sys::op::*;
|
||||
|
||||
/// Spawn a blocking function in the thread pool.
|
||||
pub struct Asyncify<F, D> {
|
||||
|
@ -59,17 +43,3 @@ impl<S> ShutdownSocket<S> {
|
|||
Self { fd, how }
|
||||
}
|
||||
}
|
||||
|
||||
/// Close socket fd.
|
||||
pub struct CloseSocket {
|
||||
pub(crate) fd: ManuallyDrop<OwnedFd>,
|
||||
}
|
||||
|
||||
impl CloseSocket {
|
||||
/// Create [`CloseSocket`].
|
||||
pub fn new(fd: OwnedFd) -> Self {
|
||||
Self {
|
||||
fd: ManuallyDrop::new(fd),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,7 @@ use crossbeam_queue::SegQueue;
|
|||
use nohash_hasher::IntMap;
|
||||
use polling::{Event, Events, Poller};
|
||||
|
||||
use crate::driver::{
|
||||
op::Handler, op::Interest, sys, AsyncifyPool, Entry, Key, ProactorBuilder,
|
||||
};
|
||||
use crate::driver::{op::Interest, sys, AsyncifyPool, Entry, Key, ProactorBuilder};
|
||||
|
||||
pub(crate) mod op;
|
||||
|
||||
|
@ -187,9 +185,10 @@ pub(crate) struct Driver {
|
|||
registry: RefCell<IntMap<RawFd, FdItem>>,
|
||||
pool: AsyncifyPool,
|
||||
pool_completed: Arc<SegQueue<Entry>>,
|
||||
|
||||
hid: Cell<usize>,
|
||||
changes: Rc<RefCell<Vec<Change>>>,
|
||||
handlers: Cell<Option<Box<Vec<Box<dyn Handler>>>>>,
|
||||
handlers: Cell<Option<Box<Vec<Box<dyn self::op::Handler>>>>>,
|
||||
}
|
||||
|
||||
impl Driver {
|
||||
|
@ -216,7 +215,7 @@ impl Driver {
|
|||
|
||||
pub fn register_handler<F>(&self, f: F)
|
||||
where
|
||||
F: FnOnce(DriverApi) -> Box<dyn Handler>,
|
||||
F: FnOnce(DriverApi) -> Box<dyn self::op::Handler>,
|
||||
{
|
||||
let id = self.hid.get();
|
||||
let mut handlers = self.handlers.take().unwrap_or_default();
|
||||
|
@ -234,10 +233,6 @@ impl Driver {
|
|||
Key::new(self.as_raw_fd(), op)
|
||||
}
|
||||
|
||||
pub fn attach(&self, _fd: RawFd) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn push(&self, op: &mut Key<dyn sys::OpCode>) -> Poll<io::Result<usize>> {
|
||||
let user_data = op.user_data();
|
||||
let op_pin = op.as_op_pin();
|
||||
|
|
|
@ -1,10 +1,24 @@
|
|||
use std::{io, marker::Send, os::fd::FromRawFd, os::fd::RawFd, pin::Pin, task::Poll};
|
||||
use std::{io, marker::Send, mem, os::fd::FromRawFd, os::fd::RawFd, pin::Pin, task::Poll};
|
||||
|
||||
pub use crate::driver::unix::op::*;
|
||||
|
||||
use super::{AsRawFd, Decision, OpCode};
|
||||
use super::{AsRawFd, Decision, OpCode, OwnedFd};
|
||||
use crate::{driver::op::*, syscall};
|
||||
|
||||
pub trait Handler {
|
||||
/// Submitted interest
|
||||
fn readable(&mut self, id: usize);
|
||||
|
||||
/// Submitted interest
|
||||
fn writable(&mut self, id: usize);
|
||||
|
||||
/// Operation submission has failed
|
||||
fn error(&mut self, id: usize, err: io::Error);
|
||||
|
||||
/// All events are processed, process all updates
|
||||
fn commit(&mut self);
|
||||
}
|
||||
|
||||
impl<D, F> OpCode for Asyncify<F, D>
|
||||
where
|
||||
D: Send + 'static,
|
||||
|
@ -27,6 +41,20 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Close socket fd.
|
||||
pub struct CloseSocket {
|
||||
pub(crate) fd: mem::ManuallyDrop<OwnedFd>,
|
||||
}
|
||||
|
||||
impl CloseSocket {
|
||||
/// Create [`CloseSocket`].
|
||||
pub fn new(fd: OwnedFd) -> Self {
|
||||
Self {
|
||||
fd: mem::ManuallyDrop::new(fd),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OpCode for CreateSocket {
|
||||
fn pre_submit(self: Pin<&mut Self>) -> io::Result<Decision> {
|
||||
Ok(Decision::Blocking)
|
||||
|
|
340
ntex-neon/src/driver/uring/mod.rs
Normal file
340
ntex-neon/src/driver/uring/mod.rs
Normal file
|
@ -0,0 +1,340 @@
|
|||
pub use std::os::fd::{AsRawFd, OwnedFd, RawFd};
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::{
|
||||
collections::VecDeque, io, pin::Pin, rc::Rc, sync::Arc, task::Poll, time::Duration,
|
||||
};
|
||||
|
||||
use crossbeam_queue::SegQueue;
|
||||
use io_uring::cqueue::{more, Entry as CEntry};
|
||||
use io_uring::opcode::{AsyncCancel, PollAdd};
|
||||
use io_uring::squeue::Entry as SEntry;
|
||||
use io_uring::types::{Fd, SubmitArgs, Timespec};
|
||||
use io_uring::IoUring;
|
||||
|
||||
mod notify;
|
||||
pub(crate) mod op;
|
||||
|
||||
pub use self::notify::NotifyHandle;
|
||||
|
||||
use self::notify::Notifier;
|
||||
use crate::driver::{sys, AsyncifyPool, Entry, Key, ProactorBuilder};
|
||||
|
||||
/// Abstraction of io-uring operations.
|
||||
pub trait OpCode {
|
||||
/// Name of the operation
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// Call the operation in a blocking way. This method will only be called if
|
||||
/// [`create_entry`] returns [`OpEntry::Blocking`].
|
||||
fn call_blocking(self: Pin<&mut Self>) -> io::Result<usize> {
|
||||
unreachable!("this operation is asynchronous")
|
||||
}
|
||||
|
||||
/// Set the result when it successfully completes.
|
||||
/// The operation stores the result and is responsible to release it if the
|
||||
/// operation is cancelled.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Users should not call it.
|
||||
unsafe fn set_result(self: Pin<&mut Self>, _: usize) {}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Change {
|
||||
Submit { entry: SEntry },
|
||||
Cancel { op_id: u64 },
|
||||
}
|
||||
|
||||
pub struct DriverApi {
|
||||
batch: u64,
|
||||
changes: Rc<RefCell<VecDeque<Change>>>,
|
||||
}
|
||||
|
||||
impl DriverApi {
|
||||
pub fn submit(&self, user_data: u32, entry: SEntry) {
|
||||
log::debug!(
|
||||
"Submit operation batch: {:?} user-data: {:?} entry: {:?}",
|
||||
self.batch >> Driver::BATCH,
|
||||
user_data,
|
||||
entry,
|
||||
);
|
||||
self.changes.borrow_mut().push_back(Change::Submit {
|
||||
entry: entry.user_data(user_data as u64 | self.batch),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn cancel(&self, op_id: u32) {
|
||||
log::debug!(
|
||||
"Cancel operation batch: {:?} user-data: {:?}",
|
||||
self.batch >> Driver::BATCH,
|
||||
op_id
|
||||
);
|
||||
self.changes.borrow_mut().push_back(Change::Cancel {
|
||||
op_id: op_id as u64 | self.batch,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Low-level driver of io-uring.
|
||||
pub(crate) struct Driver {
|
||||
ring: RefCell<IoUring<SEntry, CEntry>>,
|
||||
notifier: Notifier,
|
||||
pool: AsyncifyPool,
|
||||
pool_completed: Arc<SegQueue<Entry>>,
|
||||
|
||||
hid: Cell<u64>,
|
||||
changes: Rc<RefCell<VecDeque<Change>>>,
|
||||
handlers: Cell<Option<Box<Vec<Box<dyn op::Handler>>>>>,
|
||||
}
|
||||
|
||||
impl Driver {
|
||||
const NOTIFY: u64 = u64::MAX;
|
||||
const CANCEL: u64 = u64::MAX - 1;
|
||||
const BATCH: u64 = 48;
|
||||
const BATCH_MASK: u64 = 0xFFFF_0000_0000_0000;
|
||||
const DATA_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
|
||||
|
||||
pub fn new(builder: &ProactorBuilder) -> io::Result<Self> {
|
||||
log::trace!("New io-uring driver");
|
||||
|
||||
let mut ring = IoUring::builder()
|
||||
.setup_coop_taskrun()
|
||||
.setup_single_issuer()
|
||||
.build(builder.capacity)?;
|
||||
|
||||
let notifier = Notifier::new()?;
|
||||
|
||||
#[allow(clippy::useless_conversion)]
|
||||
unsafe {
|
||||
ring.submission()
|
||||
.push(
|
||||
&PollAdd::new(Fd(notifier.as_raw_fd()), libc::POLLIN as _)
|
||||
.multi(true)
|
||||
.build()
|
||||
.user_data(Self::NOTIFY)
|
||||
.into(),
|
||||
)
|
||||
.expect("the squeue sould not be full");
|
||||
}
|
||||
Ok(Self {
|
||||
notifier,
|
||||
ring: RefCell::new(ring),
|
||||
pool: builder.create_or_get_thread_pool(),
|
||||
pool_completed: Arc::new(SegQueue::new()),
|
||||
|
||||
hid: Cell::new(0),
|
||||
changes: Rc::new(RefCell::new(VecDeque::new())),
|
||||
handlers: Cell::new(Some(Box::new(Vec::new()))),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn register_handler<F>(&self, f: F)
|
||||
where
|
||||
F: FnOnce(DriverApi) -> Box<dyn self::op::Handler>,
|
||||
{
|
||||
let id = self.hid.get();
|
||||
let mut handlers = self.handlers.take().unwrap_or_default();
|
||||
|
||||
let api = DriverApi {
|
||||
batch: id << 48,
|
||||
changes: self.changes.clone(),
|
||||
};
|
||||
handlers.push(f(api));
|
||||
self.hid.set(id + 1);
|
||||
self.handlers.set(Some(handlers));
|
||||
}
|
||||
|
||||
// Auto means that it choose to wait or not automatically.
|
||||
fn submit_auto(&self, timeout: Option<Duration>) -> io::Result<()> {
|
||||
let mut ring = self.ring.borrow_mut();
|
||||
let res = {
|
||||
// Last part of submission queue, wait till timeout.
|
||||
if let Some(duration) = timeout {
|
||||
let timespec = timespec(duration);
|
||||
let args = SubmitArgs::new().timespec(×pec);
|
||||
ring.submitter().submit_with_args(1, &args)
|
||||
} else {
|
||||
ring.submit_and_wait(1)
|
||||
}
|
||||
};
|
||||
match res {
|
||||
Ok(_) => {
|
||||
// log::debug!("Submit result: {res:?} {:?}", timeout);
|
||||
if ring.completion().is_empty() {
|
||||
Err(io::ErrorKind::TimedOut.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Err(e) => match e.raw_os_error() {
|
||||
Some(libc::ETIME) => {
|
||||
if timeout.is_some() && timeout != Some(Duration::ZERO) {
|
||||
Err(io::ErrorKind::TimedOut.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Some(libc::EBUSY) | Some(libc::EAGAIN) => {
|
||||
Err(io::ErrorKind::Interrupted.into())
|
||||
}
|
||||
_ => Err(e),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_op<T: sys::OpCode + 'static>(&self, op: T) -> Key<T> {
|
||||
Key::new(self.as_raw_fd(), op)
|
||||
}
|
||||
|
||||
fn apply_changes(&self) -> bool {
|
||||
let mut changes = self.changes.borrow_mut();
|
||||
if changes.is_empty() {
|
||||
return false;
|
||||
}
|
||||
log::debug!("Apply changes, {:?}", changes.len());
|
||||
|
||||
let mut ring = self.ring.borrow_mut();
|
||||
let mut squeue = ring.submission();
|
||||
|
||||
while let Some(change) = changes.pop_front() {
|
||||
match change {
|
||||
Change::Submit { entry } => {
|
||||
if unsafe { squeue.push(&entry) }.is_err() {
|
||||
changes.push_front(Change::Submit { entry });
|
||||
break;
|
||||
}
|
||||
}
|
||||
Change::Cancel { op_id } => {
|
||||
let entry = AsyncCancel::new(op_id).build().user_data(Self::CANCEL);
|
||||
if unsafe { squeue.push(&entry) }.is_err() {
|
||||
changes.push_front(Change::Cancel { op_id });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
squeue.sync();
|
||||
|
||||
!changes.is_empty()
|
||||
}
|
||||
|
||||
pub unsafe fn poll<F: FnOnce()>(
|
||||
&self,
|
||||
timeout: Option<Duration>,
|
||||
f: F,
|
||||
) -> io::Result<()> {
|
||||
self.poll_blocking();
|
||||
|
||||
let has_more = self.apply_changes();
|
||||
let poll_result = self.poll_completions();
|
||||
|
||||
if !poll_result || has_more {
|
||||
if has_more {
|
||||
self.submit_auto(Some(Duration::ZERO))?;
|
||||
} else {
|
||||
self.submit_auto(timeout)?;
|
||||
}
|
||||
self.poll_completions();
|
||||
}
|
||||
|
||||
f();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn push(&self, op: &mut Key<dyn sys::OpCode>) -> Poll<io::Result<usize>> {
|
||||
log::trace!("Push op: {:?}", op.as_op_pin().name());
|
||||
|
||||
let user_data = op.user_data();
|
||||
loop {
|
||||
if self.push_blocking(user_data) {
|
||||
break Poll::Pending;
|
||||
} else {
|
||||
self.poll_blocking();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_completions(&self) -> bool {
|
||||
let mut ring = self.ring.borrow_mut();
|
||||
let mut cqueue = ring.completion();
|
||||
cqueue.sync();
|
||||
let has_entry = !cqueue.is_empty();
|
||||
if !has_entry {
|
||||
return false;
|
||||
}
|
||||
let mut handlers = self.handlers.take().unwrap();
|
||||
for entry in cqueue {
|
||||
let user_data = entry.user_data();
|
||||
match user_data {
|
||||
Self::CANCEL => {}
|
||||
Self::NOTIFY => {
|
||||
let flags = entry.flags();
|
||||
debug_assert!(more(flags));
|
||||
self.notifier.clear().expect("cannot clear notifier");
|
||||
}
|
||||
_ => {
|
||||
let batch = ((user_data & Self::BATCH_MASK) >> Self::BATCH) as usize;
|
||||
let user_data = (user_data & Self::DATA_MASK) as usize;
|
||||
|
||||
let result = entry.result();
|
||||
|
||||
if result == -libc::ECANCELED {
|
||||
handlers[batch].canceled(user_data);
|
||||
} else {
|
||||
let result = if result < 0 {
|
||||
Err(io::Error::from_raw_os_error(result))
|
||||
} else {
|
||||
Ok(result as _)
|
||||
};
|
||||
handlers[batch].completed(user_data, entry.flags(), result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.handlers.set(Some(handlers));
|
||||
true
|
||||
}
|
||||
|
||||
fn poll_blocking(&self) {
|
||||
if !self.pool_completed.is_empty() {
|
||||
while let Some(entry) = self.pool_completed.pop() {
|
||||
unsafe {
|
||||
entry.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push_blocking(&self, user_data: usize) -> bool {
|
||||
let handle = self.handle();
|
||||
let completed = self.pool_completed.clone();
|
||||
self.pool
|
||||
.dispatch(move || {
|
||||
let mut op = unsafe { Key::<dyn sys::OpCode>::new_unchecked(user_data) };
|
||||
let op_pin = op.as_op_pin();
|
||||
let res = op_pin.call_blocking();
|
||||
completed.push(Entry::new(user_data, res));
|
||||
handle.notify().ok();
|
||||
})
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub fn handle(&self) -> NotifyHandle {
|
||||
self.notifier.handle()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Driver {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.ring.borrow().as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
fn timespec(duration: std::time::Duration) -> Timespec {
|
||||
Timespec::new()
|
||||
.sec(duration.as_secs())
|
||||
.nsec(duration.subsec_nanos())
|
||||
}
|
73
ntex-neon/src/driver/uring/notify.rs
Normal file
73
ntex-neon/src/driver/uring/notify.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
|
||||
use std::{io, mem, sync::Arc};
|
||||
|
||||
use crate::syscall;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Notifier {
|
||||
fd: Arc<OwnedFd>,
|
||||
}
|
||||
|
||||
impl Notifier {
|
||||
/// Create a new notifier.
|
||||
pub(crate) fn new() -> io::Result<Self> {
|
||||
let fd = syscall!(libc::eventfd(0, libc::EFD_CLOEXEC | libc::EFD_NONBLOCK))?;
|
||||
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
|
||||
Ok(Self { fd: Arc::new(fd) })
|
||||
}
|
||||
|
||||
pub(crate) fn clear(&self) -> io::Result<()> {
|
||||
loop {
|
||||
let mut buffer = [0u64];
|
||||
let res = syscall!(libc::read(
|
||||
self.fd.as_raw_fd(),
|
||||
buffer.as_mut_ptr().cast(),
|
||||
mem::size_of::<u64>()
|
||||
));
|
||||
match res {
|
||||
Ok(len) => {
|
||||
debug_assert_eq!(len, mem::size_of::<u64>() as _);
|
||||
break Ok(());
|
||||
}
|
||||
// Clear the next time:)
|
||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => break Ok(()),
|
||||
// Just like read_exact
|
||||
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
|
||||
Err(e) => break Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handle(&self) -> NotifyHandle {
|
||||
NotifyHandle::new(self.fd.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Notifier {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.fd.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// A notify handle to the inner driver.
|
||||
pub struct NotifyHandle {
|
||||
fd: Arc<OwnedFd>,
|
||||
}
|
||||
|
||||
impl NotifyHandle {
|
||||
pub(crate) fn new(fd: Arc<OwnedFd>) -> Self {
|
||||
Self { fd }
|
||||
}
|
||||
|
||||
/// Notify the inner driver.
|
||||
pub fn notify(&self) -> io::Result<()> {
|
||||
let data = 1u64;
|
||||
syscall!(libc::write(
|
||||
self.fd.as_raw_fd(),
|
||||
&data as *const _ as *const _,
|
||||
std::mem::size_of::<u64>(),
|
||||
))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
55
ntex-neon/src/driver/uring/op.rs
Normal file
55
ntex-neon/src/driver/uring/op.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use std::{io, os::fd::AsRawFd, pin::Pin};
|
||||
|
||||
pub use crate::driver::unix::op::*;
|
||||
|
||||
use super::OpCode;
|
||||
use crate::{driver::op::*, syscall};
|
||||
|
||||
pub trait Handler {
|
||||
/// Operation is completed
|
||||
fn completed(&mut self, user_data: usize, flags: u32, result: io::Result<i32>);
|
||||
|
||||
fn canceled(&mut self, user_data: usize);
|
||||
}
|
||||
|
||||
impl<D, F> OpCode for Asyncify<F, D>
|
||||
where
|
||||
D: Send + 'static,
|
||||
F: (FnOnce() -> (io::Result<usize>, D)) + Send + 'static,
|
||||
{
|
||||
fn name(&self) -> &'static str {
|
||||
"Asyncify"
|
||||
}
|
||||
|
||||
fn call_blocking(self: Pin<&mut Self>) -> std::io::Result<usize> {
|
||||
// Safety: self won't be moved
|
||||
let this = unsafe { self.get_unchecked_mut() };
|
||||
let f = this
|
||||
.f
|
||||
.take()
|
||||
.expect("the operate method could only be called once");
|
||||
let (res, data) = f();
|
||||
this.data = Some(data);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl OpCode for CreateSocket {
|
||||
fn name(&self) -> &'static str {
|
||||
"CreateSocket"
|
||||
}
|
||||
|
||||
fn call_blocking(self: Pin<&mut Self>) -> io::Result<usize> {
|
||||
Ok(syscall!(libc::socket(self.domain, self.socket_type, self.protocol))? as _)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRawFd> OpCode for ShutdownSocket<S> {
|
||||
fn name(&self) -> &'static str {
|
||||
"ShutdownSocket"
|
||||
}
|
||||
|
||||
fn call_blocking(self: Pin<&mut Self>) -> io::Result<usize> {
|
||||
Ok(syscall!(libc::shutdown(self.fd.as_raw_fd(), self.how()))? as _)
|
||||
}
|
||||
}
|
|
@ -1,10 +1,9 @@
|
|||
#![allow(clippy::missing_safety_doc)]
|
||||
use std::{future::Future, io, mem, mem::MaybeUninit};
|
||||
use std::{io, mem::MaybeUninit};
|
||||
|
||||
use socket2::{Domain, Protocol, SockAddr, Socket as Socket2, Type};
|
||||
|
||||
use crate::driver::{op::CloseSocket, op::ShutdownSocket, AsRawFd};
|
||||
use crate::{impl_raw_fd, syscall};
|
||||
use crate::{driver::AsRawFd, impl_raw_fd, syscall};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Socket {
|
||||
|
@ -120,22 +119,6 @@ impl Socket {
|
|||
Ok(socket)
|
||||
}
|
||||
|
||||
pub fn close(self) -> impl Future<Output = io::Result<()>> {
|
||||
let op = CloseSocket::from_raw_fd(self.as_raw_fd());
|
||||
let fut = crate::submit(op);
|
||||
mem::forget(self);
|
||||
async move {
|
||||
fut.await.0?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn shutdown(&self) -> io::Result<()> {
|
||||
let op = ShutdownSocket::new(self.as_raw_fd(), std::net::Shutdown::Write);
|
||||
crate::submit(op).await.0?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub unsafe fn get_socket_option<T: Copy>(
|
||||
&self,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{future::Future, io, net::SocketAddr};
|
||||
use std::{io, net::SocketAddr};
|
||||
|
||||
use socket2::Socket as Socket2;
|
||||
|
||||
|
@ -26,11 +26,6 @@ impl TcpStream {
|
|||
Self { inner }
|
||||
}
|
||||
|
||||
/// Close the socket.
|
||||
pub fn close(self) -> impl Future<Output = io::Result<()>> {
|
||||
self.inner.close()
|
||||
}
|
||||
|
||||
/// Returns the socket address of the remote peer of this TCP connection.
|
||||
pub fn peer_addr(&self) -> io::Result<SocketAddr> {
|
||||
self.inner
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{future::Future, io};
|
||||
use std::io;
|
||||
|
||||
use socket2::{SockAddr, Socket as Socket2};
|
||||
|
||||
|
@ -27,12 +27,6 @@ impl UnixStream {
|
|||
Self { inner }
|
||||
}
|
||||
|
||||
/// Close the socket. If the returned future is dropped before polling, the
|
||||
/// socket won't be closed.
|
||||
pub fn close(self) -> impl Future<Output = io::Result<()>> {
|
||||
self.inner.close()
|
||||
}
|
||||
|
||||
/// Returns the socket path of the remote peer of this connection.
|
||||
pub fn peer_addr(&self) -> io::Result<SockAddr> {
|
||||
#[allow(unused_mut)]
|
||||
|
|
|
@ -15,7 +15,7 @@ impl<T: OpCode> OpFuture<T> {
|
|||
}
|
||||
|
||||
impl<T: OpCode> Future for OpFuture<T> {
|
||||
type Output = ((io::Result<usize>, T), u32);
|
||||
type Output = (io::Result<usize>, T);
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let res = Runtime::with_current(|r| r.poll_task(cx, self.key.take().unwrap()));
|
||||
|
|
|
@ -110,14 +110,6 @@ impl Runtime {
|
|||
}
|
||||
}
|
||||
|
||||
/// Attach a raw file descriptor/handle/socket to the runtime.
|
||||
///
|
||||
/// You only need this when authoring your own high-level APIs. High-level
|
||||
/// resources in this crate are attached automatically.
|
||||
pub fn attach(&self, fd: RawFd) -> io::Result<()> {
|
||||
self.driver.attach(fd)
|
||||
}
|
||||
|
||||
/// Block on the future till it completes.
|
||||
pub fn block_on<F: Future>(&self, future: F) -> F::Output {
|
||||
CURRENT_RUNTIME.set(self, || {
|
||||
|
@ -176,7 +168,7 @@ impl Runtime {
|
|||
// It is safe to use `submit` here because the task is spawned immediately.
|
||||
unsafe {
|
||||
let fut = self.submit_with_flags(op);
|
||||
self.spawn_unchecked(async move { fut.await.0 .1.into_inner() })
|
||||
self.spawn_unchecked(async move { fut.await.1.into_inner() })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,17 +182,13 @@ impl Runtime {
|
|||
fn submit_with_flags<T: OpCode + 'static>(
|
||||
&self,
|
||||
op: T,
|
||||
) -> impl Future<Output = ((io::Result<usize>, T), u32)> {
|
||||
) -> impl Future<Output = (io::Result<usize>, T)> {
|
||||
let fut = self.submit_raw(op);
|
||||
|
||||
async move {
|
||||
match fut {
|
||||
PushEntry::Pending(user_data) => OpFuture::new(user_data).await,
|
||||
PushEntry::Ready(res) => {
|
||||
// submit_flags won't be ready immediately, if ready, it must be error without
|
||||
// flags
|
||||
(res, 0)
|
||||
}
|
||||
PushEntry::Ready(res) => res,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -213,7 +201,7 @@ impl Runtime {
|
|||
&self,
|
||||
cx: &mut Context,
|
||||
op: Key<T>,
|
||||
) -> PushEntry<Key<T>, ((io::Result<usize>, T), u32)> {
|
||||
) -> PushEntry<Key<T>, (io::Result<usize>, T)> {
|
||||
self.driver.pop(op).map_pending(|mut k| {
|
||||
self.driver.update_waker(&mut k, cx.waker().clone());
|
||||
k
|
||||
|
@ -415,7 +403,7 @@ pub fn spawn_blocking<T: Send + 'static>(
|
|||
/// This method doesn't create runtime. It tries to obtain the current runtime
|
||||
/// by [`Runtime::with_current`].
|
||||
pub async fn submit<T: OpCode + 'static>(op: T) -> (io::Result<usize>, T) {
|
||||
submit_with_flags(op).await.0
|
||||
submit_with_flags(op).await
|
||||
}
|
||||
|
||||
/// Submit an operation to the current runtime, and return a future for it with
|
||||
|
@ -425,8 +413,6 @@ pub async fn submit<T: OpCode + 'static>(op: T) -> (io::Result<usize>, T) {
|
|||
///
|
||||
/// This method doesn't create runtime. It tries to obtain the current runtime
|
||||
/// by [`Runtime::with_current`].
|
||||
pub async fn submit_with_flags<T: OpCode + 'static>(
|
||||
op: T,
|
||||
) -> ((io::Result<usize>, T), u32) {
|
||||
pub async fn submit_with_flags<T: OpCode + 'static>(op: T) -> (io::Result<usize>, T) {
|
||||
Runtime::with_current(|r| r.submit_with_flags(op)).await
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# Changes
|
||||
|
||||
## [2.5.0] - 2025-03-10
|
||||
## [2.5.0] - 2025-03-12
|
||||
|
||||
* Add ntex-runtime support
|
||||
* Add neon runtime support
|
||||
|
||||
* Drop glommio support
|
||||
|
||||
|
|
|
@ -24,8 +24,11 @@ tokio = ["ntex-rt/tokio", "ntex-tokio"]
|
|||
# compio runtime
|
||||
compio = ["ntex-rt/compio", "ntex-compio"]
|
||||
|
||||
# default ntex runtime
|
||||
neon = ["ntex-rt/neon", "ntex-neon", "slab", "socket2"]
|
||||
# neon runtime
|
||||
neon = ["ntex-rt/neon", "ntex-neon/polling", "slab", "socket2"]
|
||||
|
||||
# neon io-uring runtime
|
||||
neon-uring = ["ntex-rt/neon", "ntex-neon/io-uring", "io-uring", "slab", "socket2"]
|
||||
|
||||
[dependencies]
|
||||
ntex-service = "3.3"
|
||||
|
@ -46,6 +49,10 @@ thiserror = { workspace = true }
|
|||
slab = { workspace = true, optional = true }
|
||||
socket2 = { workspace = true, optional = true }
|
||||
|
||||
# Linux specific dependencies
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
io-uring = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ntex = "2"
|
||||
env_logger = "0.11"
|
||||
|
|
|
@ -6,24 +6,53 @@ pub use ntex_tokio::{from_tcp_stream, tcp_connect, tcp_connect_in};
|
|||
#[cfg(all(unix, feature = "tokio"))]
|
||||
pub use ntex_tokio::{from_unix_stream, unix_connect, unix_connect_in};
|
||||
|
||||
#[cfg(all(feature = "neon", not(feature = "tokio"), not(feature = "compio")))]
|
||||
pub use crate::rt::{
|
||||
#[cfg(all(
|
||||
feature = "neon",
|
||||
not(feature = "neon-uring"),
|
||||
not(feature = "tokio"),
|
||||
not(feature = "compio")
|
||||
))]
|
||||
pub use crate::rt_polling::{
|
||||
from_tcp_stream, from_unix_stream, tcp_connect, tcp_connect_in, unix_connect,
|
||||
unix_connect_in,
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "compio", not(feature = "tokio"), not(feature = "neon")))]
|
||||
#[cfg(all(
|
||||
feature = "neon-uring",
|
||||
not(feature = "neon"),
|
||||
not(feature = "tokio"),
|
||||
not(feature = "compio"),
|
||||
target_os = "linux",
|
||||
feature = "io-uring"
|
||||
))]
|
||||
pub use crate::rt_uring::{
|
||||
from_tcp_stream, from_unix_stream, tcp_connect, tcp_connect_in, unix_connect,
|
||||
unix_connect_in,
|
||||
};
|
||||
|
||||
#[cfg(all(
|
||||
feature = "compio",
|
||||
not(feature = "tokio"),
|
||||
not(feature = "neon"),
|
||||
not(feature = "neon-uring")
|
||||
))]
|
||||
pub use ntex_compio::{from_tcp_stream, tcp_connect, tcp_connect_in};
|
||||
|
||||
#[cfg(all(
|
||||
unix,
|
||||
feature = "compio",
|
||||
not(feature = "tokio"),
|
||||
not(feature = "neon")
|
||||
not(feature = "neon"),
|
||||
not(feature = "neon-uring")
|
||||
))]
|
||||
pub use ntex_compio::{from_unix_stream, unix_connect, unix_connect_in};
|
||||
|
||||
#[cfg(all(not(feature = "tokio"), not(feature = "compio"), not(feature = "neon")))]
|
||||
#[cfg(all(
|
||||
not(feature = "tokio"),
|
||||
not(feature = "compio"),
|
||||
not(feature = "neon"),
|
||||
not(feature = "neon-uring")
|
||||
))]
|
||||
mod no_rt {
|
||||
use ntex_io::Io;
|
||||
|
||||
|
@ -88,5 +117,10 @@ mod no_rt {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "tokio"), not(feature = "compio"), not(feature = "neon")))]
|
||||
#[cfg(all(
|
||||
not(feature = "tokio"),
|
||||
not(feature = "compio"),
|
||||
not(feature = "neon"),
|
||||
not(feature = "neon-uring")
|
||||
))]
|
||||
pub use no_rt::*;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! Utility for async runtime abstraction
|
||||
#![deny(rust_2018_idioms, unreachable_pub, missing_debug_implementations)]
|
||||
#![allow(unused_variables, dead_code)]
|
||||
|
||||
mod compat;
|
||||
pub mod connect;
|
||||
|
@ -9,5 +10,20 @@ pub use ntex_rt::{spawn, spawn_blocking};
|
|||
|
||||
pub use self::compat::*;
|
||||
|
||||
#[cfg(all(feature = "neon", not(feature = "tokio"), not(feature = "compio")))]
|
||||
mod rt;
|
||||
#[cfg(all(
|
||||
feature = "neon",
|
||||
not(feature = "neon-uring"),
|
||||
not(feature = "tokio"),
|
||||
not(feature = "compio")
|
||||
))]
|
||||
mod rt_polling;
|
||||
|
||||
#[cfg(all(
|
||||
feature = "neon-uring",
|
||||
not(feature = "neon"),
|
||||
not(feature = "tokio"),
|
||||
not(feature = "compio"),
|
||||
target_os = "linux",
|
||||
feature = "io-uring"
|
||||
))]
|
||||
mod rt_uring;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{cell::Cell, collections::VecDeque, fmt, io, ptr, rc::Rc, task, task::Poll};
|
||||
|
||||
use ntex_neon::driver::op::{Handler, Interest};
|
||||
use ntex_neon::driver::op::{CloseSocket, Handler, Interest};
|
||||
use ntex_neon::driver::{AsRawFd, DriverApi, RawFd};
|
||||
use ntex_neon::{syscall, Runtime};
|
||||
use slab::Slab;
|
||||
|
@ -19,10 +19,10 @@ bitflags::bitflags! {
|
|||
|
||||
pub(crate) struct StreamCtl<T> {
|
||||
id: usize,
|
||||
inner: Rc<CompioOpsInner<T>>,
|
||||
inner: Rc<StreamOpsInner<T>>,
|
||||
}
|
||||
|
||||
struct TcpStreamItem<T> {
|
||||
struct StreamItem<T> {
|
||||
io: Option<T>,
|
||||
fd: RawFd,
|
||||
context: IoContext,
|
||||
|
@ -30,7 +30,7 @@ struct TcpStreamItem<T> {
|
|||
ref_count: usize,
|
||||
}
|
||||
|
||||
pub(crate) struct CompioOps<T>(Rc<CompioOpsInner<T>>);
|
||||
pub(crate) struct StreamOps<T>(Rc<StreamOpsInner<T>>);
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Change {
|
||||
|
@ -39,18 +39,18 @@ enum Change {
|
|||
Error(io::Error),
|
||||
}
|
||||
|
||||
struct CompioOpsBatcher<T> {
|
||||
struct StreamOpsHandler<T> {
|
||||
feed: VecDeque<(usize, Change)>,
|
||||
inner: Rc<CompioOpsInner<T>>,
|
||||
inner: Rc<StreamOpsInner<T>>,
|
||||
}
|
||||
|
||||
struct CompioOpsInner<T> {
|
||||
struct StreamOpsInner<T> {
|
||||
api: DriverApi,
|
||||
feed: Cell<Option<VecDeque<usize>>>,
|
||||
streams: Cell<Option<Box<Slab<TcpStreamItem<T>>>>>,
|
||||
streams: Cell<Option<Box<Slab<StreamItem<T>>>>>,
|
||||
}
|
||||
|
||||
impl<T: AsRawFd + 'static> CompioOps<T> {
|
||||
impl<T: AsRawFd + 'static> StreamOps<T> {
|
||||
pub(crate) fn current() -> Self {
|
||||
Runtime::with_current(|rt| {
|
||||
if let Some(s) = rt.get::<Self>() {
|
||||
|
@ -58,19 +58,19 @@ impl<T: AsRawFd + 'static> CompioOps<T> {
|
|||
} else {
|
||||
let mut inner = None;
|
||||
rt.driver().register_handler(|api| {
|
||||
let ops = Rc::new(CompioOpsInner {
|
||||
let ops = Rc::new(StreamOpsInner {
|
||||
api,
|
||||
feed: Cell::new(Some(VecDeque::new())),
|
||||
streams: Cell::new(Some(Box::new(Slab::new()))),
|
||||
});
|
||||
inner = Some(ops.clone());
|
||||
Box::new(CompioOpsBatcher {
|
||||
Box::new(StreamOpsHandler {
|
||||
inner: ops,
|
||||
feed: VecDeque::new(),
|
||||
})
|
||||
});
|
||||
|
||||
let s = CompioOps(inner.unwrap());
|
||||
let s = StreamOps(inner.unwrap());
|
||||
rt.insert(s.clone());
|
||||
s
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ impl<T: AsRawFd + 'static> CompioOps<T> {
|
|||
}
|
||||
|
||||
pub(crate) fn register(&self, io: T, context: IoContext) -> StreamCtl<T> {
|
||||
let item = TcpStreamItem {
|
||||
let item = StreamItem {
|
||||
context,
|
||||
fd: io.as_raw_fd(),
|
||||
io: Some(io),
|
||||
|
@ -96,7 +96,7 @@ impl<T: AsRawFd + 'static> CompioOps<T> {
|
|||
|
||||
fn with<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Slab<TcpStreamItem<T>>) -> R,
|
||||
F: FnOnce(&mut Slab<StreamItem<T>>) -> R,
|
||||
{
|
||||
let mut inner = self.0.streams.take().unwrap();
|
||||
let result = f(&mut inner);
|
||||
|
@ -105,13 +105,13 @@ impl<T: AsRawFd + 'static> CompioOps<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for CompioOps<T> {
|
||||
impl<T> Clone for StreamOps<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Handler for CompioOpsBatcher<T> {
|
||||
impl<T> Handler for StreamOpsHandler<T> {
|
||||
fn readable(&mut self, id: usize) {
|
||||
log::debug!("FD is readable {:?}", id);
|
||||
self.feed.push_back((id, Change::Readable));
|
||||
|
@ -149,7 +149,8 @@ impl<T> Handler for CompioOpsBatcher<T> {
|
|||
.inspect(|size| {
|
||||
unsafe { buf.advance_mut(*size) };
|
||||
log::debug!(
|
||||
"FD: {:?}, SIZE: {:?}, BUF: {:?}",
|
||||
"{}: {:?}, SIZE: {:?}, BUF: {:?}",
|
||||
item.context.tag(),
|
||||
item.fd,
|
||||
size,
|
||||
buf
|
||||
|
@ -193,10 +194,11 @@ impl<T> Handler for CompioOpsBatcher<T> {
|
|||
// extra
|
||||
let mut feed = self.inner.feed.take().unwrap();
|
||||
for id in feed.drain(..) {
|
||||
log::debug!("Drop io ({}), {:?}", id, streams[id].fd);
|
||||
let item = &mut streams[id];
|
||||
log::debug!("{}: Drop io ({}), {:?}", item.context.tag(), id, item.fd);
|
||||
|
||||
streams[id].ref_count -= 1;
|
||||
if streams[id].ref_count == 0 {
|
||||
item.ref_count -= 1;
|
||||
if item.ref_count == 0 {
|
||||
let item = streams.remove(id);
|
||||
if item.io.is_some() {
|
||||
self.inner.api.unregister_all(item.fd);
|
||||
|
@ -209,20 +211,17 @@ impl<T> Handler for CompioOpsBatcher<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) trait Closable {
|
||||
async fn close(self) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl<T> StreamCtl<T> {
|
||||
pub(crate) async fn close(self) -> io::Result<()>
|
||||
where
|
||||
T: Closable,
|
||||
{
|
||||
if let Some(io) = self.with(|streams| streams[self.id].io.take()) {
|
||||
io.close().await
|
||||
} else {
|
||||
Ok(())
|
||||
pub(crate) async fn close(self) -> io::Result<()> {
|
||||
let (io, fd) =
|
||||
self.with(|streams| (streams[self.id].io.take(), streams[self.id].fd));
|
||||
if let Some(io) = io {
|
||||
let op = CloseSocket::from_raw_fd(fd);
|
||||
let fut = ntex_neon::submit(op);
|
||||
std::mem::forget(io);
|
||||
fut.await.0?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn with_io<F, R>(&self, f: F) -> R
|
||||
|
@ -237,7 +236,12 @@ impl<T> StreamCtl<T> {
|
|||
let item = &mut streams[self.id];
|
||||
|
||||
if item.flags.intersects(Flags::RD | Flags::WR) {
|
||||
log::debug!("Pause all io ({}), {:?}", self.id, item.fd);
|
||||
log::debug!(
|
||||
"{}: Pause all io ({}), {:?}",
|
||||
item.context.tag(),
|
||||
self.id,
|
||||
item.fd
|
||||
);
|
||||
item.flags.remove(Flags::RD | Flags::WR);
|
||||
self.inner.api.unregister_all(item.fd);
|
||||
}
|
||||
|
@ -248,7 +252,12 @@ impl<T> StreamCtl<T> {
|
|||
self.with(|streams| {
|
||||
let item = &mut streams[self.id];
|
||||
|
||||
log::debug!("Pause io read ({}), {:?}", self.id, item.fd);
|
||||
log::debug!(
|
||||
"{}: Pause io read ({}), {:?}",
|
||||
item.context.tag(),
|
||||
self.id,
|
||||
item.fd
|
||||
);
|
||||
if item.flags.contains(Flags::RD) {
|
||||
item.flags.remove(Flags::RD);
|
||||
self.inner.api.unregister(item.fd, Interest::Readable);
|
||||
|
@ -260,7 +269,12 @@ impl<T> StreamCtl<T> {
|
|||
self.with(|streams| {
|
||||
let item = &mut streams[self.id];
|
||||
|
||||
log::debug!("Resume io read ({}), {:?}", self.id, item.fd);
|
||||
log::debug!(
|
||||
"{}: Resume io read ({}), {:?}",
|
||||
item.context.tag(),
|
||||
self.id,
|
||||
item.fd
|
||||
);
|
||||
if !item.flags.contains(Flags::RD) {
|
||||
item.flags.insert(Flags::RD);
|
||||
self.inner
|
||||
|
@ -275,9 +289,19 @@ impl<T> StreamCtl<T> {
|
|||
let item = &mut streams[self.id];
|
||||
|
||||
if !item.flags.contains(Flags::WR) {
|
||||
log::debug!("Resume io write ({}), {:?}", self.id, item.fd);
|
||||
log::debug!(
|
||||
"{}: Resume io write ({}), {:?}",
|
||||
item.context.tag(),
|
||||
self.id,
|
||||
item.fd
|
||||
);
|
||||
let result = item.context.with_write_buf(|buf| {
|
||||
log::debug!("Writing io ({}), buf: {:?}", self.id, buf.len());
|
||||
log::debug!(
|
||||
"{}: Writing io ({}), buf: {:?}",
|
||||
item.context.tag(),
|
||||
self.id,
|
||||
buf.len()
|
||||
);
|
||||
|
||||
let slice = &buf[..];
|
||||
syscall!(break libc::write(item.fd, slice.as_ptr() as _, slice.len()))
|
||||
|
@ -285,7 +309,8 @@ impl<T> StreamCtl<T> {
|
|||
|
||||
if result.is_pending() {
|
||||
log::debug!(
|
||||
"Write is pending ({}), {:?}",
|
||||
"{}: Write is pending ({}), {:?}",
|
||||
item.context.tag(),
|
||||
self.id,
|
||||
item.context.flags()
|
||||
);
|
||||
|
@ -301,7 +326,7 @@ impl<T> StreamCtl<T> {
|
|||
|
||||
fn with<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Slab<TcpStreamItem<T>>) -> R,
|
||||
F: FnOnce(&mut Slab<StreamItem<T>>) -> R,
|
||||
{
|
||||
let mut inner = self.inner.streams.take().unwrap();
|
||||
let result = f(&mut inner);
|
||||
|
@ -325,7 +350,12 @@ impl<T> Clone for StreamCtl<T> {
|
|||
impl<T> Drop for StreamCtl<T> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(mut streams) = self.inner.streams.take() {
|
||||
log::debug!("Drop io ({}), {:?}", self.id, streams[self.id].fd);
|
||||
log::debug!(
|
||||
"{}: Drop io ({}), {:?}",
|
||||
streams[self.id].context.tag(),
|
||||
self.id,
|
||||
streams[self.id].fd
|
||||
);
|
||||
|
||||
streams[self.id].ref_count -= 1;
|
||||
if streams[self.id].ref_count == 0 {
|
|
@ -1,17 +1,17 @@
|
|||
use std::{any, future::poll_fn, io, task::Poll};
|
||||
use std::{any, future::poll_fn, task::Poll};
|
||||
|
||||
use ntex_io::{
|
||||
types, Handle, IoContext, IoStream, ReadContext, ReadStatus, WriteContext, WriteStatus,
|
||||
};
|
||||
use ntex_neon::{net::TcpStream, net::UnixStream, spawn};
|
||||
use ntex_neon::{net::TcpStream, spawn};
|
||||
|
||||
use super::driver::{Closable, CompioOps, StreamCtl};
|
||||
use super::driver::{StreamCtl, StreamOps};
|
||||
|
||||
impl IoStream for super::TcpStream {
|
||||
fn start(self, read: ReadContext, _: WriteContext) -> Option<Box<dyn Handle>> {
|
||||
let io = self.0;
|
||||
let context = read.context();
|
||||
let ctl = CompioOps::current().register(io, context.clone());
|
||||
let ctl = StreamOps::current().register(io, context.clone());
|
||||
let ctl2 = ctl.clone();
|
||||
spawn(async move { run(ctl, context).await }).detach();
|
||||
|
||||
|
@ -23,7 +23,7 @@ impl IoStream for super::UnixStream {
|
|||
fn start(self, read: ReadContext, _: WriteContext) -> Option<Box<dyn Handle>> {
|
||||
let io = self.0;
|
||||
let context = read.context();
|
||||
let ctl = CompioOps::current().register(io, context.clone());
|
||||
let ctl = StreamOps::current().register(io, context.clone());
|
||||
spawn(async move { run(ctl, context).await }).detach();
|
||||
|
||||
None
|
||||
|
@ -44,25 +44,13 @@ impl Handle for HandleWrapper {
|
|||
}
|
||||
}
|
||||
|
||||
impl Closable for TcpStream {
|
||||
async fn close(self) -> io::Result<()> {
|
||||
TcpStream::close(self).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Closable for UnixStream {
|
||||
async fn close(self) -> io::Result<()> {
|
||||
UnixStream::close(self).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
enum Status {
|
||||
Shutdown,
|
||||
Terminate,
|
||||
}
|
||||
|
||||
async fn run<T: Closable>(ctl: StreamCtl<T>, context: IoContext) {
|
||||
async fn run<T>(ctl: StreamCtl<T>, context: IoContext) {
|
||||
// Handle io read readiness
|
||||
let st = poll_fn(|cx| {
|
||||
let read = match context.poll_read_ready(cx) {
|
|
@ -1,4 +1,3 @@
|
|||
#![allow(clippy::type_complexity)]
|
||||
use std::{io::Result, net, net::SocketAddr};
|
||||
|
||||
use ntex_bytes::PoolRef;
|
||||
|
@ -8,10 +7,10 @@ mod connect;
|
|||
mod driver;
|
||||
mod io;
|
||||
|
||||
/// Tcp stream wrapper for compio TcpStream
|
||||
/// Tcp stream wrapper for neon TcpStream
|
||||
struct TcpStream(ntex_neon::net::TcpStream);
|
||||
|
||||
/// Tcp stream wrapper for compio UnixStream
|
||||
/// Tcp stream wrapper for neon UnixStream
|
||||
struct UnixStream(ntex_neon::net::UnixStream);
|
||||
|
||||
/// Opens a TCP connection to a remote host.
|
138
ntex-net/src/rt_uring/connect.rs
Normal file
138
ntex-net/src/rt_uring/connect.rs
Normal file
|
@ -0,0 +1,138 @@
|
|||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
|
||||
use std::{cell::RefCell, io, path::Path, rc::Rc};
|
||||
|
||||
use io_uring::{opcode, types::Fd};
|
||||
use ntex_neon::driver::op::Handler;
|
||||
use ntex_neon::driver::{AsRawFd, DriverApi, RawFd};
|
||||
use ntex_neon::net::{Socket, TcpStream, UnixStream};
|
||||
use ntex_neon::Runtime;
|
||||
use ntex_util::channel::oneshot::{channel, Sender};
|
||||
use slab::Slab;
|
||||
use socket2::{Protocol, SockAddr, Type};
|
||||
|
||||
pub(crate) async fn connect(addr: SocketAddr) -> io::Result<TcpStream> {
|
||||
let addr = SockAddr::from(addr);
|
||||
let socket = if cfg!(windows) {
|
||||
let bind_addr = if addr.is_ipv4() {
|
||||
SockAddr::from(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0))
|
||||
} else if addr.is_ipv6() {
|
||||
SockAddr::from(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0))
|
||||
} else {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::AddrNotAvailable,
|
||||
"Unsupported address domain.",
|
||||
));
|
||||
};
|
||||
Socket::bind(&bind_addr, Type::STREAM, Some(Protocol::TCP)).await?
|
||||
} else {
|
||||
Socket::new(addr.domain(), Type::STREAM, Some(Protocol::TCP)).await?
|
||||
};
|
||||
|
||||
let (sender, rx) = channel();
|
||||
|
||||
ConnectOps::current().connect(socket.as_raw_fd(), addr, sender);
|
||||
|
||||
rx.await
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "IO Driver is gone"))
|
||||
.and_then(|item| item)?;
|
||||
|
||||
Ok(TcpStream::from_socket(socket))
|
||||
}
|
||||
|
||||
pub(crate) async fn connect_unix(path: impl AsRef<Path>) -> io::Result<UnixStream> {
|
||||
let addr = SockAddr::unix(path)?;
|
||||
|
||||
#[cfg(windows)]
|
||||
let socket = {
|
||||
let new_addr = empty_unix_socket();
|
||||
Socket::bind(&new_addr, Type::STREAM, None).await?
|
||||
};
|
||||
#[cfg(unix)]
|
||||
let socket = {
|
||||
use socket2::Domain;
|
||||
Socket::new(Domain::UNIX, Type::STREAM, None).await?
|
||||
};
|
||||
|
||||
let (sender, rx) = channel();
|
||||
|
||||
ConnectOps::current().connect(socket.as_raw_fd(), addr, sender);
|
||||
|
||||
rx.await
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "IO Driver is gone"))
|
||||
.and_then(|item| item)?;
|
||||
|
||||
Ok(UnixStream::from_socket(socket))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ConnectOps(Rc<ConnectOpsInner>);
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Change {
|
||||
Readable,
|
||||
Writable,
|
||||
Error(io::Error),
|
||||
}
|
||||
|
||||
struct ConnectOpsHandler {
|
||||
inner: Rc<ConnectOpsInner>,
|
||||
}
|
||||
|
||||
struct ConnectOpsInner {
|
||||
api: DriverApi,
|
||||
ops: RefCell<Slab<Sender<io::Result<()>>>>,
|
||||
}
|
||||
|
||||
impl ConnectOps {
|
||||
pub(crate) fn current() -> Self {
|
||||
Runtime::with_current(|rt| {
|
||||
if let Some(s) = rt.get::<Self>() {
|
||||
s
|
||||
} else {
|
||||
let mut inner = None;
|
||||
rt.driver().register_handler(|api| {
|
||||
let ops = Rc::new(ConnectOpsInner {
|
||||
api,
|
||||
ops: RefCell::new(Slab::new()),
|
||||
});
|
||||
inner = Some(ops.clone());
|
||||
Box::new(ConnectOpsHandler { inner: ops })
|
||||
});
|
||||
|
||||
let s = ConnectOps(inner.unwrap());
|
||||
rt.insert(s.clone());
|
||||
s
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn connect(
|
||||
&self,
|
||||
fd: RawFd,
|
||||
addr: SockAddr,
|
||||
sender: Sender<io::Result<()>>,
|
||||
) -> usize {
|
||||
let id = self.0.ops.borrow_mut().insert(sender);
|
||||
self.0.api.submit(
|
||||
id as u32,
|
||||
opcode::Connect::new(Fd(fd), addr.as_ptr(), addr.len()).build(),
|
||||
);
|
||||
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler for ConnectOpsHandler {
|
||||
fn canceled(&mut self, user_data: usize) {
|
||||
log::debug!("Op is canceled {:?}", user_data);
|
||||
|
||||
self.inner.ops.borrow_mut().remove(user_data);
|
||||
}
|
||||
|
||||
fn completed(&mut self, user_data: usize, flags: u32, result: io::Result<i32>) {
|
||||
log::debug!("Op is completed {:?} result: {:?}", user_data, result);
|
||||
|
||||
let tx = self.inner.ops.borrow_mut().remove(user_data);
|
||||
let _ = tx.send(result.map(|_| ()));
|
||||
}
|
||||
}
|
419
ntex-net/src/rt_uring/driver.rs
Normal file
419
ntex-net/src/rt_uring/driver.rs
Normal file
|
@ -0,0 +1,419 @@
|
|||
use std::{cell::RefCell, fmt, io, mem, num::NonZeroU32, rc::Rc, task::Poll};
|
||||
|
||||
use io_uring::{opcode, squeue::Entry, types::Fd};
|
||||
use ntex_neon::driver::op::Handler;
|
||||
use ntex_neon::driver::{AsRawFd, DriverApi};
|
||||
use ntex_neon::Runtime;
|
||||
use ntex_util::channel::oneshot;
|
||||
use slab::Slab;
|
||||
|
||||
use ntex_bytes::{Buf, BufMut, BytesVec};
|
||||
use ntex_io::IoContext;
|
||||
|
||||
pub(crate) struct StreamCtl<T> {
|
||||
id: usize,
|
||||
inner: Rc<StreamOpsInner<T>>,
|
||||
}
|
||||
|
||||
struct StreamItem<T> {
|
||||
io: Option<T>,
|
||||
fd: Fd,
|
||||
context: IoContext,
|
||||
ref_count: usize,
|
||||
rd_op: Option<NonZeroU32>,
|
||||
wr_op: Option<NonZeroU32>,
|
||||
}
|
||||
|
||||
enum Operation {
|
||||
Recv {
|
||||
id: usize,
|
||||
buf: BytesVec,
|
||||
context: IoContext,
|
||||
},
|
||||
Send {
|
||||
id: usize,
|
||||
buf: BytesVec,
|
||||
context: IoContext,
|
||||
},
|
||||
Close {
|
||||
tx: Option<oneshot::Sender<io::Result<i32>>>,
|
||||
},
|
||||
Nop,
|
||||
}
|
||||
|
||||
pub(crate) struct StreamOps<T>(Rc<StreamOpsInner<T>>);
|
||||
|
||||
struct StreamOpsHandler<T> {
|
||||
inner: Rc<StreamOpsInner<T>>,
|
||||
}
|
||||
|
||||
struct StreamOpsInner<T> {
|
||||
api: DriverApi,
|
||||
feed: RefCell<Vec<usize>>,
|
||||
storage: RefCell<StreamOpsStorage<T>>,
|
||||
}
|
||||
|
||||
struct StreamOpsStorage<T> {
|
||||
ops: Slab<Operation>,
|
||||
streams: Slab<StreamItem<T>>,
|
||||
}
|
||||
|
||||
impl<T: AsRawFd + 'static> StreamOps<T> {
|
||||
pub(crate) fn current() -> Self {
|
||||
Runtime::with_current(|rt| {
|
||||
if let Some(s) = rt.get::<Self>() {
|
||||
s
|
||||
} else {
|
||||
let mut inner = None;
|
||||
rt.driver().register_handler(|api| {
|
||||
let mut ops = Slab::new();
|
||||
ops.insert(Operation::Nop);
|
||||
|
||||
let ops = Rc::new(StreamOpsInner {
|
||||
api,
|
||||
feed: RefCell::new(Vec::new()),
|
||||
storage: RefCell::new(StreamOpsStorage {
|
||||
ops,
|
||||
streams: Slab::new(),
|
||||
}),
|
||||
});
|
||||
inner = Some(ops.clone());
|
||||
Box::new(StreamOpsHandler { inner: ops })
|
||||
});
|
||||
|
||||
let s = StreamOps(inner.unwrap());
|
||||
rt.insert(s.clone());
|
||||
s
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn register(&self, io: T, context: IoContext) -> StreamCtl<T> {
|
||||
let item = StreamItem {
|
||||
context,
|
||||
fd: Fd(io.as_raw_fd()),
|
||||
io: Some(io),
|
||||
ref_count: 1,
|
||||
rd_op: None,
|
||||
wr_op: None,
|
||||
};
|
||||
let id = self.0.storage.borrow_mut().streams.insert(item);
|
||||
StreamCtl {
|
||||
id,
|
||||
inner: self.0.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn with<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut StreamOpsStorage<T>) -> R,
|
||||
{
|
||||
f(&mut *self.0.storage.borrow_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for StreamOps<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Handler for StreamOpsHandler<T> {
|
||||
fn canceled(&mut self, user_data: usize) {
|
||||
let mut storage = self.inner.storage.borrow_mut();
|
||||
|
||||
match storage.ops.remove(user_data) {
|
||||
Operation::Recv { id, buf, context } => {
|
||||
log::debug!("{}: Recv canceled {:?}", context.tag(), id,);
|
||||
context.release_read_buf(buf);
|
||||
}
|
||||
Operation::Send { id, buf, context } => {
|
||||
log::debug!("{}: Send canceled: {:?}", context.tag(), id);
|
||||
context.release_write_buf(buf);
|
||||
}
|
||||
Operation::Nop | Operation::Close { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn completed(&mut self, user_data: usize, flags: u32, result: io::Result<i32>) {
|
||||
let mut storage = self.inner.storage.borrow_mut();
|
||||
|
||||
let op = storage.ops.remove(user_data);
|
||||
match op {
|
||||
Operation::Recv {
|
||||
id,
|
||||
mut buf,
|
||||
context,
|
||||
} => {
|
||||
let result = result.map(|size| {
|
||||
unsafe { buf.advance_mut(size as usize) };
|
||||
size as usize
|
||||
});
|
||||
|
||||
// reset op reference
|
||||
if let Some(item) = storage.streams.get_mut(id) {
|
||||
log::debug!(
|
||||
"{}: Recv completed {:?}, res: {:?}, buf({}): {:?}",
|
||||
context.tag(),
|
||||
item.fd,
|
||||
result,
|
||||
buf.remaining_mut(),
|
||||
buf,
|
||||
);
|
||||
item.rd_op.take();
|
||||
}
|
||||
|
||||
// set read buf
|
||||
let tag = context.tag();
|
||||
if context.set_read_buf(result, buf).is_pending() {
|
||||
if let Some((id, op)) = storage.recv(id, Some(context)) {
|
||||
self.inner.api.submit(id, op);
|
||||
}
|
||||
} else {
|
||||
log::debug!("{}: Recv to pause", tag);
|
||||
}
|
||||
}
|
||||
Operation::Send { id, buf, context } => {
|
||||
// reset op reference
|
||||
if let Some(item) = storage.streams.get_mut(id) {
|
||||
log::debug!(
|
||||
"{}: Send completed: {:?}, res: {:?}",
|
||||
context.tag(),
|
||||
item.fd,
|
||||
result
|
||||
);
|
||||
item.wr_op.take();
|
||||
}
|
||||
|
||||
// set read buf
|
||||
if context
|
||||
.set_write_buf(result.map(|size| size as usize), buf)
|
||||
.is_pending()
|
||||
{
|
||||
if let Some((id, op)) = storage.send(id, Some(context)) {
|
||||
self.inner.api.submit(id, op);
|
||||
}
|
||||
}
|
||||
}
|
||||
Operation::Close { tx } => {
|
||||
if let Some(tx) = tx {
|
||||
let _ = tx.send(result);
|
||||
}
|
||||
}
|
||||
Operation::Nop => {}
|
||||
}
|
||||
|
||||
// extra
|
||||
for id in self.inner.feed.borrow_mut().drain(..) {
|
||||
storage.streams[id].ref_count -= 1;
|
||||
if storage.streams[id].ref_count == 0 {
|
||||
let mut item = storage.streams.remove(id);
|
||||
|
||||
log::debug!("{}: Drop io ({}), {:?}", item.context.tag(), id, item.fd);
|
||||
|
||||
if let Some(io) = item.io.take() {
|
||||
mem::forget(io);
|
||||
|
||||
let id = storage.ops.insert(Operation::Close { tx: None });
|
||||
assert!(id < u32::MAX as usize);
|
||||
self.inner
|
||||
.api
|
||||
.submit(id as u32, opcode::Close::new(item.fd).build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> StreamOpsStorage<T> {
|
||||
fn recv(&mut self, id: usize, context: Option<IoContext>) -> Option<(u32, Entry)> {
|
||||
let item = &mut self.streams[id];
|
||||
|
||||
if item.rd_op.is_none() {
|
||||
if let Poll::Ready(mut buf) = item.context.get_read_buf() {
|
||||
log::debug!(
|
||||
"{}: Recv resume ({}), {:?} - {:?} = {:?}",
|
||||
item.context.tag(),
|
||||
id,
|
||||
item.fd,
|
||||
buf,
|
||||
buf.remaining_mut()
|
||||
);
|
||||
|
||||
let slice = buf.chunk_mut();
|
||||
let op = opcode::Recv::new(item.fd, slice.as_mut_ptr(), slice.len() as u32)
|
||||
.build();
|
||||
|
||||
let op_id = self.ops.insert(Operation::Recv {
|
||||
id,
|
||||
buf,
|
||||
context: context.unwrap_or_else(|| item.context.clone()),
|
||||
});
|
||||
assert!(op_id < u32::MAX as usize);
|
||||
|
||||
item.rd_op = NonZeroU32::new(op_id as u32);
|
||||
return Some((op_id as u32, op));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn send(&mut self, id: usize, context: Option<IoContext>) -> Option<(u32, Entry)> {
|
||||
let item = &mut self.streams[id];
|
||||
|
||||
if item.wr_op.is_none() {
|
||||
if let Poll::Ready(buf) = item.context.get_write_buf() {
|
||||
log::debug!(
|
||||
"{}: Send resume ({}), {:?} {:?}",
|
||||
item.context.tag(),
|
||||
id,
|
||||
item.fd,
|
||||
buf
|
||||
);
|
||||
|
||||
let slice = buf.chunk();
|
||||
let op =
|
||||
opcode::Send::new(item.fd, slice.as_ptr(), slice.len() as u32).build();
|
||||
|
||||
let op_id = self.ops.insert(Operation::Send {
|
||||
id,
|
||||
buf,
|
||||
context: context.unwrap_or_else(|| item.context.clone()),
|
||||
});
|
||||
assert!(op_id < u32::MAX as usize);
|
||||
|
||||
item.wr_op = NonZeroU32::new(op_id as u32);
|
||||
return Some((op_id as u32, op));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> StreamCtl<T> {
|
||||
pub(crate) async fn close(self) -> io::Result<()> {
|
||||
let result = {
|
||||
let mut storage = self.inner.storage.borrow_mut();
|
||||
|
||||
let (io, fd) = {
|
||||
let item = &mut storage.streams[self.id];
|
||||
(item.io.take(), item.fd)
|
||||
};
|
||||
if let Some(io) = io {
|
||||
mem::forget(io);
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let id = storage.ops.insert(Operation::Close { tx: Some(tx) });
|
||||
assert!(id < u32::MAX as usize);
|
||||
|
||||
drop(storage);
|
||||
self.inner
|
||||
.api
|
||||
.submit(id as u32, opcode::Close::new(fd).build());
|
||||
Some(rx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(rx) = result {
|
||||
rx.await
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "gone"))
|
||||
.and_then(|item| item)
|
||||
.map(|_| ())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_io<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(Option<&T>) -> R,
|
||||
{
|
||||
f(self.inner.storage.borrow().streams[self.id].io.as_ref())
|
||||
}
|
||||
|
||||
pub(crate) fn resume_read(&self) {
|
||||
let result = self.inner.storage.borrow_mut().recv(self.id, None);
|
||||
if let Some((id, op)) = result {
|
||||
self.inner.api.submit(id, op);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resume_write(&self) {
|
||||
let result = self.inner.storage.borrow_mut().send(self.id, None);
|
||||
if let Some((id, op)) = result {
|
||||
self.inner.api.submit(id, op);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pause_read(&self) {
|
||||
let mut storage = self.inner.storage.borrow_mut();
|
||||
let item = &mut storage.streams[self.id];
|
||||
|
||||
if let Some(rd_op) = item.rd_op {
|
||||
log::debug!(
|
||||
"{}: Recv to pause ({}), {:?}",
|
||||
item.context.tag(),
|
||||
self.id,
|
||||
item.fd
|
||||
);
|
||||
self.inner.api.cancel(rd_op.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for StreamCtl<T> {
|
||||
fn clone(&self) -> Self {
|
||||
self.inner.storage.borrow_mut().streams[self.id].ref_count += 1;
|
||||
Self {
|
||||
id: self.id,
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for StreamCtl<T> {
|
||||
fn drop(&mut self) {
|
||||
if let Ok(mut storage) = self.inner.storage.try_borrow_mut() {
|
||||
storage.streams[self.id].ref_count -= 1;
|
||||
if storage.streams[self.id].ref_count == 0 {
|
||||
let mut item = storage.streams.remove(self.id);
|
||||
if let Some(io) = item.io.take() {
|
||||
log::debug!(
|
||||
"{}: Close io ({}), {:?}",
|
||||
item.context.tag(),
|
||||
self.id,
|
||||
item.fd
|
||||
);
|
||||
mem::forget(io);
|
||||
|
||||
let id = storage.ops.insert(Operation::Close { tx: None });
|
||||
assert!(id < u32::MAX as usize);
|
||||
self.inner
|
||||
.api
|
||||
.submit(id as u32, opcode::Close::new(item.fd).build());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.inner.feed.borrow_mut().push(self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for StreamCtl<T> {
|
||||
#[inline]
|
||||
fn eq(&self, other: &StreamCtl<T>) -> bool {
|
||||
self.id == other.id && std::ptr::eq(&self.inner, &other.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for StreamCtl<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let storage = self.inner.storage.borrow();
|
||||
f.debug_struct("StreamCtl")
|
||||
.field("id", &self.id)
|
||||
.field("io", &storage.streams[self.id].io)
|
||||
.finish()
|
||||
}
|
||||
}
|
95
ntex-net/src/rt_uring/io.rs
Normal file
95
ntex-net/src/rt_uring/io.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
use std::{any, future::poll_fn, task::Poll};
|
||||
|
||||
use ntex_io::{
|
||||
types, Handle, IoContext, IoStream, ReadContext, ReadStatus, WriteContext, WriteStatus,
|
||||
};
|
||||
use ntex_neon::{net::TcpStream, spawn};
|
||||
|
||||
use super::driver::{StreamCtl, StreamOps};
|
||||
|
||||
impl IoStream for super::TcpStream {
|
||||
fn start(self, read: ReadContext, _: WriteContext) -> Option<Box<dyn Handle>> {
|
||||
let io = self.0;
|
||||
let context = read.context();
|
||||
let ctl = StreamOps::current().register(io, context.clone());
|
||||
let ctl2 = ctl.clone();
|
||||
spawn(async move { run(ctl, context).await }).detach();
|
||||
|
||||
Some(Box::new(HandleWrapper(ctl2)))
|
||||
}
|
||||
}
|
||||
|
||||
impl IoStream for super::UnixStream {
|
||||
fn start(self, read: ReadContext, _: WriteContext) -> Option<Box<dyn Handle>> {
|
||||
let io = self.0;
|
||||
let context = read.context();
|
||||
let ctl = StreamOps::current().register(io, context.clone());
|
||||
spawn(async move { run(ctl, context).await }).detach();
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
struct HandleWrapper(StreamCtl<TcpStream>);
|
||||
|
||||
impl Handle for HandleWrapper {
|
||||
fn query(&self, id: any::TypeId) -> Option<Box<dyn any::Any>> {
|
||||
if id == any::TypeId::of::<types::PeerAddr>() {
|
||||
let addr = self.0.with_io(|io| io.and_then(|io| io.peer_addr().ok()));
|
||||
if let Some(addr) = addr {
|
||||
return Some(Box::new(types::PeerAddr(addr)));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
enum Status {
|
||||
Shutdown,
|
||||
Terminate,
|
||||
}
|
||||
|
||||
async fn run<T>(ctl: StreamCtl<T>, context: IoContext) {
|
||||
// Handle io readiness
|
||||
let st = poll_fn(|cx| {
|
||||
let read = match context.poll_read_ready(cx) {
|
||||
Poll::Ready(ReadStatus::Ready) => {
|
||||
ctl.resume_read();
|
||||
Poll::Pending
|
||||
}
|
||||
Poll::Ready(ReadStatus::Terminate) => Poll::Ready(()),
|
||||
Poll::Pending => {
|
||||
ctl.pause_read();
|
||||
Poll::Pending
|
||||
}
|
||||
};
|
||||
|
||||
let write = match context.poll_write_ready(cx) {
|
||||
Poll::Ready(WriteStatus::Ready) => {
|
||||
log::debug!("{}: write ready", context.tag());
|
||||
ctl.resume_write();
|
||||
Poll::Pending
|
||||
}
|
||||
Poll::Ready(WriteStatus::Shutdown) => Poll::Ready(Status::Shutdown),
|
||||
Poll::Ready(WriteStatus::Terminate) => Poll::Ready(Status::Terminate),
|
||||
Poll::Pending => Poll::Pending,
|
||||
};
|
||||
|
||||
if read.is_pending() && write.is_pending() {
|
||||
Poll::Pending
|
||||
} else if write.is_ready() {
|
||||
write
|
||||
} else {
|
||||
Poll::Ready(Status::Terminate)
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
ctl.pause_read();
|
||||
ctl.resume_write();
|
||||
context.shutdown(st == Status::Shutdown).await;
|
||||
|
||||
let result = ctl.close().await;
|
||||
context.stopped(result.err());
|
||||
}
|
59
ntex-net/src/rt_uring/mod.rs
Normal file
59
ntex-net/src/rt_uring/mod.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use std::{io::Result, net, net::SocketAddr};
|
||||
|
||||
use ntex_bytes::PoolRef;
|
||||
use ntex_io::Io;
|
||||
|
||||
mod connect;
|
||||
mod driver;
|
||||
mod io;
|
||||
|
||||
/// Tcp stream wrapper for neon TcpStream
|
||||
struct TcpStream(ntex_neon::net::TcpStream);
|
||||
|
||||
/// Tcp stream wrapper for neon UnixStream
|
||||
struct UnixStream(ntex_neon::net::UnixStream);
|
||||
|
||||
/// Opens a TCP connection to a remote host.
|
||||
pub async fn tcp_connect(addr: SocketAddr) -> Result<Io> {
|
||||
let sock = connect::connect(addr).await?;
|
||||
Ok(Io::new(TcpStream(sock)))
|
||||
}
|
||||
|
||||
/// Opens a TCP connection to a remote host and use specified memory pool.
|
||||
pub async fn tcp_connect_in(addr: SocketAddr, pool: PoolRef) -> Result<Io> {
|
||||
let sock = connect::connect(addr).await?;
|
||||
Ok(Io::with_memory_pool(TcpStream(sock), pool))
|
||||
}
|
||||
|
||||
/// Opens a unix stream connection.
|
||||
pub async fn unix_connect<'a, P>(addr: P) -> Result<Io>
|
||||
where
|
||||
P: AsRef<std::path::Path> + 'a,
|
||||
{
|
||||
let sock = connect::connect_unix(addr).await?;
|
||||
Ok(Io::new(UnixStream(sock)))
|
||||
}
|
||||
|
||||
/// Opens a unix stream connection and specified memory pool.
|
||||
pub async fn unix_connect_in<'a, P>(addr: P, pool: PoolRef) -> Result<Io>
|
||||
where
|
||||
P: AsRef<std::path::Path> + 'a,
|
||||
{
|
||||
let sock = connect::connect_unix(addr).await?;
|
||||
Ok(Io::with_memory_pool(UnixStream(sock), pool))
|
||||
}
|
||||
|
||||
/// Convert std TcpStream to tokio's TcpStream
|
||||
pub fn from_tcp_stream(stream: net::TcpStream) -> Result<Io> {
|
||||
stream.set_nodelay(true)?;
|
||||
Ok(Io::new(TcpStream(ntex_neon::net::TcpStream::from_std(
|
||||
stream,
|
||||
)?)))
|
||||
}
|
||||
|
||||
/// Convert std UnixStream to tokio's UnixStream
|
||||
pub fn from_unix_stream(stream: std::os::unix::net::UnixStream) -> Result<Io> {
|
||||
Ok(Io::new(UnixStream(ntex_neon::net::UnixStream::from_std(
|
||||
stream,
|
||||
)?)))
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
# Changes
|
||||
|
||||
## [0.4.26] - 2025-03-xx
|
||||
## [0.4.26] - 2025-03-12
|
||||
|
||||
* Add "neon" runtime support
|
||||
|
||||
|
|
|
@ -154,6 +154,7 @@ impl Arbiter {
|
|||
.try_send(ArbiterCommand::Execute(Box::pin(future)));
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
/// Send a function to the Arbiter's thread. This function will be executed asynchronously.
|
||||
/// A future is created, and when resolved will contain the result of the function sent
|
||||
/// to the Arbiters thread.
|
||||
|
|
|
@ -259,7 +259,7 @@ mod neon {
|
|||
/// completes.
|
||||
pub fn block_on<F: Future<Output = ()>>(fut: F) {
|
||||
log::info!(
|
||||
"Starting compio runtime, driver {:?}",
|
||||
"Starting neon runtime, driver {:?}",
|
||||
ntex_neon::driver::DriverType::current()
|
||||
);
|
||||
let rt = Runtime::new().unwrap();
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# Changes
|
||||
|
||||
## [2.12.0] - 2025-03-10
|
||||
## [2.12.0] - 2025-03-12
|
||||
|
||||
* Add "ntex-runtime" support
|
||||
* Add neon runtime support
|
||||
|
||||
* Drop glommio support
|
||||
|
||||
|
|
|
@ -51,6 +51,9 @@ compio = ["ntex-net/compio"]
|
|||
# neon runtime
|
||||
neon = ["ntex-net/neon"]
|
||||
|
||||
# neon runtime
|
||||
neon-uring = ["ntex-net/neon-uring"]
|
||||
|
||||
# websocket support
|
||||
ws = ["dep:sha-1"]
|
||||
|
||||
|
|
|
@ -92,6 +92,7 @@ async fn test_run() {
|
|||
})
|
||||
})
|
||||
.unwrap()
|
||||
.set_tag("test", "SRV")
|
||||
.run();
|
||||
let _ = tx.send((srv, ntex::rt::System::current()));
|
||||
Ok(())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue