Compare commits

..

No commits in common. "master" and "rt-v0.4.29" have entirely different histories.

40 changed files with 463 additions and 724 deletions

View file

@ -46,12 +46,7 @@ ntex-compio = { path = "ntex-compio" }
ntex-tokio = { path = "ntex-tokio" }
[workspace.dependencies]
ntex-polling = "3.7.4"
async-channel = "2"
async-task = "4.5.0"
atomic-waker = "1.1"
core_affinity = "0.8"
bitflags = "2"
cfg_aliases = "0.2.1"
cfg-if = "1.0.0"
@ -62,8 +57,7 @@ fxhash = "0.2"
libc = "0.2.164"
log = "0.4"
io-uring = "0.7.4"
oneshot = "0.1"
polling = "3.7.4"
polling = "3.3.0"
nohash-hasher = "0.2.0"
scoped-tls = "1.0.1"
slab = "0.4.9"

View file

@ -965,7 +965,7 @@ impl From<String> for Bytes {
fn from(src: String) -> Bytes {
if src.is_empty() {
Bytes::new()
} else if src.len() <= INLINE_CAP {
} else if src.bytes().len() <= INLINE_CAP {
Bytes {
inner: Inner::from_slice_inline(src.as_bytes()),
}

View file

@ -104,7 +104,6 @@ fn clone_mut() {
assert_eq!(buf1, "hello");
}
#[allow(clippy::sliced_string_as_bytes)]
#[test]
fn fmt_write() {
use std::fmt::Write;

View file

@ -1,6 +1,6 @@
[package]
name = "ntex-io"
version = "2.11.2"
version = "2.11.1"
authors = ["ntex contributors <team@ntex.rs>"]
description = "Utilities for encoding and decoding frames"
keywords = ["network", "framework", "async", "futures"]
@ -28,3 +28,4 @@ pin-project-lite = "0.2"
[dev-dependencies]
ntex = "2"
rand = "0.8"
env_logger = "0.11"

View file

@ -543,7 +543,7 @@ where
if self.flags.contains(Flags::KA_ENABLED)
&& !self.flags.contains(Flags::KA_TIMEOUT)
{
log::trace!(
log::debug!(
"{}: Start keep-alive timer {:?}",
self.shared.io.tag(),
self.cfg.keepalive_timeout()

View file

@ -98,19 +98,17 @@ impl IoState {
}
pub(super) fn io_stopped(&self, err: Option<io::Error>) {
if !self.flags.get().contains(Flags::IO_STOPPED) {
if err.is_some() {
self.error.set(err);
}
self.read_task.wake();
self.write_task.wake();
self.dispatch_task.wake();
self.notify_disconnect();
self.handle.take();
self.insert_flags(
Flags::IO_STOPPED | Flags::IO_STOPPING | Flags::IO_STOPPING_FILTERS,
);
if err.is_some() {
self.error.set(err);
}
self.read_task.wake();
self.write_task.wake();
self.dispatch_task.wake();
self.notify_disconnect();
self.handle.take();
self.insert_flags(
Flags::IO_STOPPED | Flags::IO_STOPPING | Flags::IO_STOPPING_FILTERS,
);
}
/// Gracefully shutdown read and write io tasks
@ -535,8 +533,8 @@ impl<F> Io<F> {
} else {
match self.poll_read_ready(cx) {
Poll::Pending | Poll::Ready(Ok(Some(()))) => {
if log::log_enabled!(log::Level::Trace) && decoded.remains != 0 {
log::trace!(
if log::log_enabled!(log::Level::Debug) && decoded.remains != 0 {
log::debug!(
"{}: Not enough data to decode next frame",
self.tag()
);

View file

@ -246,12 +246,12 @@ impl IoRef {
if cur_hnd.is_set() {
let hnd = timer::update(cur_hnd, timeout, self);
if hnd != cur_hnd {
log::trace!("{}: Update timer {:?}", self.tag(), timeout);
log::debug!("{}: Update timer {:?}", self.tag(), timeout);
self.0.timeout.set(hnd);
}
hnd
} else {
log::trace!("{}: Start timer {:?}", self.tag(), timeout);
log::debug!("{}: Start timer {:?}", self.tag(), timeout);
let hnd = timer::register(timeout, self);
self.0.timeout.set(hnd);
hnd
@ -270,7 +270,7 @@ impl IoRef {
pub fn stop_timer(&self) {
let hnd = self.0.timeout.get();
if hnd.is_set() {
log::trace!("{}: Stop timer", self.tag());
log::debug!("{}: Stop timer", self.tag());
self.0.timeout.set(timer::TimerHandle::ZERO);
timer::unregister(hnd, self)
}

View file

@ -395,12 +395,6 @@ impl IoContext {
self.0 .0.io_stopped(e);
}
#[inline]
/// Check if Io stopped
pub fn is_stopped(&self) -> bool {
self.0.flags().is_stopped()
}
/// Wait when io get closed or preparing for close
pub async fn shutdown(&self, flush_buf: bool) {
let st = &self.0 .0;
@ -674,6 +668,7 @@ impl IoContext {
// 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())
}
@ -683,6 +678,7 @@ impl IoContext {
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)
}
@ -739,31 +735,76 @@ impl IoContext {
false
}
pub fn is_write_ready(&self) -> bool {
if let Some(waker) = self.0 .0.write_task.take() {
let ready = self
.0
.filter()
.poll_write_ready(&mut Context::from_waker(&waker));
if !matches!(
ready,
Poll::Ready(WriteStatus::Ready | WriteStatus::Shutdown)
) {
return true;
}
}
false
}
pub fn with_read_buf<F>(&self, f: F) -> Poll<()>
where
F: FnOnce(&mut BytesVec, usize, usize) -> (usize, Poll<io::Result<()>>),
F: FnOnce(&mut BytesVec) -> Poll<io::Result<usize>>,
{
let inner = &self.0 .0;
let (hw, lw) = self.0.memory_pool().read_params().unpack();
let (nbytes, result) = inner.buffer.with_read_source(&self.0, |buf| f(buf, hw, lw));
let result = inner.buffer.with_read_source(&self.0, |buf| {
// make sure we've got room
let remaining = buf.remaining_mut();
if remaining < lw {
buf.reserve(hw - remaining);
}
f(buf)
});
// handle buffer changes
let st_res = if nbytes > 0 {
let filter = self.0.filter();
match filter.process_read_buf(&self.0, &inner.buffer, 0, nbytes) {
Ok(status) => {
if status.nbytes > 0 {
// dest buffer has new data, wake up dispatcher
if inner.buffer.read_destination_size() >= hw {
match result {
Poll::Ready(Ok(0)) => {
inner.io_stopped(None);
Poll::Ready(())
}
Poll::Ready(Ok(nbytes)) => {
let filter = self.0.filter();
let _ = 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!(
"{}: Io read buffer is too large {}, enable read back-pressure",
"{}: New {} bytes available, wakeup dispatcher",
self.0.tag(),
nbytes
);
inner.insert_flags(Flags::BUF_R_READY | Flags::BUF_R_FULL);
inner.dispatch_task.wake();
} 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
@ -771,70 +812,36 @@ impl IoContext {
// 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();
}
}
log::trace!(
"{}: New {} bytes available, wakeup dispatcher",
self.0.tag(),
nbytes
);
// while reading, filter wrote some data
// in that case filters need to process write buffers
// and potentialy wake write task
if status.need_write {
filter.process_write_buf(&self.0, &inner.buffer, 0)
} else {
Ok(())
}
})
.map_err(|err| {
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 {
filter.process_write_buf(&self.0, &inner.buffer, 0)
} else {
Ok(())
}
}
Err(err) => {
inner.insert_flags(Flags::BUF_R_READY);
Err(err)
}
}
} else {
Ok(())
};
match result {
Poll::Ready(Ok(_)) => {
if let Err(e) = st_res {
inner.io_stopped(Some(e));
Poll::Ready(())
} else if nbytes == 0 {
inner.io_stopped(None);
Poll::Ready(())
} else {
Poll::Pending
}
inner.io_stopped(Some(err));
inner.insert_flags(Flags::BUF_R_READY);
});
Poll::Pending
}
Poll::Ready(Err(e)) => {
inner.io_stopped(Some(e));
Poll::Ready(())
}
Poll::Pending => {
if let Err(e) = st_res {
inner.io_stopped(Some(e));
Poll::Ready(())
} else {
self.shutdown_filters();
Poll::Pending
}
self.shutdown_filters();
Poll::Pending
}
}
}

View file

@ -18,3 +18,4 @@ proc-macro2 = "^1"
[dev-dependencies]
ntex = "2"
futures = "0.3"
env_logger = "0.11"

View file

@ -1,17 +1,5 @@
# Changes
## [2.5.11] - 2025-04-05
* Various improvements for polling driver
## [2.5.10] - 2025-03-28
* Better closed sockets handling
## [2.5.9] - 2025-03-27
* Handle closed sockets
## [2.5.8] - 2025-03-25
* Update neon runtime

View file

@ -1,6 +1,6 @@
[package]
name = "ntex-net"
version = "2.5.11"
version = "2.5.8"
authors = ["ntex contributors <team@ntex.rs>"]
description = "ntexwork utils for ntex framework"
keywords = ["network", "framework", "async", "futures"]
@ -27,19 +27,20 @@ compio = ["ntex-rt/compio", "ntex-compio"]
# neon runtime
neon = ["ntex-rt/neon", "ntex-neon", "slab", "socket2"]
polling = ["ntex-neon/polling", "dep:polling", "socket2"]
io-uring = ["ntex-neon/io-uring", "dep:io-uring", "socket2"]
[dependencies]
ntex-service = "3.3"
ntex-bytes = "0.1"
ntex-http = "0.1"
ntex-io = "2.11.2"
ntex-rt = "0.4.30"
ntex-io = "2.11.1"
ntex-rt = "0.4.25"
ntex-util = "2.5"
ntex-tokio = { version = "0.5.3", optional = true }
ntex-compio = { version = "0.2.4", optional = true }
ntex-neon = { version = "0.1.17", optional = true }
ntex-neon = { version = "0.1.11", optional = true }
bitflags = { workspace = true }
cfg-if = { workspace = true }
@ -52,7 +53,8 @@ thiserror = { workspace = true }
# Linux specific dependencies
[target.'cfg(target_os = "linux")'.dependencies]
io-uring = { workspace = true, optional = true }
polling = { workspace = true, optional = true }
[dev-dependencies]
ntex = "2"
oneshot = "0.1"
env_logger = "0.11"

View file

@ -14,14 +14,14 @@ cfg_if::cfg_if! {
mod rt_impl;
pub use self::rt_impl::{
from_tcp_stream, from_unix_stream, tcp_connect, tcp_connect_in, unix_connect,
unix_connect_in, active_stream_ops
unix_connect_in,
};
} else if #[cfg(all(unix, feature = "neon"))] {
#[path = "rt_polling/mod.rs"]
mod rt_impl;
pub use self::rt_impl::{
from_tcp_stream, from_unix_stream, tcp_connect, tcp_connect_in, unix_connect,
unix_connect_in, active_stream_ops
unix_connect_in,
};
} else {
pub use self::compat::*;

View file

@ -34,7 +34,7 @@ impl ConnectOps {
pub(crate) fn current() -> Self {
Runtime::value(|rt| {
let mut inner = None;
rt.register_handler(|api| {
rt.driver().register(|api| {
let ops = Rc::new(ConnectOpsInner {
api,
connects: RefCell::new(Slab::new()),
@ -62,16 +62,17 @@ impl ConnectOps {
let item = Item { fd, sender };
let id = self.0.connects.borrow_mut().insert(item);
self.0.api.attach(fd, id as u32, Event::writable(0));
self.0.api.attach(fd, id as u32, Some(Event::writable(0)));
Ok(id)
}
}
impl Handler for ConnectOpsBatcher {
fn event(&mut self, id: usize, event: Event) {
log::trace!("connect-fd is readable {:?}", id);
log::debug!("connect-fd is readable {:?}", id);
let mut connects = self.inner.connects.borrow_mut();
if connects.contains(id) {
let item = connects.remove(id);
if event.writable {

View file

@ -1,5 +1,5 @@
use std::os::fd::RawFd;
use std::{cell::Cell, cell::RefCell, future::Future, io, rc::Rc, task::Poll};
use std::os::fd::{AsRawFd, RawFd};
use std::{cell::Cell, cell::RefCell, future::Future, io, rc::Rc, task, task::Poll};
use ntex_neon::driver::{DriverApi, Event, Handler};
use ntex_neon::{syscall, Runtime};
@ -8,45 +8,51 @@ use slab::Slab;
use ntex_bytes::BufMut;
use ntex_io::IoContext;
pub(crate) struct StreamCtl {
pub(crate) struct StreamCtl<T> {
id: u32,
inner: Rc<StreamOpsInner>,
inner: Rc<StreamOpsInner<T>>,
}
bitflags::bitflags! {
#[derive(Copy, Clone, Debug)]
struct Flags: u8 {
const RD = 0b0000_0001;
const WR = 0b0000_0010;
const CLOSED = 0b0001_0000;
const RD = 0b0000_0001;
const WR = 0b0000_0010;
}
}
struct StreamItem {
struct StreamItem<T> {
io: Option<T>,
fd: RawFd,
flags: Flags,
ref_count: u16,
context: IoContext,
}
pub(crate) struct StreamOps(Rc<StreamOpsInner>);
pub(crate) struct StreamOps<T>(Rc<StreamOpsInner<T>>);
struct StreamOpsHandler {
inner: Rc<StreamOpsInner>,
struct StreamOpsHandler<T> {
inner: Rc<StreamOpsInner<T>>,
}
struct StreamOpsInner {
struct StreamOpsInner<T> {
api: DriverApi,
delayd_drop: Cell<bool>,
feed: RefCell<Vec<u32>>,
streams: Cell<Option<Box<Slab<StreamItem>>>>,
streams: Cell<Option<Box<Slab<StreamItem<T>>>>>,
}
impl StreamOps {
impl<T> StreamItem<T> {
fn tag(&self) -> &'static str {
self.context.tag()
}
}
impl<T: AsRawFd + 'static> StreamOps<T> {
pub(crate) fn current() -> Self {
Runtime::value(|rt| {
let mut inner = None;
rt.register_handler(|api| {
rt.driver().register(|api| {
let ops = Rc::new(StreamOpsInner {
api,
feed: RefCell::new(Vec::new()),
@ -61,15 +67,13 @@ impl StreamOps {
})
}
pub(crate) fn active_ops() -> usize {
Self::current().0.with(|streams| streams.len())
}
pub(crate) fn register(&self, fd: RawFd, context: IoContext) -> StreamCtl {
pub(crate) fn register(&self, io: T, context: IoContext) -> StreamCtl<T> {
let fd = io.as_raw_fd();
let stream = self.0.with(move |streams| {
let item = StreamItem {
fd,
context,
io: Some(io),
ref_count: 1,
flags: Flags::empty(),
};
@ -79,75 +83,81 @@ impl StreamOps {
}
});
self.0
.api
.attach(fd, stream.id, Event::new(0, false, false));
self.0.api.attach(
fd,
stream.id,
Some(Event::new(0, false, false).with_interrupt()),
);
stream
}
}
impl Clone for StreamOps {
impl<T> Clone for StreamOps<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl Handler for StreamOpsHandler {
impl<T> Handler for StreamOpsHandler<T> {
fn event(&mut self, id: usize, ev: Event) {
log::debug!("FD event {:?} event: {:?}", id, ev);
self.inner.with(|streams| {
if !streams.contains(id) {
return;
}
let item = &mut streams[id];
if item.flags.contains(Flags::CLOSED) {
// handle HUP
if ev.is_interrupt() {
item.context.stopped(None);
if item.io.take().is_some() {
close(id as u32, item.fd, &self.inner.api);
}
return;
}
log::trace!("{}: Event ({:?}): {:?}", item.tag(), item.fd, ev);
let mut renew_ev = Event::new(0, false, false).with_interrupt();
let mut flags = item.flags;
let mut renew = Event::new(0, false, false);
if ev.readable {
if item
.read(&mut flags, id as u32, &self.inner.api)
.is_pending()
&& item.can_read()
{
renew.readable = true;
flags.insert(Flags::RD);
let res = item.context.with_read_buf(|buf| {
let chunk = buf.chunk_mut();
let result = task::ready!(syscall!(
break libc::read(item.fd, chunk.as_mut_ptr() as _, chunk.len())
));
if let Ok(size) = result {
log::debug!("{}: data {:?}, s: {:?}", item.tag(), item.fd, size);
unsafe { buf.advance_mut(size) };
}
Poll::Ready(result)
});
if res.is_pending() && item.context.is_read_ready() {
renew_ev.readable = true;
item.flags.insert(Flags::RD);
} else {
flags.remove(Flags::RD);
item.flags.remove(Flags::RD);
}
} else if flags.contains(Flags::RD) {
renew.readable = true;
} else if item.flags.contains(Flags::RD) {
renew_ev.readable = true;
}
if ev.writable {
let result = item.context.with_write_buf(|buf| {
log::trace!("{}: write {:?} s: {:?}", item.tag(), item.fd, buf.len());
log::debug!("{}: write {:?} s: {:?}", item.tag(), item.fd, buf.len());
syscall!(break libc::write(item.fd, buf[..].as_ptr() as _, buf.len()))
});
if result.is_pending() {
renew.writable = true;
flags.insert(Flags::WR);
renew_ev.writable = true;
item.flags.insert(Flags::WR);
} else {
flags.remove(Flags::WR);
item.flags.remove(Flags::WR);
}
} else if flags.contains(Flags::WR) {
renew.writable = true;
}
item.flags = flags;
// handle HUP
if ev.is_interrupt() {
item.close(id as u32, &self.inner.api, None, false);
return;
} else if item.flags.contains(Flags::WR) {
renew_ev.writable = true;
}
// register Event in driver
if !item.flags.contains(Flags::CLOSED) {
self.inner.api.modify(item.fd, id as u32, renew);
}
self.inner.api.modify(item.fd, id as u32, renew_ev);
// delayed drops
if self.inner.delayd_drop.get() {
@ -155,14 +165,17 @@ impl Handler for StreamOpsHandler {
let item = &mut streams[id as usize];
item.ref_count -= 1;
if item.ref_count == 0 {
let mut item = streams.remove(id as usize);
log::trace!(
"{}: Drop ({:?}), flags: {:?}",
let item = streams.remove(id as usize);
log::debug!(
"{}: Drop ({}), {:?}, has-io: {}",
item.tag(),
id,
item.fd,
item.flags
item.io.is_some()
);
item.close(id, &self.inner.api, None, true);
if item.io.is_some() {
close(id, item.fd, &self.inner.api);
}
}
}
self.inner.delayd_drop.set(false);
@ -173,22 +186,20 @@ impl Handler for StreamOpsHandler {
fn error(&mut self, id: usize, err: io::Error) {
self.inner.with(|streams| {
if let Some(item) = streams.get_mut(id) {
log::trace!(
"{}: FD is failed ({:?}) err: {:?}",
item.tag(),
item.fd,
err
);
item.close(id as u32, &self.inner.api, Some(err), false);
log::debug!("FD is failed ({}) {:?}, err: {:?}", id, item.fd, err);
item.context.stopped(Some(err));
if item.io.take().is_some() {
close(id as u32, item.fd, &self.inner.api);
}
}
})
}
}
impl StreamOpsInner {
impl<T> StreamOpsInner<T> {
fn with<F, R>(&self, f: F) -> R
where
F: FnOnce(&mut Slab<StreamItem>) -> R,
F: FnOnce(&mut Slab<StreamItem<T>>) -> R,
{
let mut streams = self.streams.take().unwrap();
let result = f(&mut streams);
@ -197,12 +208,27 @@ impl StreamOpsInner {
}
}
impl StreamCtl {
fn close(id: u32, fd: RawFd, api: &DriverApi) -> ntex_rt::JoinHandle<io::Result<i32>> {
api.detach(fd, id);
ntex_rt::spawn_blocking(move || {
syscall!(libc::shutdown(fd, libc::SHUT_RDWR))?;
syscall!(libc::close(fd))
})
}
impl<T> StreamCtl<T> {
pub(crate) fn close(self) -> impl Future<Output = io::Result<()>> {
let id = self.id as usize;
let fut = self
let (io, fd) = self
.inner
.with(|streams| streams[id].close(self.id, &self.inner.api, None, true));
.with(|streams| (streams[id].io.take(), streams[id].fd));
let fut = if let Some(io) = io {
log::debug!("Closing ({}), {:?}", id, fd);
std::mem::forget(io);
Some(close(self.id, fd, &self.inner.api))
} else {
None
};
async move {
if let Some(fut) = fut {
fut.await
@ -213,44 +239,66 @@ impl StreamCtl {
}
}
pub(crate) fn modify(&self, rd: bool, wr: bool) -> bool {
pub(crate) fn with_io<F, R>(&self, f: F) -> R
where
F: FnOnce(Option<&T>) -> R,
{
self.inner
.with(|streams| f(streams[self.id as usize].io.as_ref()))
}
pub(crate) fn modify(&self, rd: bool, wr: bool) {
self.inner.with(|streams| {
let item = &mut streams[self.id as usize];
if item.flags.contains(Flags::CLOSED) {
return false;
}
log::trace!(
"{}: Modify interest ({:?}) rd: {:?}, wr: {:?}",
log::debug!(
"{}: Modify interest ({}), {:?} rd: {:?}, wr: {:?}",
item.tag(),
self.id,
item.fd,
rd,
wr
);
let mut flags = item.flags;
let mut event = Event::new(0, false, false);
let mut event = Event::new(0, false, false).with_interrupt();
if rd {
if flags.contains(Flags::RD) {
if item.flags.contains(Flags::RD) {
event.readable = true;
} else if item.read(&mut flags, self.id, &self.inner.api).is_pending() {
event.readable = true;
flags.insert(Flags::RD);
} else {
let res = item.context.with_read_buf(|buf| {
let chunk = buf.chunk_mut();
let result = task::ready!(syscall!(
break libc::read(item.fd, chunk.as_mut_ptr() as _, chunk.len())
));
if let Ok(size) = result {
log::debug!(
"{}: read {:?}, s: {:?}",
item.tag(),
item.fd,
size
);
unsafe { buf.advance_mut(size) };
}
Poll::Ready(result)
});
if res.is_pending() && item.context.is_read_ready() {
event.readable = true;
item.flags.insert(Flags::RD);
}
}
} else if flags.contains(Flags::RD) {
flags.remove(Flags::RD);
}
if wr {
if flags.contains(Flags::WR) {
if item.flags.contains(Flags::WR) {
event.writable = true;
} else {
let result = item.context.with_write_buf(|buf| {
log::trace!(
log::debug!(
"{}: Writing ({}), buf: {:?}",
item.tag(),
item.fd,
self.id,
buf.len()
);
syscall!(
@ -260,25 +308,17 @@ impl StreamCtl {
if result.is_pending() {
event.writable = true;
flags.insert(Flags::WR);
item.flags.insert(Flags::WR);
}
}
} else if flags.contains(Flags::WR) {
flags.remove(Flags::WR);
}
item.flags = flags;
if !flags.contains(Flags::CLOSED) {
self.inner.api.modify(item.fd, self.id, event);
true
} else {
false
}
self.inner.api.modify(item.fd, self.id, event);
})
}
}
impl Clone for StreamCtl {
impl<T> Clone for StreamCtl<T> {
fn clone(&self) -> Self {
self.inner.with(|streams| {
streams[self.id as usize].ref_count += 1;
@ -290,20 +330,23 @@ impl Clone for StreamCtl {
}
}
impl Drop for StreamCtl {
impl<T> Drop for StreamCtl<T> {
fn drop(&mut self) {
if let Some(mut streams) = self.inner.streams.take() {
let id = self.id as usize;
streams[id].ref_count -= 1;
if streams[id].ref_count == 0 {
let mut item = streams.remove(id);
log::trace!(
"{}: Drop io ({:?}), flags: {:?}",
let item = streams.remove(id);
log::debug!(
"{}: Drop io ({}), {:?}, has-io: {}",
item.tag(),
self.id,
item.fd,
item.flags
item.io.is_some()
);
item.close(self.id, &self.inner.api, None, true);
if item.io.is_some() {
close(self.id, item.fd, &self.inner.api);
}
}
self.inner.streams.set(Some(streams));
} else {
@ -312,103 +355,3 @@ impl Drop for StreamCtl {
}
}
}
impl StreamItem {
fn tag(&self) -> &'static str {
self.context.tag()
}
fn can_read(&self) -> bool {
self.context.is_read_ready()
}
fn read(&mut self, flags: &mut Flags, id: u32, api: &DriverApi) -> Poll<()> {
let mut close = false;
let result = self.context.with_read_buf(|buf, hw, lw| {
let mut total = 0;
loop {
// make sure we've got room
if buf.remaining_mut() < lw {
buf.reserve(hw);
}
let chunk = buf.chunk_mut();
let chunk_len = chunk.len();
let chunk_ptr = chunk.as_mut_ptr();
let res = syscall!(break libc::read(self.fd, chunk_ptr as _, chunk_len));
if let Poll::Ready(Ok(size)) = res {
unsafe { buf.advance_mut(size) };
total += size;
if size == chunk_len {
continue;
}
}
log::trace!(
"{}: Read fd ({:?}), size: {:?}, cap: {:?}, result: {:?}",
self.tag(),
self.fd,
total,
buf.remaining_mut(),
res
);
return match res {
Poll::Ready(Err(err)) => {
close = true;
(total, Poll::Ready(Err(err)))
}
Poll::Ready(Ok(size)) => {
if size == 0 {
close = true;
}
(total, Poll::Ready(Ok(())))
}
Poll::Pending => (total, Poll::Pending),
};
}
});
if close {
flags.insert(Flags::CLOSED);
self.close(id, api, None, false);
}
result
}
fn close(
&mut self,
id: u32,
api: &DriverApi,
error: Option<io::Error>,
shutdown: bool,
) -> Option<ntex_rt::JoinHandle<io::Result<i32>>> {
if !self.flags.contains(Flags::CLOSED) {
log::trace!(
"{}: Closing ({}) sh: {:?}, flags: {:?}, ctx: {:?}",
self.tag(),
self.fd,
shutdown,
self.flags,
self.context.flags()
);
self.flags.insert(Flags::CLOSED);
if !self.context.is_stopped() {
self.context.stopped(error);
}
let fd = self.fd;
api.detach(fd, id);
Some(ntex_rt::spawn_blocking(move || {
if shutdown {
let _ = syscall!(libc::shutdown(fd, libc::SHUT_RDWR));
}
syscall!(libc::close(fd))
}))
} else {
None
}
}
}

View file

@ -1,4 +1,4 @@
use std::{any, future::poll_fn, mem, os::fd::AsRawFd, task::Poll};
use std::{any, future::poll_fn, task::Poll};
use ntex_io::{
types, Handle, IoContext, IoStream, ReadContext, ReadStatus, WriteContext, WriteStatus,
@ -12,10 +12,11 @@ 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.as_raw_fd(), context.clone());
let ctl = StreamOps::current().register(io, context.clone());
let ctl2 = ctl.clone();
spawn(async move { run(ctl, context).await });
Some(Box::new(HandleWrapper(Some(io))))
Some(Box::new(HandleWrapper(ctl2)))
}
}
@ -23,20 +24,19 @@ 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.as_raw_fd(), context.clone());
let ctl = StreamOps::current().register(io, context.clone());
spawn(async move { run(ctl, context).await });
mem::forget(io);
None
}
}
struct HandleWrapper(Option<Socket>);
struct HandleWrapper(StreamCtl<Socket>);
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.as_ref().unwrap().peer_addr().ok();
let addr = self.0.with_io(|io| io.and_then(|io| io.peer_addr().ok()));
if let Some(addr) = addr.and_then(|addr| addr.as_socket()) {
return Some(Box::new(types::PeerAddr(addr)));
}
@ -45,19 +45,13 @@ impl Handle for HandleWrapper {
}
}
impl Drop for HandleWrapper {
fn drop(&mut self) {
mem::forget(self.0.take());
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum Status {
Shutdown,
Terminate,
}
async fn run(ctl: StreamCtl, context: IoContext) {
async fn run<T>(ctl: StreamCtl<T>, context: IoContext) {
// Handle io read readiness
let st = poll_fn(|cx| {
let mut modify = false;
@ -87,8 +81,8 @@ async fn run(ctl: StreamCtl, context: IoContext) {
Poll::Pending => Poll::Pending,
};
if modify && !ctl.modify(readable, writable) {
return Poll::Ready(Status::Terminate);
if modify {
ctl.modify(readable, writable);
}
if read.is_pending() && write.is_pending() {
@ -101,10 +95,7 @@ async fn run(ctl: StreamCtl, context: IoContext) {
})
.await;
if st != Status::Terminate && ctl.modify(false, true) {
context.shutdown(st == Status::Shutdown).await;
}
if !context.is_stopped() {
context.stopped(ctl.close().await.err());
}
ctl.modify(false, true);
context.shutdown(st == Status::Shutdown).await;
context.stopped(ctl.close().await.err());
}

View file

@ -67,68 +67,3 @@ pub fn from_unix_stream(stream: std::os::unix::net::UnixStream) -> Result<Io> {
Socket::from(stream),
)?)))
}
#[doc(hidden)]
/// Get number of active Io objects
pub fn active_stream_ops() -> usize {
self::driver::StreamOps::active_ops()
}
#[cfg(all(target_os = "linux", feature = "neon"))]
#[cfg(test)]
mod tests {
use ntex::{io::Io, time::sleep, time::Millis, util::PoolId};
use std::sync::{Arc, Mutex};
use crate::connect::Connect;
const DATA: &[u8] = b"Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World";
// #[ntex::test]
async fn idle_disconnect() {
PoolId::P5.set_read_params(24, 12);
let (tx, rx) = ::oneshot::channel();
let tx = Arc::new(Mutex::new(Some(tx)));
let server = ntex::server::test_server(move || {
let tx = tx.clone();
ntex_service::fn_service(move |io: Io<_>| {
tx.lock().unwrap().take().unwrap().send(()).unwrap();
async move {
io.write(DATA).unwrap();
sleep(Millis(250)).await;
io.close();
Ok::<_, ()>(())
}
})
});
let msg = Connect::new(server.addr());
let io = crate::connect::connect(msg).await.unwrap();
io.set_memory_pool(PoolId::P5.into());
rx.await.unwrap();
io.on_disconnect().await;
}
}

View file

@ -31,7 +31,7 @@ impl ConnectOps {
pub(crate) fn current() -> Self {
Runtime::value(|rt| {
let mut inner = None;
rt.register_handler(|api| {
rt.driver().register(|api| {
if !api.is_supported(opcode::Connect::CODE) {
panic!("opcode::Connect is required for io-uring support");
}
@ -72,14 +72,14 @@ impl ConnectOps {
impl Handler for ConnectOpsHandler {
fn canceled(&mut self, user_data: usize) {
log::trace!("connect-op is canceled {:?}", user_data);
log::debug!("connect-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>) {
let (addr, tx) = self.inner.ops.borrow_mut().remove(user_data);
log::trace!(
log::debug!(
"connect-op is completed {:?} result: {:?}, addr: {:?}",
user_data,
result,

View file

@ -33,12 +33,6 @@ struct StreamItem<T> {
wr_op: Option<NonZeroU32>,
}
impl<T> StreamItem<T> {
fn tag(&self) -> &'static str {
self.context.tag()
}
}
enum Operation {
Recv {
id: usize,
@ -77,7 +71,7 @@ impl<T: os::fd::AsRawFd + 'static> StreamOps<T> {
pub(crate) fn current() -> Self {
Runtime::value(|rt| {
let mut inner = None;
rt.register_handler(|api| {
rt.driver().register(|api| {
if !api.is_supported(opcode::Recv::CODE) {
panic!("opcode::Recv is required for io-uring support");
}
@ -124,10 +118,6 @@ impl<T: os::fd::AsRawFd + 'static> StreamOps<T> {
}
}
pub(crate) fn active_ops() -> usize {
Self::current().with(|st| st.streams.len())
}
fn with<F, R>(&self, f: F) -> R
where
F: FnOnce(&mut StreamOpsStorage<T>) -> R,
@ -148,7 +138,7 @@ impl<T> Handler for StreamOpsHandler<T> {
match storage.ops.remove(user_data) {
Operation::Recv { id, buf, context } => {
log::trace!("{}: Recv canceled {:?}", context.tag(), id);
log::debug!("{}: Recv canceled {:?}", context.tag(), id);
context.release_read_buf(buf);
if let Some(item) = storage.streams.get_mut(id) {
item.rd_op.take();
@ -164,7 +154,7 @@ impl<T> Handler for StreamOpsHandler<T> {
}
}
Operation::Send { id, buf, context } => {
log::trace!("{}: Send canceled: {:?}", context.tag(), id);
log::debug!("{}: Send canceled: {:?}", context.tag(), id);
context.release_write_buf(buf);
if let Some(item) = storage.streams.get_mut(id) {
item.wr_op.take();
@ -200,7 +190,7 @@ impl<T> Handler for StreamOpsHandler<T> {
// reset op reference
if let Some(item) = storage.streams.get_mut(id) {
log::trace!(
log::debug!(
"{}: Recv completed {:?}, res: {:?}, buf({})",
context.tag(),
item.fd,
@ -217,13 +207,13 @@ impl<T> Handler for StreamOpsHandler<T> {
self.inner.api.submit(id, op);
}
} else {
log::trace!("{}: Recv to pause", tag);
log::debug!("{}: Recv to pause", tag);
}
}
Operation::Send { id, buf, context } => {
// reset op reference
let fd = if let Some(item) = storage.streams.get_mut(id) {
log::trace!(
log::debug!(
"{}: Send completed: {:?}, res: {:?}, buf({})",
context.tag(),
item.fd,
@ -239,7 +229,7 @@ impl<T> Handler for StreamOpsHandler<T> {
// set read buf
let result = context.set_write_buf(result.map(|size| size as usize), buf);
if result.is_pending() {
log::trace!("{}: Need to send more: {:?}", context.tag(), fd);
log::debug!("{}: Need to send more: {:?}", context.tag(), fd);
if let Some((id, op)) = storage.send(id, Some(context)) {
self.inner.api.submit(id, op);
}
@ -259,7 +249,7 @@ impl<T> Handler for StreamOpsHandler<T> {
if storage.streams[id].ref_count == 0 {
let mut item = storage.streams.remove(id);
log::trace!("{}: Drop io ({}), {:?}", item.tag(), id, item.fd);
log::debug!("{}: Drop io ({}), {:?}", item.context.tag(), id, item.fd);
if let Some(io) = item.io.take() {
mem::forget(io);
@ -281,9 +271,9 @@ impl<T> StreamOpsStorage<T> {
if item.rd_op.is_none() {
if let Poll::Ready(mut buf) = item.context.get_read_buf() {
log::trace!(
log::debug!(
"{}: Recv resume ({}), {:?} rem: {:?}",
item.tag(),
item.context.tag(),
id,
item.fd,
buf.remaining_mut()
@ -314,9 +304,9 @@ impl<T> StreamOpsStorage<T> {
if item.wr_op.is_none() {
if let Poll::Ready(buf) = item.context.get_write_buf() {
log::trace!(
log::debug!(
"{}: Send resume ({}), {:?} len: {:?}",
item.tag(),
item.context.tag(),
id,
item.fd,
buf.len()
@ -406,7 +396,12 @@ impl<T> StreamCtl<T> {
if let Some(rd_op) = item.rd_op {
if !item.flags.contains(Flags::RD_CANCELING) {
log::trace!("{}: Recv to pause ({}), {:?}", item.tag(), self.id, item.fd);
log::debug!(
"{}: Recv to pause ({}), {:?}",
item.context.tag(),
self.id,
item.fd
);
item.flags.insert(Flags::RD_CANCELING);
self.inner.api.cancel(rd_op.get());
}
@ -431,7 +426,12 @@ impl<T> Drop for StreamCtl<T> {
if storage.streams[self.id].ref_count == 0 {
let mut item = storage.streams.remove(self.id);
if let Some(io) = item.io.take() {
log::trace!("{}: Close io ({}), {:?}", item.tag(), self.id, item.fd);
log::debug!(
"{}: Close io ({}), {:?}",
item.context.tag(),
self.id,
item.fd
);
mem::forget(io);
let id = storage.ops.insert(Operation::Close { tx: None });

View file

@ -64,9 +64,3 @@ pub fn from_unix_stream(stream: std::os::unix::net::UnixStream) -> Result<Io> {
Socket::from(stream),
)?)))
}
#[doc(hidden)]
/// Get number of active Io objects
pub fn active_stream_ops() -> usize {
self::driver::StreamOps::<socket2::Socket>::active_ops()
}

View file

@ -1,6 +1,6 @@
[package]
name = "ntex-rt"
version = "0.4.30"
version = "0.4.29"
authors = ["ntex contributors <team@ntex.rs>"]
description = "ntex runtime"
keywords = ["network", "framework", "async", "futures"]
@ -42,4 +42,7 @@ tok-io = { version = "1", package = "tokio", default-features = false, features
"net",
], optional = true }
ntex-neon = { version = "0.1.17", optional = true }
ntex-neon = { version = "0.1.11", optional = true }
[dev-dependencies]
env_logger = "0.11"

View file

@ -112,8 +112,6 @@ mod tokio {
///
/// This function panics if ntex system is not running.
#[inline]
#[doc(hidden)]
#[deprecated]
pub fn spawn_fn<F, R>(f: F) -> tok_io::task::JoinHandle<R::Output>
where
F: FnOnce() -> R + 'static,
@ -198,8 +196,6 @@ mod compio {
///
/// This function panics if ntex system is not running.
#[inline]
#[doc(hidden)]
#[deprecated]
pub fn spawn_fn<F, R>(f: F) -> JoinHandle<R::Output>
where
F: FnOnce() -> R + 'static,
@ -265,7 +261,7 @@ mod neon {
let rt = Runtime::new().unwrap();
log::info!(
"Starting neon runtime, driver {:?}",
rt.driver_type().name()
rt.driver().tp().name()
);
rt.block_on(fut);
@ -327,8 +323,6 @@ mod neon {
///
/// This function panics if ntex system is not running.
#[inline]
#[doc(hidden)]
#[deprecated]
pub fn spawn_fn<F, R>(f: F) -> Task<R::Output>
where
F: FnOnce() -> R + 'static,
@ -383,7 +377,7 @@ mod neon {
impl<T> JoinHandle<T> {
pub fn is_finished(&self) -> bool {
self.fut.is_none()
false
}
}

View file

@ -1,13 +1,5 @@
# Changes
## [2.7.3] - 2025-03-28
* Better worker availability handling
## [2.7.2] - 2025-03-27
* Handle paused state
## [2.7.1] - 2025-02-28
* Fix set core affinity out of worker start #508

View file

@ -1,6 +1,6 @@
[package]
name = "ntex-server"
version = "2.7.4"
version = "2.7.1"
authors = ["ntex contributors <team@ntex.rs>"]
description = "Server for ntex framework"
keywords = ["network", "framework", "async", "futures"]
@ -22,13 +22,13 @@ ntex-service = "3.4"
ntex-rt = "0.4"
ntex-util = "2.8"
async-channel = { workspace = true }
atomic-waker = { workspace = true }
core_affinity = { workspace = true }
oneshot = { workspace = true }
polling = { workspace = true }
log = { workspace = true }
socket2 = { workspace = true }
async-channel = "2"
async-broadcast = "0.7"
core_affinity = "0.8"
polling = "3.3"
log = "0.4"
socket2 = "0.5"
oneshot = { version = "0.1", default-features = false, features = ["async"] }
[dev-dependencies]
ntex = "2"

View file

@ -139,6 +139,7 @@ impl<F: ServerConfiguration> ServerManager<F> {
fn start_worker<F: ServerConfiguration>(mgr: ServerManager<F>, cid: Option<CoreId>) {
let _ = ntex_rt::spawn(async move {
let id = mgr.next_id();
let mut wrk = Worker::start(id, mgr.factory(), cid);
loop {
@ -180,7 +181,7 @@ impl<F: ServerConfiguration> HandleCmdState<F> {
fn process(&mut self, mut item: F::Item) {
loop {
if !self.workers.is_empty() {
if self.next >= self.workers.len() {
if self.next > self.workers.len() {
self.next = self.workers.len() - 1;
}
match self.workers[self.next].send(item) {
@ -211,9 +212,10 @@ impl<F: ServerConfiguration> HandleCmdState<F> {
match upd {
Update::Available(worker) => {
self.workers.push(worker);
self.workers.sort();
if self.workers.len() == 1 {
self.mgr.resume();
} else {
self.workers.sort();
}
}
Update::Unavailable(worker) => {
@ -232,9 +234,6 @@ impl<F: ServerConfiguration> HandleCmdState<F> {
if let Err(item) = self.workers[0].send(item) {
self.backlog.push_back(item);
self.workers.remove(0);
if self.workers.is_empty() {
self.mgr.pause();
}
break;
}
}

View file

@ -92,14 +92,12 @@ impl AcceptLoop {
/// Start accept loop
pub fn start(mut self, socks: Vec<(Token, Listener)>, srv: Server) {
let (tx, rx_start) = oneshot::channel();
let (rx, poll) = self
.inner
.take()
.expect("AcceptLoop cannot be used multiple times");
Accept::start(
tx,
rx,
poll,
socks,
@ -107,8 +105,6 @@ impl AcceptLoop {
self.notify.clone(),
self.status_handler.take(),
);
let _ = rx_start.recv();
}
}
@ -125,7 +121,6 @@ impl fmt::Debug for AcceptLoop {
struct Accept {
poller: Arc<Poller>,
rx: mpsc::Receiver<AcceptorCommand>,
tx: Option<oneshot::Sender<()>>,
sockets: Vec<ServerSocketInfo>,
srv: Server,
notify: AcceptNotify,
@ -136,7 +131,6 @@ struct Accept {
impl Accept {
fn start(
tx: oneshot::Sender<()>,
rx: mpsc::Receiver<AcceptorCommand>,
poller: Arc<Poller>,
socks: Vec<(Token, Listener)>,
@ -151,12 +145,11 @@ impl Accept {
.name("ntex-server accept loop".to_owned())
.spawn(move || {
System::set_current(sys);
Accept::new(tx, rx, poller, socks, srv, notify, status_handler).poll()
Accept::new(rx, poller, socks, srv, notify, status_handler).poll()
});
}
fn new(
tx: oneshot::Sender<()>,
rx: mpsc::Receiver<AcceptorCommand>,
poller: Arc<Poller>,
socks: Vec<(Token, Listener)>,
@ -182,7 +175,6 @@ impl Accept {
notify,
srv,
status_handler,
tx: Some(tx),
backpressure: true,
backlog: VecDeque::new(),
}
@ -200,23 +192,19 @@ impl Accept {
// Create storage for events
let mut events = Events::with_capacity(NonZeroUsize::new(512).unwrap());
let mut timeout = Some(Duration::ZERO);
loop {
if let Err(e) = self.poller.wait(&mut events, timeout) {
if e.kind() != io::ErrorKind::Interrupted {
if let Err(e) = self.poller.wait(&mut events, None) {
if e.kind() == io::ErrorKind::Interrupted {
continue;
} else {
panic!("Cannot wait for events in poller: {}", e)
}
} else if timeout.is_some() {
timeout = None;
let _ = self.tx.take().unwrap().send(());
}
for idx in 0..self.sockets.len() {
if self.sockets[idx].registered.get() {
let readd = self.accept(idx);
if readd {
self.add_source(idx);
}
for event in events.iter() {
let readd = self.accept(event.key);
if readd {
self.add_source(event.key);
}
}

View file

@ -114,7 +114,7 @@ impl ServiceFactory<Connection> for StreamService {
for info in &self.services {
match info.factory.create(()).await {
Ok(svc) => {
log::trace!("Constructed server service for {:?}", info.tokens);
log::debug!("Constructed server service for {:?}", info.tokens);
services.push(svc);
let idx = services.len() - 1;
for (token, tag) in &info.tokens {

View file

@ -2,8 +2,8 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::task::{ready, Context, Poll};
use std::{cmp, future::poll_fn, future::Future, hash, pin::Pin, sync::Arc};
use async_broadcast::{self as bus, broadcast};
use async_channel::{unbounded, Receiver, Sender};
use atomic_waker::AtomicWaker;
use core_affinity::CoreId;
use ntex_rt::{spawn, Arbiter};
@ -99,10 +99,10 @@ impl<T> Worker<T> {
log::debug!("Creating server instance in {:?}", id);
let factory = cfg.create().await;
log::debug!("Server instance has been created in {:?}", id);
match create(id, rx1, rx2, factory, avail_tx).await {
Ok((svc, wrk)) => {
log::debug!("Server instance has been created in {:?}", id);
run_worker(svc, wrk).await;
}
Err(e) => {
@ -151,8 +151,10 @@ impl<T> Worker<T> {
if self.failed.load(Ordering::Acquire) {
WorkerStatus::Failed
} else {
self.avail.wait_for_update().await;
if self.avail.failed() {
// cleanup updates
while self.avail.notify.try_recv().is_ok() {}
if self.avail.notify.recv_direct().await.is_err() {
self.failed.store(true, Ordering::Release);
}
self.status()
@ -194,85 +196,52 @@ impl Future for WorkerStop {
#[derive(Debug, Clone)]
struct WorkerAvailability {
inner: Arc<Inner>,
notify: bus::Receiver<()>,
available: Arc<AtomicBool>,
}
#[derive(Debug, Clone)]
struct WorkerAvailabilityTx {
inner: Arc<Inner>,
}
#[derive(Debug)]
struct Inner {
waker: AtomicWaker,
updated: AtomicBool,
available: AtomicBool,
failed: AtomicBool,
notify: bus::Sender<()>,
available: Arc<AtomicBool>,
}
impl WorkerAvailability {
fn create() -> (Self, WorkerAvailabilityTx) {
let inner = Arc::new(Inner {
waker: AtomicWaker::new(),
updated: AtomicBool::new(false),
available: AtomicBool::new(false),
failed: AtomicBool::new(false),
});
let (mut tx, rx) = broadcast(16);
tx.set_overflow(true);
let avail = WorkerAvailability {
inner: inner.clone(),
notify: rx,
available: Arc::new(AtomicBool::new(false)),
};
let avail_tx = WorkerAvailabilityTx {
notify: tx,
available: avail.available.clone(),
};
let avail_tx = WorkerAvailabilityTx { inner };
(avail, avail_tx)
}
fn failed(&self) -> bool {
self.inner.failed.load(Ordering::Acquire)
}
fn available(&self) -> bool {
self.inner.available.load(Ordering::Acquire)
}
async fn wait_for_update(&self) {
poll_fn(|cx| {
if self.inner.updated.load(Ordering::Acquire) {
self.inner.updated.store(false, Ordering::Release);
Poll::Ready(())
} else {
self.inner.waker.register(cx.waker());
Poll::Pending
}
})
.await;
self.available.load(Ordering::Acquire)
}
}
impl WorkerAvailabilityTx {
fn set(&self, val: bool) {
let old = self.inner.available.swap(val, Ordering::Release);
if old != val {
self.inner.updated.store(true, Ordering::Release);
self.inner.waker.wake();
let old = self.available.swap(val, Ordering::Release);
if !old && val {
let _ = self.notify.try_broadcast(());
}
}
}
impl Drop for WorkerAvailabilityTx {
fn drop(&mut self) {
self.inner.failed.store(true, Ordering::Release);
self.inner.updated.store(true, Ordering::Release);
self.inner.available.store(false, Ordering::Release);
self.inner.waker.wake();
}
}
/// Service worker
///
/// Worker accepts message via unbounded channel and starts processing.
struct WorkerSt<T, F: ServiceFactory<T>> {
id: WorkerId,
rx: Receiver<T>,
rx: Pin<Box<dyn Stream<Item = T>>>,
stop: Pin<Box<dyn Stream<Item = Shutdown>>>,
factory: F,
availability: WorkerAvailabilityTx,
@ -284,43 +253,25 @@ where
F: ServiceFactory<T> + 'static,
{
loop {
let mut recv = std::pin::pin!(wrk.rx.recv());
let fut = poll_fn(|cx| {
match svc.poll_ready(cx) {
Poll::Ready(Ok(())) => {
wrk.availability.set(true);
}
Poll::Ready(Err(err)) => {
wrk.availability.set(false);
return Poll::Ready(Err(err));
}
Poll::Pending => {
wrk.availability.set(false);
return Poll::Pending;
}
}
ready!(svc.poll_ready(cx)?);
match ready!(recv.as_mut().poll(cx)) {
Ok(item) => {
let fut = svc.call(item);
let _ = spawn(async move {
let _ = fut.await;
});
Poll::Ready(Ok::<_, F::Error>(true))
}
Err(_) => {
log::error!("Server is gone");
Poll::Ready(Ok(false))
}
if let Some(item) = ready!(Pin::new(&mut wrk.rx).poll_next(cx)) {
let fut = svc.call(item);
let _ = spawn(async move {
let _ = fut.await;
});
}
Poll::Ready(Ok::<(), F::Error>(()))
});
match select(fut, stream_recv(&mut wrk.stop)).await {
Either::Left(Ok(true)) => continue,
Either::Left(Ok(())) => continue,
Either::Left(Err(_)) => {
let _ = ntex_rt::spawn(async move {
svc.shutdown().await;
});
wrk.availability.set(false);
}
Either::Right(Some(Shutdown { timeout, result })) => {
wrk.availability.set(false);
@ -334,8 +285,7 @@ where
stop_svc(wrk.id, svc, timeout, Some(result)).await;
return;
}
Either::Left(Ok(false)) | Either::Right(None) => {
wrk.availability.set(false);
Either::Right(None) => {
stop_svc(wrk.id, svc, STOP_TIMEOUT, None).await;
return;
}
@ -345,6 +295,7 @@ where
loop {
match select(wrk.factory.create(()), stream_recv(&mut wrk.stop)).await {
Either::Left(Ok(service)) => {
wrk.availability.set(true);
svc = Pipeline::new(service).bind();
break;
}
@ -385,6 +336,8 @@ where
{
availability.set(false);
let factory = factory?;
let rx = Box::pin(rx);
let mut stop = Box::pin(stop);
let svc = match select(factory.create(()), stream_recv(&mut stop)).await {
@ -403,9 +356,9 @@ where
svc,
WorkerSt {
id,
rx,
factory,
availability,
rx: Box::pin(rx),
stop: Box::pin(stop),
},
))

View file

@ -128,7 +128,7 @@ impl<F: Filter> Service<Io<F>> for SslAcceptorService {
};
let io = io.add_filter(filter);
log::trace!("Accepting tls connection");
log::debug!("Accepting tls connection");
loop {
let result = io.with_buf(|buf| {
let filter = io.filter();

View file

@ -1,11 +1,5 @@
# Changes
## [2.12.4] - 2025-03-28
* http: Return PayloadError::Incomplete on server disconnect
* web: Expose WebStack for external wrapper support in downstream crates #542
## [2.12.3] - 2025-03-22
* web: Export web::app_service::AppService #534

View file

@ -1,6 +1,6 @@
[package]
name = "ntex"
version = "2.12.4"
version = "2.12.3"
authors = ["ntex contributors <team@ntex.rs>"]
description = "Framework for composable network services"
readme = "README.md"
@ -68,11 +68,11 @@ ntex-service = "3.4"
ntex-macros = "0.1"
ntex-util = "2.8"
ntex-bytes = "0.1.27"
ntex-server = "2.7.4"
ntex-server = "2.7"
ntex-h2 = "1.8.6"
ntex-rt = "0.4.27"
ntex-io = "2.11"
ntex-net = "2.5.10"
ntex-net = "2.5.8"
ntex-tls = "2.3"
base64 = "0.22"
@ -114,7 +114,6 @@ flate2 = { version = "1.0", optional = true }
[dev-dependencies]
rand = "0.8"
time = "0.3"
oneshot = "0.1"
futures-util = "0.3"
tls-openssl = { version = "0.10", package = "openssl" }
tls-rustls = { version = "0.23", package = "rustls", features = ["ring", "std"], default-features = false }

View file

@ -1,11 +1,13 @@
use std::{future::poll_fn, io, io::Write, pin::Pin, task, task::Poll, time::Instant};
use std::{
future::poll_fn, io, io::Write, pin::Pin, task::Context, task::Poll, time::Instant,
};
use crate::http::body::{BodySize, MessageBody};
use crate::http::error::PayloadError;
use crate::http::h1;
use crate::http::header::{HeaderMap, HeaderValue, HOST};
use crate::http::message::{RequestHeadType, ResponseHead};
use crate::http::payload::{Payload, PayloadStream};
use crate::http::{h1, Version};
use crate::io::{IoBoxed, RecvError};
use crate::time::{timeout_checked, Millis};
use crate::util::{ready, BufMut, Bytes, BytesMut, Stream};
@ -99,13 +101,7 @@ where
Ok((head, Payload::None))
}
_ => {
let pl: PayloadStream = Box::pin(PlStream::new(
io,
codec,
created,
pool,
head.version == Version::HTTP_10,
));
let pl: PayloadStream = Box::pin(PlStream::new(io, codec, created, pool));
Ok((head, pl.into()))
}
}
@ -141,7 +137,6 @@ pub(super) struct PlStream {
io: Option<IoBoxed>,
codec: h1::ClientPayloadCodec,
created: Instant,
http_10: bool,
pool: Option<Acquired>,
}
@ -151,14 +146,12 @@ impl PlStream {
codec: h1::ClientCodec,
created: Instant,
pool: Option<Acquired>,
http_10: bool,
) -> Self {
PlStream {
io: Some(io),
codec: codec.into_payload_codec(),
created,
pool,
http_10,
}
}
}
@ -168,46 +161,41 @@ impl Stream for PlStream {
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut task::Context<'_>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
let mut this = self.as_mut();
loop {
let item = ready!(this.io.as_ref().unwrap().poll_recv(&this.codec, cx));
return Poll::Ready(Some(match item {
Ok(chunk) => {
if let Some(chunk) = chunk {
Ok(chunk)
} else {
release_connection(
this.io.take().unwrap(),
!this.codec.keepalive(),
this.created,
this.pool.take(),
);
return Poll::Ready(None);
return Poll::Ready(Some(
match ready!(this.io.as_ref().unwrap().poll_recv(&this.codec, cx)) {
Ok(chunk) => {
if let Some(chunk) = chunk {
Ok(chunk)
} else {
release_connection(
this.io.take().unwrap(),
!this.codec.keepalive(),
this.created,
this.pool.take(),
);
return Poll::Ready(None);
}
}
}
Err(RecvError::KeepAlive) => {
Err(io::Error::new(io::ErrorKind::TimedOut, "Keep-alive").into())
}
Err(RecvError::Stop) => {
Err(io::Error::new(io::ErrorKind::Other, "Dispatcher stopped").into())
}
Err(RecvError::WriteBackpressure) => {
ready!(this.io.as_ref().unwrap().poll_flush(cx, false))?;
continue;
}
Err(RecvError::Decoder(err)) => Err(err),
Err(RecvError::PeerGone(Some(err))) => {
Err(PayloadError::Incomplete(Some(err)))
}
Err(RecvError::PeerGone(None)) => {
if this.http_10 {
return Poll::Ready(None);
Err(RecvError::KeepAlive) => {
Err(io::Error::new(io::ErrorKind::TimedOut, "Keep-alive").into())
}
Err(PayloadError::Incomplete(None))
}
}));
Err(RecvError::Stop) => {
Err(io::Error::new(io::ErrorKind::Other, "Dispatcher stopped")
.into())
}
Err(RecvError::WriteBackpressure) => {
ready!(this.io.as_ref().unwrap().poll_flush(cx, false))?;
continue;
}
Err(RecvError::Decoder(err)) => Err(err),
Err(RecvError::PeerGone(Some(err))) => Err(err.into()),
Err(RecvError::PeerGone(None)) => return Poll::Ready(None),
},
));
}
}
}

View file

@ -387,8 +387,8 @@ impl Future for ReadBody {
let this = self.get_mut();
loop {
return match Pin::new(&mut this.stream).poll_next(cx) {
Poll::Ready(Some(Ok(chunk))) => {
return match Pin::new(&mut this.stream).poll_next(cx)? {
Poll::Ready(Some(chunk)) => {
if this.limit > 0 && (this.buf.len() + chunk.len()) > this.limit {
Poll::Ready(Err(PayloadError::Overflow))
} else {
@ -397,7 +397,6 @@ impl Future for ReadBody {
}
}
Poll::Ready(None) => Poll::Ready(Ok(this.buf.split().freeze())),
Poll::Ready(Some(Err(err))) => Poll::Ready(Err(err)),
Poll::Pending => {
if this.timeout.poll_elapsed(cx).is_ready() {
Poll::Ready(Err(PayloadError::Incomplete(Some(

View file

@ -107,12 +107,12 @@ pub(super) trait MessageType: Sized {
};
match name {
header::CONTENT_LENGTH if content_length.is_some() || chunked => {
log::trace!("multiple Content-Length not allowed");
log::debug!("multiple Content-Length not allowed");
return Err(DecodeError::Header);
}
header::CONTENT_LENGTH => match value.to_str() {
Ok(s) if s.trim_start().starts_with('+') => {
log::trace!("illegal Content-Length: {:?}", s);
log::debug!("illegal Content-Length: {:?}", s);
return Err(DecodeError::Header);
}
Ok(s) => {
@ -121,18 +121,18 @@ pub(super) trait MessageType: Sized {
// headers have been processed to prevent request smuggling issues
content_length = Some(len);
} else {
log::trace!("illegal Content-Length: {:?}", s);
log::debug!("illegal Content-Length: {:?}", s);
return Err(DecodeError::Header);
}
}
Err(_) => {
log::trace!("illegal Content-Length: {:?}", value);
log::debug!("illegal Content-Length: {:?}", value);
return Err(DecodeError::Header);
}
},
// transfer-encoding
header::TRANSFER_ENCODING if seen_te => {
log::trace!("Transfer-Encoding header usage is not allowed");
log::debug!("Transfer-Encoding header usage is not allowed");
return Err(DecodeError::Header);
}
header::TRANSFER_ENCODING if version == Version::HTTP_11 => {
@ -144,7 +144,7 @@ pub(super) trait MessageType: Sized {
} else if s.eq_ignore_ascii_case("identity") {
// allow silently since multiple TE headers are already checked
} else {
log::trace!("illegal Transfer-Encoding: {:?}", s);
log::debug!("illegal Transfer-Encoding: {:?}", s);
return Err(DecodeError::Header);
}
} else {
@ -266,7 +266,7 @@ impl MessageType for Request {
// disallow HTTP/1.0 POST requests that do not contain a Content-Length headers
// see https://datatracker.ietf.org/doc/html/rfc1945#section-7.2.2
if ver == Version::HTTP_10 && method == Method::POST && length.is_none() {
log::trace!("no Content-Length specified for HTTP/1.0 POST request");
log::debug!("no Content-Length specified for HTTP/1.0 POST request");
return Err(DecodeError::Header);
}
@ -682,7 +682,7 @@ impl ChunkedState {
Poll::Ready(Ok(ChunkedState::Size))
}
None => {
log::trace!("chunk size would overflow u64");
log::debug!("chunk size would overflow u64");
Poll::Ready(Err(DecodeError::InvalidInput(
"Invalid chunk size line: Size is too big",
)))

View file

@ -252,6 +252,7 @@ where
Ok(())
})
});
let (system, server, addr) = rx.recv().unwrap();
TestServer {

View file

@ -82,7 +82,7 @@ mod route;
mod scope;
mod server;
mod service;
pub mod stack;
mod stack;
pub mod test;
pub mod types;
mod util;

View file

@ -116,7 +116,7 @@ impl Parser {
return Err(ProtocolError::InvalidLength(length));
}
OpCode::Close if length > 125 => {
log::trace!("Received close frame with payload length exceeding 125. Morphing to protocol close frame.");
log::debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame.");
return Ok(Some((true, OpCode::Close, None)));
}
_ => (),

View file

@ -1,8 +1,9 @@
use std::{io, rc::Rc};
use ntex::codec::BytesCodec;
use ntex::connect::Connect;
use ntex::io::{types::PeerAddr, Io};
use ntex::service::{chain_factory, fn_service, Pipeline, ServiceFactory};
use ntex::{codec::BytesCodec, connect::Connect};
use ntex::{server::build_test_server, server::test_server, time, util::Bytes};
#[cfg(feature = "rustls")]

View file

@ -508,21 +508,19 @@ async fn test_client_gzip_encoding_large() {
async fn test_client_gzip_encoding_large_random() {
let data = rand::thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(1_048_500)
.take(100_000)
.map(char::from)
.collect::<String>();
let srv = test::server(|| {
App::new()
.state(web::types::PayloadConfig::default().limit(1_048_576))
.service(web::resource("/").route(web::to(|data: Bytes| async move {
let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(&data).unwrap();
let data = e.finish().unwrap();
HttpResponse::Ok()
.header("content-encoding", "gzip")
.body(data)
})))
App::new().service(web::resource("/").route(web::to(|data: Bytes| async move {
let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(&data).unwrap();
let data = e.finish().unwrap();
HttpResponse::Ok()
.header("content-encoding", "gzip")
.body(data)
})))
});
// client request
@ -530,7 +528,7 @@ async fn test_client_gzip_encoding_large_random() {
assert!(response.status().is_success());
// read response
let bytes = response.body().limit(1_048_576).await.unwrap();
let bytes = response.body().await.unwrap();
assert_eq!(bytes, Bytes::from(data));
}
@ -682,18 +680,15 @@ async fn client_read_until_eof() {
for stream in lst.incoming() {
if let Ok(mut stream) = stream {
let mut b = [0; 1000];
log::debug!("Reading request");
let res = stream.read(&mut b).unwrap();
log::debug!("Read {:?}", res);
let res = stream
let _ = stream.read(&mut b).unwrap();
let _ = stream
.write_all(b"HTTP/1.0 200 OK\r\nconnection: close\r\n\r\nwelcome!");
log::debug!("Sent {:?}", res);
} else {
break;
}
}
});
sleep(Millis(500)).await;
sleep(Millis(300)).await;
// client request
let req = Client::build()

View file

@ -1,6 +1,5 @@
#![cfg(feature = "openssl")]
use std::io;
use std::sync::{atomic::AtomicUsize, atomic::Ordering, Arc, Mutex};
use std::{io, sync::atomic::AtomicUsize, sync::atomic::Ordering, sync::Arc};
use futures_util::stream::{once, Stream, StreamExt};
use tls_openssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod};
@ -425,12 +424,11 @@ async fn test_h2_service_error() {
assert_eq!(bytes, Bytes::from_static(b"error"));
}
struct SetOnDrop(Arc<AtomicUsize>, Arc<Mutex<Option<::oneshot::Sender<()>>>>);
struct SetOnDrop(Arc<AtomicUsize>);
impl Drop for SetOnDrop {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::Relaxed);
let _ = self.1.lock().unwrap().take().unwrap().send(());
}
}
@ -438,20 +436,17 @@ impl Drop for SetOnDrop {
async fn test_h2_client_drop() -> io::Result<()> {
let count = Arc::new(AtomicUsize::new(0));
let count2 = count.clone();
let (tx, rx) = ::oneshot::channel();
let tx = Arc::new(Mutex::new(Some(tx)));
let srv = test_server(move || {
let tx = tx.clone();
let count = count2.clone();
HttpService::build()
.h2(move |req: Request| {
let st = SetOnDrop(count.clone(), tx.clone());
let count = count.clone();
async move {
let _st = SetOnDrop(count);
assert!(req.peer_addr().is_some());
assert_eq!(req.version(), Version::HTTP_2);
sleep(Seconds(30)).await;
drop(st);
sleep(Seconds(100)).await;
Ok::<_, io::Error>(Response::Ok().finish())
}
})
@ -459,9 +454,9 @@ async fn test_h2_client_drop() -> io::Result<()> {
.map_err(|_| ())
});
let result = timeout(Millis(1500), srv.srequest(Method::GET, "/").send()).await;
let result = timeout(Millis(250), srv.srequest(Method::GET, "/").send()).await;
assert!(result.is_err());
let _ = timeout(Millis(1500), rx).await;
sleep(Millis(150)).await;
assert_eq!(count.load(Ordering::Relaxed), 1);
Ok(())
}
@ -544,19 +539,13 @@ async fn test_ws_transport() {
async fn test_h2_graceful_shutdown() -> io::Result<()> {
let count = Arc::new(AtomicUsize::new(0));
let count2 = count.clone();
let (tx, rx) = ::oneshot::channel();
let tx = Arc::new(Mutex::new(Some(tx)));
let srv = test_server(move || {
let tx = tx.clone();
let count = count2.clone();
HttpService::build()
.h2(move |_| {
let count = count.clone();
count.fetch_add(1, Ordering::Relaxed);
if count.load(Ordering::Relaxed) == 2 {
let _ = tx.lock().unwrap().take().unwrap().send(());
}
async move {
sleep(Millis(1000)).await;
count.fetch_sub(1, Ordering::Relaxed);
@ -577,7 +566,7 @@ async fn test_h2_graceful_shutdown() -> io::Result<()> {
let _ = req.send().await.unwrap();
sleep(Millis(100000)).await;
});
let _ = rx.await;
sleep(Millis(150)).await;
assert_eq!(count.load(Ordering::Relaxed), 2);
let (tx, rx) = oneshot::channel();
@ -585,6 +574,8 @@ async fn test_h2_graceful_shutdown() -> io::Result<()> {
srv.stop().await;
let _ = tx.send(());
});
sleep(Millis(150)).await;
assert_eq!(count.load(Ordering::Relaxed), 2);
let _ = rx.await;
assert_eq!(count.load(Ordering::Relaxed), 0);

View file

@ -1,4 +1,4 @@
use std::sync::{atomic::AtomicUsize, atomic::Ordering, Arc, Mutex};
use std::sync::{atomic::AtomicUsize, atomic::Ordering, Arc};
use std::{io, io::Read, io::Write, net};
use futures_util::future::{self, FutureExt};
@ -723,12 +723,11 @@ async fn test_h1_service_error() {
assert_eq!(bytes, Bytes::from_static(b"error"));
}
struct SetOnDrop(Arc<AtomicUsize>, Option<::oneshot::Sender<()>>);
struct SetOnDrop(Arc<AtomicUsize>);
impl Drop for SetOnDrop {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::Relaxed);
let _ = self.1.take().unwrap().send(());
}
}
@ -736,28 +735,24 @@ impl Drop for SetOnDrop {
async fn test_h1_client_drop() -> io::Result<()> {
let count = Arc::new(AtomicUsize::new(0));
let count2 = count.clone();
let (tx, rx) = ::oneshot::channel();
let tx = Arc::new(Mutex::new(Some(tx)));
let srv = test_server(move || {
let tx = tx.clone();
let count = count2.clone();
HttpService::build().h1(move |req: Request| {
let tx = tx.clone();
let count = count.clone();
async move {
let _st = SetOnDrop(count, tx.lock().unwrap().take());
let _st = SetOnDrop(count);
assert!(req.peer_addr().is_some());
assert_eq!(req.version(), Version::HTTP_11);
sleep(Millis(50000)).await;
sleep(Millis(500)).await;
Ok::<_, io::Error>(Response::Ok().finish())
}
})
});
let result = timeout(Millis(1500), srv.request(Method::GET, "/").send()).await;
let result = timeout(Millis(100), srv.request(Method::GET, "/").send()).await;
assert!(result.is_err());
let _ = rx.await;
sleep(Millis(1000)).await;
assert_eq!(count.load(Ordering::Relaxed), 1);
Ok(())
}
@ -766,18 +761,12 @@ async fn test_h1_client_drop() -> io::Result<()> {
async fn test_h1_gracefull_shutdown() {
let count = Arc::new(AtomicUsize::new(0));
let count2 = count.clone();
let (tx, rx) = ::oneshot::channel();
let tx = Arc::new(Mutex::new(Some(tx)));
let srv = test_server(move || {
let tx = tx.clone();
let count = count2.clone();
HttpService::build().h1(move |_: Request| {
let count = count.clone();
count.fetch_add(1, Ordering::Relaxed);
if count.load(Ordering::Relaxed) == 2 {
let _ = tx.lock().unwrap().take().unwrap().send(());
}
async move {
sleep(Millis(1000)).await;
count.fetch_sub(1, Ordering::Relaxed);
@ -792,7 +781,7 @@ async fn test_h1_gracefull_shutdown() {
let mut stream2 = net::TcpStream::connect(srv.addr()).unwrap();
let _ = stream2.write_all(b"GET /index.html HTTP/1.1\r\n\r\n");
let _ = rx.await;
sleep(Millis(150)).await;
assert_eq!(count.load(Ordering::Relaxed), 2);
let (tx, rx) = oneshot::channel();
@ -800,6 +789,8 @@ async fn test_h1_gracefull_shutdown() {
srv.stop().await;
let _ = tx.send(());
});
sleep(Millis(150)).await;
assert_eq!(count.load(Ordering::Relaxed), 2);
let _ = rx.await;
assert_eq!(count.load(Ordering::Relaxed), 0);
@ -809,18 +800,12 @@ async fn test_h1_gracefull_shutdown() {
async fn test_h1_gracefull_shutdown_2() {
let count = Arc::new(AtomicUsize::new(0));
let count2 = count.clone();
let (tx, rx) = ::oneshot::channel();
let tx = Arc::new(Mutex::new(Some(tx)));
let srv = test_server(move || {
let tx = tx.clone();
let count = count2.clone();
HttpService::build().finish(move |_: Request| {
let count = count.clone();
count.fetch_add(1, Ordering::Relaxed);
if count.load(Ordering::Relaxed) == 2 {
let _ = tx.lock().unwrap().take().unwrap().send(());
}
async move {
sleep(Millis(1000)).await;
count.fetch_sub(1, Ordering::Relaxed);
@ -835,14 +820,17 @@ async fn test_h1_gracefull_shutdown_2() {
let mut stream2 = net::TcpStream::connect(srv.addr()).unwrap();
let _ = stream2.write_all(b"GET /index.html HTTP/1.1\r\n\r\n");
let _ = rx.await;
assert_eq!(count.load(Ordering::Acquire), 2);
sleep(Millis(150)).await;
assert_eq!(count.load(Ordering::Relaxed), 2);
let (tx, rx) = oneshot::channel();
rt::spawn(async move {
srv.stop().await;
let _ = tx.send(());
});
sleep(Millis(150)).await;
assert_eq!(count.load(Ordering::Relaxed), 2);
let _ = rx.await;
assert_eq!(count.load(Ordering::Relaxed), 0);
}