Skip to content

Commit

Permalink
Handle io flush during write back-pressure (#246)
Browse files Browse the repository at this point in the history
  • Loading branch information
fafhrd91 authored Nov 12, 2023
1 parent 2a19b7f commit c6b2612
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 38 deletions.
4 changes: 4 additions & 0 deletions ntex-io/CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changes

## [0.3.7] - 2023-11-12

* Handle io flush during write back-pressure

## [0.3.6] - 2023-11-11

* Add support for frame read timeout
Expand Down
6 changes: 3 additions & 3 deletions ntex-io/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ntex-io"
version = "0.3.6"
version = "0.3.7"
authors = ["ntex contributors <team@ntex.rs>"]
description = "Utilities for encoding and decoding frames"
keywords = ["network", "framework", "async", "futures"]
Expand All @@ -17,7 +17,7 @@ path = "src/lib.rs"

[dependencies]
ntex-codec = "0.6.2"
ntex-bytes = "0.1.20"
ntex-bytes = "0.1.21"
ntex-util = "0.3.4"
ntex-service = "1.2.7"

Expand All @@ -29,4 +29,4 @@ pin-project-lite = "0.2"
rand = "0.8"
env_logger = "0.10"

ntex = { version = "0.7.0", features = ["tokio"] }
ntex = { version = "0.7", features = ["tokio"] }
17 changes: 9 additions & 8 deletions ntex-io/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ impl Default for DispatcherConfig {
fn default() -> Self {
DispatcherConfig(Rc::new(DispatcherConfigInner {
keepalive_timeout: Cell::new(Seconds(30).into()),
disconnect_timeout: Cell::new(Seconds(1).into()),
disconnect_timeout: Cell::new(Seconds(1)),
frame_read_rate: Cell::new(0),
frame_read_enabled: Cell::new(false),
frame_read_timeout: Cell::new(Seconds::ZERO.into()),
Expand Down Expand Up @@ -92,8 +92,9 @@ impl DispatcherConfig {

/// Set read rate parameters for single frame.
///
/// Set max timeout for reading single frame. If the client sends data,
/// increase the timeout by 1 second for every.
/// Set max timeout for reading single frame. If the client
/// sends `rate` amount of data, increase the timeout by 1 second for every.
/// But no more than `max_timeout` timeout.
///
/// By default frame read rate is disabled.
pub fn set_frame_read_rate(
Expand Down Expand Up @@ -320,8 +321,7 @@ where
loop {
match slf.st {
DispatcherState::Processing => {
let srv = ready!(slf.poll_service(cx));
let item = match srv {
let item = match ready!(slf.poll_service(cx)) {
PollService::Ready => {
// decode incoming bytes if buffer is ready
match slf.shared.io.poll_recv_decode(&slf.shared.codec, cx) {
Expand Down Expand Up @@ -386,11 +386,12 @@ where
DispatcherState::Backpressure => {
let item = match ready!(slf.poll_service(cx)) {
PollService::Ready => {
if slf.shared.io.poll_flush(cx, false).is_ready() {
if let Err(err) = ready!(slf.shared.io.poll_flush(cx, false)) {
slf.st = DispatcherState::Stop;
DispatchItem::Disconnect(Some(err))
} else {
slf.st = DispatcherState::Processing;
DispatchItem::WBackPressureDisabled
} else {
return Poll::Pending;
}
}
PollService::Item(item) => item,
Expand Down
14 changes: 7 additions & 7 deletions ntex-io/src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,15 @@ impl Filter for Base {

if flags.intersects(Flags::IO_STOPPING | Flags::IO_STOPPED) {
Poll::Ready(ReadStatus::Terminate)
} else if flags.intersects(Flags::IO_STOPPING_FILTERS) {
self.0 .0.read_task.register(cx.waker());
Poll::Ready(ReadStatus::Ready)
} else if flags.intersects(Flags::RD_PAUSED | Flags::RD_BUF_FULL) {
self.0 .0.read_task.register(cx.waker());
Poll::Pending
} else {
self.0 .0.read_task.register(cx.waker());
Poll::Ready(ReadStatus::Ready)
if flags.intersects(Flags::IO_STOPPING_FILTERS) {
Poll::Ready(ReadStatus::Ready)
} else if flags.intersects(Flags::RD_PAUSED | Flags::RD_BUF_FULL) {
Poll::Pending
} else {
Poll::Ready(ReadStatus::Ready)
}
}
}

Expand Down
37 changes: 24 additions & 13 deletions ntex-io/src/tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,33 +38,44 @@ impl ReadContext {

// handle buffer changes
if nbytes > 0 {
let buf_full = nbytes >= hw;
let filter = self.0.filter();
let _ = filter
.process_read_buf(&self.0, &inner.buffer, 0, nbytes)
.and_then(|status| {
if status.nbytes > 0 {
if buf_full || inner.buffer.read_destination_size() >= hw {
// 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",
total
);
inner.insert_flags(Flags::RD_READY | Flags::RD_BUF_FULL);
} else {
inner.insert_flags(Flags::RD_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", nbytes);
inner.dispatch_task.wake();
} else if buf_full {
// 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();
} else if inner.flags.get().contains(Flags::RD_FORCE_READY) {
// in case of "force read" we must wake up dispatch task
// if we read any data from source
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().contains(Flags::RD_FORCE_READY) {
// in case of "force read" we must wake up dispatch task
// if we read any data from source
inner.dispatch_task.wake();
}
}

// while reading, filter wrote some data
Expand All @@ -78,8 +89,8 @@ impl ReadContext {
})
.map_err(|err| {
inner.dispatch_task.wake();
inner.insert_flags(Flags::RD_READY);
inner.io_stopped(Some(err));
inner.insert_flags(Flags::RD_READY);
});
}

Expand Down
11 changes: 5 additions & 6 deletions ntex-tokio/src/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ impl Future for WriteTask {

match this.st {
IoWriteState::Processing(ref mut delay) => {
match this.state.poll_ready(cx) {
Poll::Ready(WriteStatus::Ready) => {
match ready!(this.state.poll_ready(cx)) {
WriteStatus::Ready => {
if let Some(delay) = delay {
if delay.poll_elapsed(cx).is_ready() {
this.state.close(Some(io::Error::new(
Expand All @@ -157,14 +157,14 @@ impl Future for WriteTask {
}
}
}
Poll::Ready(WriteStatus::Timeout(time)) => {
WriteStatus::Timeout(time) => {
log::trace!("initiate timeout delay for {:?}", time);
if delay.is_none() {
*delay = Some(sleep(time));
}
self.poll(cx)
}
Poll::Ready(WriteStatus::Shutdown(time)) => {
WriteStatus::Shutdown(time) => {
log::trace!("write task is instructed to shutdown");

let timeout = if let Some(delay) = delay.take() {
Expand All @@ -176,7 +176,7 @@ impl Future for WriteTask {
this.st = IoWriteState::Shutdown(timeout, Shutdown::None);
self.poll(cx)
}
Poll::Ready(WriteStatus::Terminate) => {
WriteStatus::Terminate => {
log::trace!("write task is instructed to terminate");

if !matches!(
Expand All @@ -191,7 +191,6 @@ impl Future for WriteTask {
this.state.close(None);
Poll::Ready(())
}
Poll::Pending => Poll::Pending,
}
}
IoWriteState::Shutdown(ref mut delay, ref mut st) => {
Expand Down
2 changes: 1 addition & 1 deletion ntex/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ ntex-util = "0.3.4"
ntex-bytes = "0.1.21"
ntex-h2 = "0.4.4"
ntex-rt = "0.4.10"
ntex-io = "0.3.6"
ntex-io = "0.3.7"
ntex-tls = "0.3.2"
ntex-tokio = { version = "0.3.1", optional = true }
ntex-glommio = { version = "0.3.0", optional = true }
Expand Down

0 comments on commit c6b2612

Please sign in to comment.