ntex_io/ctx.rs
1use std::{fmt, io, task::Context, task::Poll};
2
3use ntex_bytes::{BytePages, BytesMut};
4use ntex_util::time::{Seconds, sleep};
5
6use crate::filter::{read_readiness, write_readiness};
7use crate::{Flags, Id, IoRef, IoTaskStatus, Readiness, io::IoState};
8
9/// Connection context shared with transport read and write tasks.
10///
11/// Transport implementations obtain buffers from this context, perform
12/// nonblocking I/O, and return completion through
13/// [`release_read_buf`](Self::release_read_buf) and
14/// [`update_write_status`](Self::update_write_status). Their return value tells
15/// the task whether to continue, pause until notified, or stop.
16///
17/// # Shutdown
18///
19/// A transport task runs until [`poll_read_ready`](Self::poll_read_ready) or
20/// [`poll_write_ready`](Self::poll_write_ready) reports [`Readiness::Close`] or
21/// [`Readiness::Terminate`], or until a status update returns
22/// [`IoTaskStatus::Stop`]. All of those imply that the connection is already
23/// closing or closed.
24///
25/// Graceful shutdown runs in two phases. In the first the filters shut down
26/// while both directions stay open. In the second, buffered output is drained
27/// into the transport while the read side is paused;
28/// [`Readiness::Close`] is reported only once nothing is left to write. A
29/// single shutdown timeout bounds both phases. If it elapses during filter
30/// shutdown, that phase is skipped and transport shutdown begins. If it
31/// elapses during transport shutdown, the connection closes and any remaining
32/// output is discarded.
33///
34/// So by the time the loop exits there is nothing left to drain, whether the
35/// connection was shut down gracefully or terminated. A task must never attempt
36/// a final flush on the way out; it should release the transport immediately
37/// and report the outcome through [`stopped`](Self::stopped). The two variants
38/// differ only in how the transport is released: [`Readiness::Close`] closes
39/// both directions gracefully, while [`Readiness::Terminate`] skips the
40/// graceful close so that an aborted connection stays distinguishable from one
41/// that ended normally. `Terminate` is reported for an explicit
42/// [`IoRef::terminate`](crate::IoRef::terminate), and when [`Io`](crate::Io) is
43/// dropped while output it accepted has not reached the transport, because the
44/// filter chain goes away with it and that output can never be delivered. Every
45/// other way a connection can end, an expired shutdown timeout included,
46/// reports `Close`.
47// `repr(transparent)` is required: `IoRef::call_write` casts `&IoRef` to `&IoContext`.
48#[repr(transparent)]
49pub struct IoContext(IoRef);
50
51impl IoContext {
52 pub(crate) fn new(io: IoRef) -> Self {
53 Self(io)
54 }
55
56 fn st(&self) -> &IoState {
57 &self.0.0
58 }
59
60 #[inline]
61 /// Gets the ID.
62 pub fn id(&self) -> Id {
63 self.0.id()
64 }
65
66 #[inline]
67 /// Gets the I/O tag.
68 pub fn tag(&self) -> &'static str {
69 self.0.tag()
70 }
71
72 #[doc(hidden)]
73 /// Gets the state flags. (for debug purpose only)
74 pub fn flags(&self) -> Flags {
75 self.0.flags()
76 }
77
78 #[inline]
79 /// Gets the configured shutdown timeout.
80 ///
81 /// A backend whose own teardown can stall, such as one waiting for
82 /// cancelled operations to complete, can bound it with this, as the
83 /// graceful shutdown before it is bounded.
84 pub fn shutdown_timeout(&self) -> Seconds {
85 self.0.cfg().shutdown_timeout()
86 }
87
88 #[inline]
89 /// Checks readiness for read operations.
90 ///
91 /// Resolves to [`Readiness::Ready`], [`Readiness::Close`] or
92 /// [`Readiness::Terminate`], or stays `Pending`. Reads continue through the
93 /// filter shutdown phase so that filters can complete theirs, and are
94 /// paused for the transport shutdown phase, so `Close` is resolved here
95 /// only once the connection is terminated.
96 ///
97 /// A filter that is not ready while the io state allows reads pauses
98 /// them, see [`IoRef::is_read_filter_paused`]. The dispatcher is notified
99 /// when the pause starts and when it ends.
100 pub fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<Readiness> {
101 let st = self.st();
102 if st.flags.is_force_closing() {
103 // The filter chain is replaced by `NullFilter` when `Io` is
104 // dropped, so the force-close decision is made here rather than in
105 // the chain: it has to survive that replacement.
106 return Poll::Ready(Readiness::Terminate);
107 }
108 self.poll_filters_shutdown(cx);
109 let res = {
110 let _borrow = st.buffer.borrow();
111 self.0.filter().poll_read_ready(cx)
112 };
113
114 if res.is_pending() {
115 // A pause the io state accounts for is known to the dispatcher. A
116 // filter pause is kept until the chain is ready again.
117 if !st.flags.is_read_filter_paused()
118 && read_readiness(st) == Poll::Ready(Readiness::Ready)
119 {
120 log::trace!("{}: Filter is not ready, pause reading", st.tag());
121 st.flags.set_read_filter_paused();
122 st.wake_dispatch_task();
123 }
124 } else if st.flags.is_read_filter_paused() {
125 log::trace!("{}: Filter is ready, resume reading", st.tag());
126 st.flags.unset_read_filter_paused();
127 st.wake_dispatch_task();
128 }
129 res
130 }
131
132 #[inline]
133 /// Checks readiness for write operations.
134 ///
135 /// Resolves to [`Readiness::Ready`], [`Readiness::Close`] or
136 /// [`Readiness::Terminate`], or stays `Pending`. Unlike the read path this
137 /// reports `Close` at the end of a graceful shutdown as well, once buffered
138 /// output has been drained, so the task must not flush again.
139 ///
140 /// A filter that is not ready while output is waiting pauses writes, see
141 /// [`IoRef::is_write_filter_paused`]. The dispatcher is notified when the
142 /// pause starts and when it ends.
143 pub fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<Readiness> {
144 let st = self.st();
145 if st.flags.is_force_closing() {
146 // see `poll_read_ready`
147 return Poll::Ready(Readiness::Terminate);
148 }
149 self.poll_shutdown_deadline(cx);
150 let res = {
151 let _borrow = st.buffer.borrow();
152 self.0.filter().poll_write_ready(cx)
153 };
154
155 if res.is_pending() {
156 // see `poll_read_ready`
157 if !st.flags.is_write_filter_paused()
158 && write_readiness(st) == Poll::Ready(Readiness::Ready)
159 {
160 log::trace!("{}: Filter is not ready, pause writing", st.tag());
161 st.flags.set_write_filter_paused();
162 st.wake_dispatch_task();
163 }
164 } else if st.flags.is_write_filter_paused() {
165 log::trace!("{}: Filter is ready, resume writing", st.tag());
166 st.flags.unset_write_filter_paused();
167 st.wake_dispatch_task();
168 }
169 res
170 }
171
172 /// Stops I/O processing without graceful filter shutdown.
173 ///
174 /// Pending application work is not drained. Unlike
175 /// [`IoRef::terminate`](crate::IoRef::terminate), this does not request an
176 /// aborted transport release: the transport observes
177 /// [`Readiness::Close`] and closes both directions gracefully. Call
178 /// [`stopped`](Self::stopped) afterwards, once transport teardown has
179 /// actually finished.
180 pub fn stop(&self, e: Option<io::Error>) {
181 self.st().terminate_connection(e);
182 }
183
184 /// Marks backend transport teardown as complete.
185 pub fn stopped(&self, e: Option<io::Error>) {
186 self.st().stop_connection(e);
187 }
188
189 /// Takes a buffer for the next transport read.
190 ///
191 /// The returned buffer must be released exactly once through
192 /// [`release_read_buf`](Self::release_read_buf), even when the read
193 /// fails or would otherwise stop the task.
194 ///
195 /// This hands out a buffer of its own while the dispatcher still has input
196 /// to consume, because the read buffer is moved out of the io state until
197 /// it is released and the dispatcher would not find it. A transport whose
198 /// read completes without suspending can avoid that with
199 /// [`with_read_buf`](Self::with_read_buf).
200 pub fn take_read_buf(&self) -> BytesMut {
201 let st = self.st();
202
203 if st.flags.is_read_ready() {
204 // The dispatcher has not consumed the read buffer yet, so it must
205 // stay in place and the read goes to a buffer of its own.
206 st.get_read_buf()
207 } else if let Some(mut buf) = st.buffer.get_read_buf() {
208 buf.reserve_more();
209 buf
210 } else {
211 st.get_read_buf()
212 }
213 }
214
215 /// Releases a transport read buffer and reports the read result.
216 ///
217 /// This is the counterpart of [`take_read_buf`](Self::take_read_buf); every
218 /// buffer it hands out must come back here exactly once.
219 ///
220 /// `Poll::Ready(Ok(n))` reports that `n` bytes were appended to `buf`.
221 /// Zero marks the transport read side as closed and invokes the read filter
222 /// chain once with no new bytes. This lets filters emit final buffered data
223 /// or report truncated input. Further transport reads are parked, but
224 /// buffered input remains decodable and the write side remains usable until
225 /// graceful shutdown. `Poll::Ready(Err(_))` terminates the connection.
226 /// `Poll::Pending` returns the buffer after a nonblocking operation made no
227 /// progress or a submitted operation was canceled for reissue.
228 ///
229 /// The returned [`IoTaskStatus`] instructs the read task to continue
230 /// immediately, pause until notified, or stop.
231 pub fn release_read_buf(&self, buf: BytesMut, status: Poll<io::Result<usize>>) -> IoTaskStatus {
232 let st = self.st();
233 let orig = st.buffer.read_dst_size();
234
235 #[cfg(feature = "trace")]
236 log::trace!(
237 "{}: read-status == {status:?} orig:{orig:?} flags:{:?}",
238 st.tag(),
239 st.flags
240 );
241
242 // Transport shutdown phase, the filters are shut down and the
243 // connection is about to be closed. The read task is paused, but a read
244 // issued before the transition can still complete here; its input is
245 // discarded because nothing can consume it anymore.
246 if st.flags.is_stopping() {
247 let mut buf = buf;
248 buf.clear();
249 st.buffer.set_read_buf(buf);
250 stopping_read_status(st, &status)
251 } else {
252 let mut buf = buf;
253 track_read(st, &buf, &status);
254 if st.is_io_dropped() {
255 // the `Io` is gone, nothing can consume this input anymore
256 buf.clear();
257 }
258 // release read buffer
259 st.buffer.set_read_buf(buf);
260
261 self.process_read_status(orig, status)
262 }
263 }
264
265 /// Reads into the read buffer in place and reports the read result.
266 ///
267 /// This is the counterpart of [`take_read_buf`](Self::take_read_buf) and
268 /// [`release_read_buf`](Self::release_read_buf) for a transport whose
269 /// read completes without suspending. `f` reads into the buffer it is
270 /// given and reports the same status `release_read_buf` takes, with the
271 /// same meaning.
272 ///
273 /// The buffer is not moved out of the io state for the duration of the
274 /// call, so no temporary buffer is taken from the pool and no append is
275 /// needed to put the result back. A transport that keeps the buffer across
276 /// a suspension point cannot use this: the buffer would be missing while
277 /// the dispatcher looks for input, so it must take one of its own through
278 /// `take_read_buf` instead.
279 ///
280 /// `f` must not read from this io again. Nested read access is unsupported
281 /// and may terminate the connection or lose nested buffer changes.
282 pub fn with_read_buf<F>(&self, f: F) -> IoTaskStatus
283 where
284 F: FnOnce(&mut BytesMut) -> Poll<io::Result<usize>>,
285 {
286 let st = self.st();
287 let orig = st.buffer.read_dst_size();
288 let stopping = st.flags.is_stopping();
289 let discard = stopping || st.is_io_dropped();
290
291 let status = st.buffer.with_read_src(&self.0, |buf| {
292 buf.reserve_more();
293 let status = f(buf);
294 if !stopping {
295 track_read(st, buf, &status);
296 }
297 if discard {
298 // the filters are done or the `Io` is gone, nothing can
299 // consume this input anymore
300 buf.clear();
301 }
302 status
303 });
304
305 #[cfg(feature = "trace")]
306 log::trace!(
307 "{}: rd-status = {status:?} orig:{orig:?} flags:{:?}",
308 st.tag(),
309 st.flags
310 );
311
312 if stopping {
313 stopping_read_status(st, &status)
314 } else {
315 self.process_read_status(orig, status)
316 }
317 }
318
319 /// Processes input that reached the transport-facing read buffer.
320 ///
321 /// `orig` is the size of the destination read buffer before the read.
322 fn process_read_status(&self, orig: usize, status: Poll<io::Result<usize>>) -> IoTaskStatus {
323 let st = self.st();
324
325 // process read buf
326 let result = match status {
327 Poll::Pending => Ok(()),
328 Poll::Ready(status) => status.and_then(|nbytes| {
329 if nbytes == 0 {
330 if st.flags.is_read_eof() {
331 // A clean eof is reported to the filter chain exactly
332 // once, no matter how often the transport reports it.
333 return Ok(());
334 }
335 st.flags.set_read_eof();
336 st.wake_dispatch_task();
337 }
338
339 st.buffer.process_read_buf(&self.0).and_then(|status| {
340 let size = st.buffer.read_dst_size();
341
342 // The destination read buffer has new data, wake up the dispatcher
343 if size > orig {
344 if st.is_rd_backpressure_needed(size) {
345 log::trace!("{}: Read buf({size}), enable back-pressure", st.tag());
346 st.flags.set_read_ready_and_backpressure();
347 } else {
348 st.flags.set_read_ready();
349 }
350 #[cfg(feature = "trace")]
351 log::trace!("{}: New {size} bytes available", st.tag());
352 st.wake_dispatch_task();
353 }
354
355 if st.flags.is_read_notify() {
356 // If the "notify" flag is set, we must wake the
357 // dispatcher task whenever data is read from the source.
358 st.wake_dispatch_task();
359 st.flags.set_read_notified();
360 }
361
362 // A filter may write data while processing reads, for
363 // example a TLS handshake record. Such output can land in
364 // an intermediate buffer that `write_buf_size()` does not
365 // account for, so the write chain is forced from the
366 // outermost layer to move it to the transport.
367 if status.wants_write {
368 st.buffer.process_write_buf_force(&self.0)?;
369 self.0.consolidate_write_state(false)?;
370
371 // Output produced by reading, for example replies to
372 // peer pings, must not grow without bound while the
373 // peer does not read. Reads pause until it drains or
374 // the dispatcher asks for more input.
375 if st.is_wr_backpressure_needed(st.transport_outstanding()) {
376 log::trace!("{}: Write buf is full, pause reading", st.tag());
377 st.flags.set_read_wr_backpressure();
378 }
379 }
380
381 // The input may be what a filter waits for to complete its
382 // shutdown, e.g. the peer's TLS close_notify, which is
383 // polled by the read task. Only actual input wakes it, a
384 // pending read would make the read task spin until the
385 // shutdown deadline.
386 if st.flags.is_shutting_down_filters() {
387 st.wake_read_task();
388 }
389 Ok(())
390 })
391 }),
392 };
393
394 if let Err(err) = result {
395 // A read failure while the filters are shutting down does not
396 // terminate the connection: the filter handshake cannot complete,
397 // but buffered output is still drained by the transport shutdown
398 // phase.
399 if st.flags.is_stopping_filters() {
400 // Output the filters produced before the failure, for example
401 // a close notification, may sit in an intermediate buffer that
402 // the transport shutdown phase does not drain.
403 let _ = st.buffer.process_write_buf_force(&self.0);
404 stop_filters(st, Some(err));
405 IoTaskStatus::Pause
406 } else {
407 st.terminate_connection(Some(err));
408 IoTaskStatus::Stop
409 }
410 } else if st.flags.is_aborted() {
411 IoTaskStatus::Stop
412 } else if st.flags.is_read_eof()
413 || st.flags.is_read_paused_or_backpressure()
414 || st.flags.is_read_filter_paused()
415 || (st.flags.is_read_wr_backpressure() && !st.flags.is_stopping_filters())
416 {
417 IoTaskStatus::Pause
418 } else {
419 IoTaskStatus::Io
420 }
421 }
422
423 /// Provides mutable access to the transport-facing write destination.
424 ///
425 /// This holds the encoded bytes that are ready to be written out.
426 ///
427 /// Pending filter output is processed before `f` is invoked. The transport
428 /// may write bytes out directly, or take ownership of pages and write them
429 /// later; any page it removes is counted as in-flight output until it is
430 /// either returned to this buffer or reported as written through
431 /// [`update_write_status`](Self::update_write_status).
432 ///
433 /// # Panics
434 ///
435 /// Panics if the closure accesses the transport-facing write buffer again.
436 pub fn with_write_dst<F, R>(&self, f: F) -> R
437 where
438 F: FnOnce(&mut BytePages) -> R,
439 {
440 let st = self.st();
441
442 // Write buffer processing may be delayed
443 if let Err(e) = st.buffer.process_write_buf(&self.0) {
444 st.terminate_connection(Some(e));
445 }
446
447 let before = st.buffer.write_buf_size();
448 let result = st.buffer.with_write_dst(|buffer| f(buffer));
449 st.track_wr_inflight(before, st.buffer.write_buf_size());
450
451 result
452 }
453
454 /// Updates the write status.
455 ///
456 /// `Ok(n)` reports that the write attempt completed without error and that
457 /// `n` bytes reached the peer; `n` is zero when the attempt moved nothing.
458 /// Any page the transport is still holding stays counted as outstanding
459 /// output, so it must either be returned to the write buffer or reported
460 /// here. An error terminates the connection. The returned
461 /// [`IoTaskStatus`] instructs the write task to continue, pause until
462 /// notified, or stop.
463 pub fn update_write_status(&self, status: io::Result<usize>) -> IoTaskStatus {
464 let st = &self.st();
465
466 #[cfg(feature = "trace")]
467 log::trace!(
468 "{}: write-status == {status:?} buf:{} inflight:{} flags:{:?}",
469 st.tag(),
470 st.buffer.write_buf_size(),
471 st.wr_inflight.get(),
472 st.flags
473 );
474
475 match status {
476 Ok(written) => {
477 st.wr_inflight_written(written);
478
479 let len = st.buffer.write_buf_size();
480 let outstanding = st.write_outstanding();
481
482 // Full flush is active
483 if st.flags.is_write_flush() {
484 // All output must reach the peer, including in-flight pages
485 if outstanding == 0 {
486 st.wake_dispatch_task();
487 }
488 } else if st.flags.is_wr_backpressure()
489 && st.should_disable_wr_backpressure(outstanding)
490 {
491 // Write backpressure is active and outstanding output is
492 // below the threshold
493 st.wake_dispatch_task();
494 }
495
496 // Producers waiting for the write back-pressure release, the
497 // flag itself is released by the dispatcher. Repeated calls
498 // until then are cheap, a notified waiter's waker is consumed.
499 if st.flags.is_wr_backpressure() && st.should_disable_wr_backpressure(outstanding) {
500 st.wake_write_waiters();
501 }
502
503 // Reads paused by their own output resume once it drains
504 if st.flags.is_read_wr_backpressure()
505 && st.should_disable_wr_backpressure(st.transport_outstanding())
506 {
507 st.flags.unset_read_wr_backpressure();
508 st.wake_read_task();
509 }
510
511 if st.flags.is_aborted() {
512 IoTaskStatus::Stop
513 } else if len == 0 {
514 // Nothing left to submit, pause the write task. In-flight
515 // pages are not actionable here, their completion wakes
516 // the task again.
517 st.flags.set_write_paused();
518 if st.flags.is_stopping_filters() {
519 st.wake_read_task();
520 }
521 if st.flags.is_stopping() && outstanding == 0 {
522 // The transport shutdown phase ends once buffered
523 // output is drained, but only `poll_write_ready`
524 // reports that, so the write task has to run once more
525 // to observe it. A backend that drives both directions
526 // from a single task is covered by the read wake above,
527 // one that splits them is not.
528 st.wake_write_task();
529 }
530 IoTaskStatus::Pause
531 } else {
532 st.flags.unset_write_paused();
533 if st.flags.is_write_filter_paused() {
534 // the write task waits for the filter chain, it is
535 // not paused for lack of output
536 IoTaskStatus::Pause
537 } else {
538 IoTaskStatus::Io
539 }
540 }
541 }
542 Err(err) => {
543 st.terminate_connection(Some(err));
544 IoTaskStatus::Stop
545 }
546 }
547 }
548
549 /// Drives the filter shutdown phase.
550 ///
551 /// This is polled from [`poll_read_ready`](Self::poll_read_ready), so the
552 /// read task advances the phase, and does nothing unless it is active.
553 /// Both directions stay open here: a filter may emit its closing data and
554 /// still read the peer's.
555 ///
556 /// The phase ends once every filter reports ready and its output has
557 /// reached the transport, and the transport shutdown phase begins. It is
558 /// also ended early when the filters cannot finish: after a clean read EOF,
559 /// because no further input can arrive, which is a normal close rather than
560 /// an error; when reads are paused or back-pressured, which is reported as
561 /// a blocked shutdown; and when the shutdown timeout elapses. An I/O error
562 /// terminates the connection instead.
563 ///
564 /// The deadline is kept once it has expired so that
565 /// [`poll_shutdown_deadline`](Self::poll_shutdown_deadline) sees it
566 /// expired, which is what makes one `shutdown_timeout` bound both
567 /// phases.
568 fn poll_filters_shutdown(&self, cx: &mut Context<'_>) {
569 let st = &self.st();
570 if !st.flags.is_shutting_down_filters() {
571 return;
572 }
573
574 // process filter shutdown
575 let ready = match st.buffer.process_shutdown(&self.0) {
576 Ok(Poll::Ready(())) => true,
577 Ok(Poll::Pending) => false,
578 Err(err) => {
579 st.terminate_connection(Some(err));
580 return;
581 }
582 };
583 if self.0.consolidate_write_state(true).is_err() {
584 return;
585 }
586
587 // all pending output has reached the transport
588 let flushed = st.flags.is_write_paused() && !st.flags.is_wr_send_scheduled();
589
590 #[cfg(feature = "trace")]
591 log::trace!(
592 "{}: shutdown filters, done:{ready:?} flushed:{flushed:?} wr-buf:{:?}, flags:{:?}",
593 st.tag(),
594 st.buffer.write_buf_size(),
595 st.flags,
596 );
597
598 // filters are shutdown and write task is paused
599 if ready && flushed {
600 st.filters_stopped();
601 return;
602 }
603
604 // After a clean read EOF no further input can arrive, so a filter that
605 // is waiting for the peer can never finish. The peer closing first is
606 // a normal close, so this is not reported as an error.
607 let eof = !ready && st.flags.is_read_eof();
608
609 // If the read buffer is not consumed it is unlikely that the filter
610 // will ever complete its shutdown. Back-pressure counts on its own,
611 // even once the dispatcher has taken the buffered input: reads pause
612 // under it, so the transport would neither read the input the filter
613 // waits for nor arm read interest for it.
614 let blocked =
615 !ready && !eof && (st.flags.is_read_paused() || st.flags.is_rd_backpressure());
616
617 // The filter shutdown cannot complete. Move on to the transport
618 // shutdown phase, which drains whatever output has been produced so far
619 // and then closes the connection.
620 if eof || blocked {
621 if eof {
622 log::debug!("{}: Peer closed before filter shutdown completed", st.tag());
623 }
624 stop_filters(st, blocked.then(blocked_err));
625 return;
626 }
627
628 // filter shutdown timeout
629 let timeout = st
630 .shutdown_timeout
631 .take()
632 .unwrap_or_else(|| sleep(st.cfg.shutdown_timeout()));
633 if timeout.poll_elapsed(cx).is_ready() {
634 stop_filters(
635 st,
636 Some(io::Error::new(
637 io::ErrorKind::TimedOut,
638 "filter shutdown timed out",
639 )),
640 );
641 }
642 // the deadline is put back even once it has elapsed, so that the
643 // transport shutdown phase sees it expired instead of starting a
644 // second one
645 st.shutdown_timeout.set(Some(timeout));
646 }
647
648 /// Polls the shutdown deadline during the transport shutdown phase.
649 ///
650 /// The deadline is created when filter shutdown starts and is not reset
651 /// here, so a single `shutdown_timeout` bounds both shutdown phases. When
652 /// it elapses the connection is terminated and any output that has not
653 /// reached the transport is lost.
654 fn poll_shutdown_deadline(&self, cx: &mut Context<'_>) {
655 let st = &self.st();
656 if !st.flags.is_stopping() {
657 return;
658 }
659
660 // Nothing is left to drain, so the connection closes cleanly on the
661 // next readiness check and the deadline does not apply. Without this
662 // the shutdown would be reported as timed out whenever the filter
663 // phase happened to consume the whole deadline.
664 if st.write_outstanding() == 0 {
665 return;
666 }
667
668 let timeout = st
669 .shutdown_timeout
670 .take()
671 .unwrap_or_else(|| sleep(st.cfg.shutdown_timeout()));
672 if timeout.poll_elapsed(cx).is_ready() {
673 let len = st.write_outstanding();
674 if len != 0 {
675 log::warn!(
676 "{}: Shutdown timed out, discarding {len} bytes of buffered output",
677 st.tag()
678 );
679 }
680 st.terminate_connection(Some(io::Error::new(
681 io::ErrorKind::TimedOut,
682 "io shutdown timed out",
683 )));
684 } else {
685 st.shutdown_timeout.set(Some(timeout));
686 }
687 }
688}
689
690fn blocked_err() -> io::Error {
691 io::Error::other("filter shutdown blocked by unread buffered data")
692}
693
694/// Leaves the filter shutdown phase after an incomplete shutdown.
695///
696/// Output that has not reached the transport is not lost: the transport
697/// shutdown phase drains it before closing the connection.
698///
699/// The error is recorded here rather than when the failure is first
700/// detected: while an error is set, `IoRef::consolidate_write_state()`
701/// short-circuits, which would stop the write buffer from draining.
702fn stop_filters(st: &IoState, err: Option<io::Error>) {
703 if let Some(err) = err {
704 st.set_shutdown_error(err);
705 }
706 st.filters_stopped();
707}
708
709/// Adapts the connection's read page size to a transport read.
710fn track_read(st: &IoState, buf: &BytesMut, status: &Poll<io::Result<usize>>) {
711 match status {
712 Poll::Ready(Ok(n)) => st.track_read(*n, *n != 0 && buf.len() == buf.capacity()),
713 Poll::Pending => st.track_read(0, false),
714 Poll::Ready(Err(_)) => {}
715 }
716}
717
718/// Reports a read that completed during the transport shutdown phase.
719///
720/// Neither a clean eof nor an error terminates the connection: the write
721/// side keeps draining until it completes or the shutdown deadline elapses.
722fn stopping_read_status(st: &IoState, status: &Poll<io::Result<usize>>) -> IoTaskStatus {
723 match status {
724 Poll::Ready(Ok(n)) if *n != 0 => IoTaskStatus::Io,
725 Poll::Ready(_) => {
726 st.flags.set_read_eof();
727 IoTaskStatus::Pause
728 }
729 Poll::Pending => IoTaskStatus::Pause,
730 }
731}
732
733impl Clone for IoContext {
734 fn clone(&self) -> Self {
735 Self(self.0.clone())
736 }
737}
738
739impl fmt::Debug for IoContext {
740 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
741 f.debug_struct("IoContext").field("io", &self.0).finish()
742 }
743}
744
745#[cfg(test)]
746mod tests {
747 use super::*;
748 use crate::{FilterBuf, FilterLayer, Io, testing::IoTest};
749 use ntex_util::future::lazy;
750
751 #[ntex::test]
752 async fn ctx_basics() {
753 let (_, server) = IoTest::create();
754
755 let state = Io::from(server);
756 let ctx = IoContext::new(state.get_ref());
757 let _ = ctx.flags();
758 assert_ne!(ctx.id(), Id::default());
759 assert!(format!("{ctx:?}").contains("IoContext"));
760 }
761
762 #[ntex::test]
763 async fn pending_read_completion_is_not_eof() {
764 let (_, server) = IoTest::create();
765 let state = Io::from(server);
766 let ctx = IoContext::new(state.get_ref());
767
768 assert!(lazy(|cx| state.poll_read_more(cx)).await.is_pending());
769 assert_ne!(
770 ctx.release_read_buf(ctx.take_read_buf(), Poll::Pending),
771 IoTaskStatus::Stop
772 );
773 assert!(lazy(|cx| state.poll_read_more(cx)).await.is_pending());
774
775 assert_eq!(
776 ctx.release_read_buf(ctx.take_read_buf(), Poll::Ready(Ok(0))),
777 IoTaskStatus::Pause
778 );
779 assert!(matches!(
780 lazy(|cx| state.poll_read_more(cx)).await,
781 Poll::Ready(Ok(None))
782 ));
783 }
784
785 #[derive(Debug)]
786 struct FinishOnEof;
787
788 impl FilterLayer for FinishOnEof {
789 fn process_read_buf(&self, buf: &FilterBuf<'_>) -> io::Result<()> {
790 if buf.io().is_read_eof() {
791 buf.with_read_buffers(|_, dst| dst.extend_from_slice(b"final"));
792 }
793 Ok(())
794 }
795
796 fn process_write_buf(&self, _: &FilterBuf<'_>) -> io::Result<()> {
797 Ok(())
798 }
799 }
800
801 #[ntex::test]
802 async fn eof_reports_available_input_once() {
803 let (_, server) = IoTest::create();
804 let state = Io::from(server);
805 let ctx = IoContext::new(state.get_ref());
806
807 // data arrives, then a clean eof
808 ctx.release_read_buf(BytesMut::copy_from_slice(b"12345"), Poll::Ready(Ok(5)));
809 ctx.release_read_buf(ctx.take_read_buf(), Poll::Ready(Ok(0)));
810
811 // the buffered input is reported once
812 assert!(matches!(
813 lazy(|cx| state.poll_read_more(cx)).await,
814 Poll::Ready(Ok(Some(())))
815 ));
816
817 // accessing the buffer marks the input as reported
818 assert_eq!(state.with_read_dst(|b| b.len()), 5);
819 assert!(matches!(
820 lazy(|cx| state.poll_read_more(cx)).await,
821 Poll::Ready(Ok(None))
822 ));
823
824 // "no further input" does not mean the read buffer is empty, the
825 // remaining bytes are still decodable
826 assert_eq!(state.with_read_dst(BytesMut::take), b"12345");
827 }
828
829 #[ntex::test]
830 async fn shutdown_keeps_unconsumed_input_visible() {
831 let (_, server) = IoTest::create();
832 let state = Io::from(server);
833 let ctx = IoContext::new(state.get_ref());
834
835 // input arrives but the dispatcher has not consumed it yet
836 ctx.release_read_buf(BytesMut::copy_from_slice(b"12345"), Poll::Ready(Ok(5)));
837 assert!(ctx.flags().is_read_ready());
838
839 // starting a shutdown must not discard the "input available" signal
840 assert!(lazy(|cx| state.poll_shutdown(cx)).await.is_pending());
841 assert!(ctx.flags().is_read_ready());
842
843 // so the read task is handed a fresh buffer instead of the one the
844 // dispatcher still has to decode
845 assert!(ctx.take_read_buf().is_empty());
846 assert_eq!(state.with_read_dst(BytesMut::take), b"12345");
847 }
848
849 #[ntex::test]
850 async fn clean_eof_is_processed_by_filters_once() {
851 let (_, server) = IoTest::create();
852 let state = Io::from(server).add_filter(FinishOnEof);
853 let ctx = IoContext::new(state.get_ref());
854
855 for _ in 0..3 {
856 assert_eq!(
857 ctx.release_read_buf(ctx.take_read_buf(), Poll::Ready(Ok(0))),
858 IoTaskStatus::Pause
859 );
860 assert!(state.is_read_eof());
861 }
862 assert_eq!(state.with_read_dst(BytesMut::take), b"final");
863 }
864
865 #[ntex::test]
866 async fn clean_eof_is_processed_by_filters() {
867 let (_, server) = IoTest::create();
868 let state = Io::from(server).add_filter(FinishOnEof);
869 let ctx = IoContext::new(state.get_ref());
870
871 assert!(lazy(|cx| state.poll_read_notify(cx)).await.is_pending());
872 assert_eq!(
873 ctx.release_read_buf(ctx.take_read_buf(), Poll::Ready(Ok(0))),
874 IoTaskStatus::Pause
875 );
876 assert!(state.is_read_eof());
877 assert!(matches!(
878 lazy(|cx| state.poll_read_notify(cx)).await,
879 Poll::Ready(Ok(Some(())))
880 ));
881 assert!(matches!(
882 lazy(|cx| state.poll_read_notify(cx)).await,
883 Poll::Ready(Ok(None))
884 ));
885 assert_eq!(state.with_read_dst(BytesMut::take), b"final");
886 assert!(matches!(
887 lazy(|cx| state.poll_read_more(cx)).await,
888 Poll::Ready(Ok(None))
889 ));
890 }
891
892 #[derive(Debug)]
893 struct RejectEof;
894
895 impl FilterLayer for RejectEof {
896 fn process_read_buf(&self, buf: &FilterBuf<'_>) -> io::Result<()> {
897 if buf.io().is_read_eof() {
898 Err(io::Error::new(
899 io::ErrorKind::UnexpectedEof,
900 "truncated filtered stream",
901 ))
902 } else {
903 Ok(())
904 }
905 }
906
907 fn process_write_buf(&self, _: &FilterBuf<'_>) -> io::Result<()> {
908 Ok(())
909 }
910 }
911
912 #[ntex::test]
913 async fn clean_eof_filter_error_terminates_connection() {
914 let (_, server) = IoTest::create();
915 let state = Io::from(server).add_filter(RejectEof);
916 let ctx = IoContext::new(state.get_ref());
917
918 assert_eq!(
919 ctx.release_read_buf(ctx.take_read_buf(), Poll::Ready(Ok(0))),
920 IoTaskStatus::Stop
921 );
922 assert!(state.is_read_eof());
923 assert!(state.flags().is_terminating());
924 }
925
926 #[ntex::test]
927 async fn filter_shutdown_does_not_spin_on_pending_read() {
928 use crate::{Handle, IoStream};
929 use std::{cell::Cell, future::poll_fn, rc::Rc};
930
931 // transport that never makes progress in either direction
932 struct Stalled(Rc<Cell<usize>>);
933
934 impl IoStream for Stalled {
935 fn start(self, ctx: IoContext) -> Box<dyn Handle> {
936 let polls = self.0.clone();
937 ntex_util::spawn(async move {
938 poll_fn(|cx| {
939 polls.set(polls.get() + 1);
940 if let Poll::Ready(Readiness::Ready) = ctx.poll_read_ready(cx) {
941 let _ = ctx.with_read_buf(|_| Poll::Pending);
942 }
943 match ctx.poll_write_ready(cx) {
944 Poll::Ready(Readiness::Ready) => {
945 let _ = ctx.update_write_status(Ok(0));
946 Poll::Pending
947 }
948 Poll::Ready(_) => Poll::Ready(()),
949 Poll::Pending => Poll::Pending,
950 }
951 })
952 .await;
953 ctx.stopped(None);
954 });
955 Box::new(Stalled(self.0))
956 }
957 }
958
959 impl Handle for Stalled {}
960
961 let polls = Rc::new(Cell::new(0));
962 let io = Io::new(
963 Stalled(polls.clone()),
964 ntex_service::cfg::SharedCfg::default(),
965 );
966 io.encode_slice(b"data").unwrap();
967 ntex_util::time::sleep(ntex_util::time::Millis(20)).await;
968
969 // graceful shutdown cannot flush the output, it waits for the deadline
970 io.close();
971 let start = polls.get();
972 ntex_util::time::sleep(ntex_util::time::Millis(100)).await;
973 assert!(
974 polls.get() - start < 10,
975 "io task polled {} times",
976 polls.get() - start
977 );
978 }
979
980 /// Transport driven by the test through an `IoContext`.
981 struct Manual;
982
983 impl crate::IoStream for Manual {
984 fn start(self, _: IoContext) -> Box<dyn crate::Handle> {
985 Box::new(Manual)
986 }
987 }
988
989 impl crate::Handle for Manual {}
990
991 #[ntex::test]
992 async fn take_read_buf_reuses_consumed_buffer() {
993 let io = Io::new(Manual, ntex_service::cfg::SharedCfg::default());
994 let ctx = IoContext::new(io.get_ref());
995 assert_eq!(ctx.shutdown_timeout(), io.cfg().shutdown_timeout());
996 assert!(io.query::<u32>().get().is_none());
997
998 let mut buf = ctx.take_read_buf();
999 buf.extend_from_slice(b"12345");
1000 assert_eq!(
1001 ctx.release_read_buf(buf, Poll::Ready(Ok(5))),
1002 IoTaskStatus::Io
1003 );
1004 assert_eq!(io.with_read_dst(|b| b.split_to(3)), b"123");
1005
1006 // the dispatcher consumed the input, the partly filled buffer is
1007 // handed to the transport
1008 let mut buf = ctx.take_read_buf();
1009 assert_eq!(buf, b"45");
1010 buf.reserve_more();
1011 assert!(buf.capacity() - buf.len() >= io.get_ref().0.read_size().low());
1012 buf.extend_from_slice(b"6");
1013 assert_eq!(
1014 ctx.release_read_buf(buf, Poll::Ready(Ok(1))),
1015 IoTaskStatus::Io
1016 );
1017 assert_eq!(io.with_read_dst(BytesMut::take), b"456");
1018 }
1019
1020 #[ntex::test]
1021 async fn reads_are_discarded_in_transport_shutdown_phase() {
1022 let io = Io::new(Manual, ntex_service::cfg::SharedCfg::default());
1023 let ctx = IoContext::new(io.get_ref());
1024
1025 // no filter work and no output, the filter phase ends at once
1026 io.close();
1027 assert!(lazy(|cx| ctx.poll_read_ready(cx)).await.is_pending());
1028 assert!(ctx.flags().is_stopping());
1029
1030 // input is discarded, reads keep going until eof
1031 let mut buf = ctx.take_read_buf();
1032 buf.extend_from_slice(b"12345");
1033 assert_eq!(
1034 ctx.release_read_buf(buf, Poll::Ready(Ok(5))),
1035 IoTaskStatus::Io
1036 );
1037 assert_eq!(
1038 ctx.with_read_buf(|buf| {
1039 buf.extend_from_slice(b"678");
1040 Poll::Ready(Ok(3))
1041 }),
1042 IoTaskStatus::Io
1043 );
1044 assert_eq!(io.with_read_dst(|b| b.len()), 0);
1045
1046 assert_eq!(
1047 ctx.release_read_buf(ctx.take_read_buf(), Poll::Pending),
1048 IoTaskStatus::Pause
1049 );
1050 assert_eq!(ctx.with_read_buf(|_| Poll::Pending), IoTaskStatus::Pause);
1051 assert!(!io.is_read_eof());
1052
1053 // a read error does not terminate the connection
1054 assert_eq!(
1055 ctx.release_read_buf(
1056 ctx.take_read_buf(),
1057 Poll::Ready(Err(io::Error::other("err")))
1058 ),
1059 IoTaskStatus::Pause
1060 );
1061 assert!(io.is_read_eof());
1062 assert!(!ctx.flags().is_terminating());
1063
1064 assert_eq!(
1065 ctx.with_read_buf(|_| Poll::Ready(Ok(0))),
1066 IoTaskStatus::Pause
1067 );
1068
1069 // nothing left to drain, the transport closes the connection
1070 assert_eq!(
1071 lazy(|cx| ctx.poll_write_ready(cx)).await,
1072 Poll::Ready(Readiness::Close)
1073 );
1074 ctx.stopped(None);
1075 assert!(io.is_closed());
1076 }
1077
1078 #[derive(Debug)]
1079 struct FailShutdown;
1080
1081 impl FilterLayer for FailShutdown {
1082 fn process_read_buf(&self, _: &FilterBuf<'_>) -> io::Result<()> {
1083 Ok(())
1084 }
1085
1086 fn process_write_buf(&self, _: &FilterBuf<'_>) -> io::Result<()> {
1087 Ok(())
1088 }
1089
1090 fn shutdown(&self, _: &FilterBuf<'_>) -> io::Result<Poll<()>> {
1091 Err(io::Error::other("shutdown failed"))
1092 }
1093 }
1094
1095 #[ntex::test]
1096 async fn filter_shutdown_error_terminates_connection() {
1097 let io = Io::new(Manual, ntex_service::cfg::SharedCfg::default()).add_filter(FailShutdown);
1098 let ctx = IoContext::new(io.get_ref());
1099 // neither the layer nor the transport provide values
1100 assert!(io.query::<u32>().get().is_none());
1101
1102 io.close();
1103 assert_eq!(
1104 lazy(|cx| ctx.poll_read_ready(cx)).await,
1105 Poll::Ready(Readiness::Close)
1106 );
1107 assert!(ctx.flags().is_terminating());
1108 ctx.stopped(None);
1109 let err = io.shutdown().await.unwrap_err();
1110 assert_eq!(err.to_string(), "shutdown failed");
1111 }
1112
1113 #[ntex::test]
1114 async fn read_after_termination_stops_read_task() {
1115 let io = Io::new(Manual, ntex_service::cfg::SharedCfg::default());
1116 let ctx = IoContext::new(io.get_ref());
1117 let ctx2 = ctx.clone();
1118 assert_eq!(ctx.id(), ctx2.id());
1119
1120 ctx.stop(Some(io::Error::other("failed")));
1121 let mut buf = ctx2.take_read_buf();
1122 buf.extend_from_slice(b"1");
1123 assert_eq!(
1124 ctx2.release_read_buf(buf, Poll::Ready(Ok(1))),
1125 IoTaskStatus::Stop
1126 );
1127 }
1128}