diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..8d9da9aea --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,942 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'tide'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=tide" + ], + "filter": { + "name": "tide", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'cookies'", + "cargo": { + "args": [ + "build", + "--example=cookies", + "--package=tide" + ], + "filter": { + "name": "cookies", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'cookies'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=cookies", + "--package=tide" + ], + "filter": { + "name": "cookies", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'sessions'", + "cargo": { + "args": [ + "build", + "--example=sessions", + "--package=tide" + ], + "filter": { + "name": "sessions", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'sessions'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=sessions", + "--package=tide" + ], + "filter": { + "name": "sessions", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'chunked'", + "cargo": { + "args": [ + "build", + "--example=chunked", + "--package=tide" + ], + "filter": { + "name": "chunked", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'chunked'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=chunked", + "--package=tide" + ], + "filter": { + "name": "chunked", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'sse'", + "cargo": { + "args": [ + "build", + "--example=sse", + "--package=tide" + ], + "filter": { + "name": "sse", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'sse'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=sse", + "--package=tide" + ], + "filter": { + "name": "sse", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'fib'", + "cargo": { + "args": [ + "build", + "--example=fib", + "--package=tide" + ], + "filter": { + "name": "fib", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'fib'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=fib", + "--package=tide" + ], + "filter": { + "name": "fib", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'redirect'", + "cargo": { + "args": [ + "build", + "--example=redirect", + "--package=tide" + ], + "filter": { + "name": "redirect", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'redirect'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=redirect", + "--package=tide" + ], + "filter": { + "name": "redirect", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'upload'", + "cargo": { + "args": [ + "build", + "--example=upload", + "--package=tide" + ], + "filter": { + "name": "upload", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'upload'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=upload", + "--package=tide" + ], + "filter": { + "name": "upload", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'hello'", + "cargo": { + "args": [ + "build", + "--example=hello", + "--package=tide" + ], + "filter": { + "name": "hello", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'hello'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=hello", + "--package=tide" + ], + "filter": { + "name": "hello", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'graphql'", + "cargo": { + "args": [ + "build", + "--example=graphql", + "--package=tide" + ], + "filter": { + "name": "graphql", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'graphql'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=graphql", + "--package=tide" + ], + "filter": { + "name": "graphql", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'json'", + "cargo": { + "args": [ + "build", + "--example=json", + "--package=tide" + ], + "filter": { + "name": "json", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'json'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=json", + "--package=tide" + ], + "filter": { + "name": "json", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'nested'", + "cargo": { + "args": [ + "build", + "--example=nested", + "--package=tide" + ], + "filter": { + "name": "nested", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'nested'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=nested", + "--package=tide" + ], + "filter": { + "name": "nested", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'concurrent_listeners'", + "cargo": { + "args": [ + "build", + "--example=concurrent_listeners", + "--package=tide" + ], + "filter": { + "name": "concurrent_listeners", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'concurrent_listeners'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=concurrent_listeners", + "--package=tide" + ], + "filter": { + "name": "concurrent_listeners", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'catflap'", + "cargo": { + "args": [ + "build", + "--example=catflap", + "--package=tide" + ], + "filter": { + "name": "catflap", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'catflap'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=catflap", + "--package=tide" + ], + "filter": { + "name": "catflap", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'middleware'", + "cargo": { + "args": [ + "build", + "--example=middleware", + "--package=tide" + ], + "filter": { + "name": "middleware", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'middleware'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=middleware", + "--package=tide" + ], + "filter": { + "name": "middleware", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'static_file'", + "cargo": { + "args": [ + "build", + "--example=static_file", + "--package=tide" + ], + "filter": { + "name": "static_file", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'static_file'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=static_file", + "--package=tide" + ], + "filter": { + "name": "static_file", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'error_handling'", + "cargo": { + "args": [ + "build", + "--example=error_handling", + "--package=tide" + ], + "filter": { + "name": "error_handling", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'error_handling'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=error_handling", + "--package=tide" + ], + "filter": { + "name": "error_handling", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'cookies'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=cookies", + "--package=tide" + ], + "filter": { + "name": "cookies", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'nested'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=nested", + "--package=tide" + ], + "filter": { + "name": "nested", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'sessions'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=sessions", + "--package=tide" + ], + "filter": { + "name": "sessions", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'response'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=response", + "--package=tide" + ], + "filter": { + "name": "response", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'chunked-encode-large'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=chunked-encode-large", + "--package=tide" + ], + "filter": { + "name": "chunked-encode-large", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'chunked-encode-small'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=chunked-encode-small", + "--package=tide" + ], + "filter": { + "name": "chunked-encode-small", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'unix'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=unix", + "--package=tide" + ], + "filter": { + "name": "unix", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'log'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=log", + "--package=tide" + ], + "filter": { + "name": "log", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'server'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=server", + "--package=tide" + ], + "filter": { + "name": "server", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'params'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=params", + "--package=tide" + ], + "filter": { + "name": "params", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'test_utils'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=test_utils", + "--package=tide" + ], + "filter": { + "name": "test_utils", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'route_middleware'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=route_middleware", + "--package=tide" + ], + "filter": { + "name": "route_middleware", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'function_middleware'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=function_middleware", + "--package=tide" + ], + "filter": { + "name": "function_middleware", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'endpoint'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=endpoint", + "--package=tide" + ], + "filter": { + "name": "endpoint", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'serve_dir'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=serve_dir", + "--package=tide" + ], + "filter": { + "name": "serve_dir", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'wildcard'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=wildcard", + "--package=tide" + ], + "filter": { + "name": "wildcard", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug benchmark 'router'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bench=router", + "--package=tide" + ], + "filter": { + "name": "router", + "kind": "bench" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 5d93415c4..285991dcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ async-sse = "4.0.1" async-std = { version = "1.6.5", features = ["unstable"] } async-trait = "0.1.41" femme = { version = "2.1.1", optional = true } +futures-lite = "1.11.2" futures-util = "0.3.6" http-client = { version = "6.1.0", default-features = false } http-types = "2.5.0" diff --git a/src/cancelation_token.rs b/src/cancelation_token.rs new file mode 100644 index 000000000..b446c255c --- /dev/null +++ b/src/cancelation_token.rs @@ -0,0 +1,76 @@ +use std::cell::RefCell; +use std::future::Future; +use std::pin::Pin; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll, Waker}; + +#[derive(Debug)] +pub struct CancelationToken { + shared_state: Arc> +} + +#[derive(Debug)] +struct CancelationTokenState { + canceled: bool, + waker: Option +} + +#[derive(Debug)] +pub struct ReturnOnCancel { + result: Mutex>>, + shared_state: Arc> +} + +/// Future that allows gracefully shutting down the server +impl CancelationToken { + pub fn new() -> CancelationToken { + CancelationToken { + shared_state: Arc::new(Mutex::new(CancelationTokenState { + canceled: false, + waker: None + })) + } + } + + /// Call to shut down the server + pub fn complete(&self) { + let mut shared_state = self.shared_state.lock().unwrap(); + + shared_state.canceled = true; + if let Some(waker) = shared_state.waker.take() { + waker.wake() + } + } + + pub fn return_on_cancel(&self, result: T) -> ReturnOnCancel { + ReturnOnCancel { + result: Mutex::new(RefCell::new(Some(result))), + shared_state: self.shared_state.clone() + } + } +} + +impl Future for ReturnOnCancel { + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut shared_state = self.shared_state.lock().unwrap(); + + if shared_state.canceled { + let result_refcell = self.result.lock().unwrap(); + let result = result_refcell.replace(None).expect("Result was already returned"); + Poll::Ready(result) + } else { + shared_state.waker = Some(cx.waker().clone()); + Poll::Pending + } + } +} + +impl Clone for CancelationToken { + fn clone(&self) -> Self { + CancelationToken { + shared_state: self.shared_state.clone() + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index d59b8565e..f016f32ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,6 +61,7 @@ #![doc(html_favicon_url = "https://yoshuawuyts.com/assets/http-rs/favicon.ico")] #![doc(html_logo_url = "https://yoshuawuyts.com/assets/http-rs/logo-rounded.png")] +mod cancelation_token; #[cfg(feature = "cookies")] mod cookies; mod endpoint; @@ -85,6 +86,7 @@ pub mod utils; #[cfg(feature = "sessions")] pub mod sessions; +pub use cancelation_token::CancelationToken; pub use endpoint::Endpoint; pub use middleware::{Middleware, Next}; pub use redirect::Redirect; diff --git a/src/listener/concurrent_listener.rs b/src/listener/concurrent_listener.rs index 486b21e68..10831de3f 100644 --- a/src/listener/concurrent_listener.rs +++ b/src/listener/concurrent_listener.rs @@ -1,9 +1,9 @@ use crate::listener::{ListenInfo, Listener, ToListener}; -use crate::Server; +use crate::{CancelationToken, Server}; use std::fmt::{self, Debug, Display, Formatter}; -use async_std::io; +use async_std::{io, task}; use futures_util::stream::{futures_unordered::FuturesUnordered, StreamExt}; /// ConcurrentListener allows tide to listen on any number of transports @@ -97,13 +97,24 @@ where Ok(()) } - async fn accept(&mut self) -> io::Result<()> { + async fn accept(&mut self, cancelation_token: CancelationToken) -> io::Result<()> { let mut futures_unordered = FuturesUnordered::new(); + let mut cancelation_tokens = Vec::new(); + for listener in self.listeners.iter_mut() { - futures_unordered.push(listener.accept()); + let sub_cancelation_token = CancelationToken::new(); + futures_unordered.push(listener.accept(sub_cancelation_token.clone())); + cancelation_tokens.push(sub_cancelation_token); } + task::spawn(async move { + cancelation_token.return_on_cancel::<()>(()).await; + for sub_cancelation_token in cancelation_tokens.iter_mut() { + sub_cancelation_token.complete(); + } + }); + while let Some(result) = futures_unordered.next().await { result?; } diff --git a/src/listener/failover_listener.rs b/src/listener/failover_listener.rs index d81658c77..86c3bd7bf 100644 --- a/src/listener/failover_listener.rs +++ b/src/listener/failover_listener.rs @@ -1,5 +1,5 @@ use crate::listener::{Listener, ToListener}; -use crate::Server; +use crate::{CancelationToken, Server}; use std::fmt::{self, Debug, Display, Formatter}; @@ -123,11 +123,11 @@ where )) } - async fn accept(&mut self) -> io::Result<()> { + async fn accept(&mut self, cancelation_token: CancelationToken) -> io::Result<()> { match self.index { Some(index) => { let mut listener = self.listeners[index].take().expect("accept called twice"); - listener.accept().await?; + listener.accept(cancelation_token).await?; Ok(()) } None => Err(io::Error::new( diff --git a/src/listener/mod.rs b/src/listener/mod.rs index 2d469872d..00a84d8d1 100644 --- a/src/listener/mod.rs +++ b/src/listener/mod.rs @@ -17,7 +17,7 @@ use std::fmt::{Debug, Display}; use async_std::io; use async_trait::async_trait; -use crate::Server; +use crate::{CancelationToken, Server}; pub use concurrent_listener::ConcurrentListener; pub use failover_listener::FailoverListener; @@ -46,7 +46,7 @@ where /// Start accepting incoming connections. This method must be called only /// after `bind` has succeeded. - async fn accept(&mut self) -> io::Result<()>; + async fn accept(&mut self, cancelation_token: CancelationToken) -> io::Result<()>; /// Expose information about the connection. This should always return valid /// data after `bind` has succeeded. @@ -63,8 +63,8 @@ where self.as_mut().bind(app).await } - async fn accept(&mut self) -> io::Result<()> { - self.as_mut().accept().await + async fn accept(&mut self, cancelation_token: CancelationToken) -> io::Result<()> { + self.as_mut().accept(cancelation_token).await } fn info(&self) -> Vec { diff --git a/src/listener/parsed_listener.rs b/src/listener/parsed_listener.rs index ad2926a10..14a9fdec7 100644 --- a/src/listener/parsed_listener.rs +++ b/src/listener/parsed_listener.rs @@ -1,7 +1,7 @@ #[cfg(unix)] use super::UnixListener; use super::{ListenInfo, Listener, TcpListener}; -use crate::Server; +use crate::{CancelationToken, Server}; use async_std::io; use std::fmt::{self, Debug, Display, Formatter}; @@ -52,11 +52,11 @@ where } } - async fn accept(&mut self) -> io::Result<()> { + async fn accept(&mut self, cancelation_token: CancelationToken) -> io::Result<()> { match self { #[cfg(unix)] - Self::Unix(u) => u.accept().await, - Self::Tcp(t) => t.accept().await, + Self::Unix(u) => u.accept(cancelation_token).await, + Self::Tcp(t) => t.accept(cancelation_token).await, } } diff --git a/src/listener/tcp_listener.rs b/src/listener/tcp_listener.rs index 9ca3585f8..ba562bb71 100644 --- a/src/listener/tcp_listener.rs +++ b/src/listener/tcp_listener.rs @@ -1,7 +1,7 @@ use super::{is_transient_error, ListenInfo}; use crate::listener::Listener; -use crate::{log, Server}; +use crate::{CancelationToken, log, Server}; use std::fmt::{self, Display, Formatter}; @@ -9,6 +9,8 @@ use async_std::net::{self, SocketAddr, TcpStream}; use async_std::prelude::*; use async_std::{io, task}; +use futures_lite::future; + /// This represents a tide [Listener](crate::listener::Listener) that /// wraps an [async_std::net::TcpListener]. It is implemented as an /// enum in order to allow creation of a tide::listener::TcpListener @@ -88,7 +90,7 @@ where Ok(()) } - async fn accept(&mut self) -> io::Result<()> { + async fn accept(&mut self, cancelation_token: CancelationToken) -> io::Result<()> { let server = self .server .take() @@ -100,7 +102,7 @@ where let mut incoming = listener.incoming(); - while let Some(stream) = incoming.next().await { + while let Some(stream) = future::race(incoming.next(), cancelation_token.return_on_cancel(None)).await { match stream { Err(ref e) if is_transient_error(e) => continue, Err(error) => { diff --git a/src/listener/unix_listener.rs b/src/listener/unix_listener.rs index 6b9c95221..1dffcf56f 100644 --- a/src/listener/unix_listener.rs +++ b/src/listener/unix_listener.rs @@ -1,7 +1,7 @@ use super::{is_transient_error, ListenInfo}; use crate::listener::Listener; -use crate::{log, Server}; +use crate::{CancelationToken, log, Server}; use std::fmt::{self, Display, Formatter}; @@ -86,7 +86,7 @@ where Ok(()) } - async fn accept(&mut self) -> io::Result<()> { + async fn accept(&mut self, _cancelation_token: CancelationToken) -> io::Result<()> { let server = self .server .take() diff --git a/src/server.rs b/src/server.rs index bff2266a3..6963b9176 100644 --- a/src/server.rs +++ b/src/server.rs @@ -9,7 +9,7 @@ use crate::listener::{Listener, ToListener}; use crate::log; use crate::middleware::{Middleware, Next}; use crate::router::{Router, Selection}; -use crate::{Endpoint, Request, Route}; +use crate::{CancelationToken, Endpoint, Request, Route}; /// An HTTP server. /// @@ -206,12 +206,16 @@ where /// # Ok(()) }) } /// ``` pub async fn listen>(self, listener: L) -> io::Result<()> { + self.listen_with_cancelation_token(listener, CancelationToken::new()).await + } + + pub async fn listen_with_cancelation_token>(self, listener: L, cancelation_token: CancelationToken) -> io::Result<()> { let mut listener = listener.to_listener()?; listener.bind(self).await?; for info in listener.info().iter() { log::info!("Server listening on {}", info); } - listener.accept().await?; + listener.accept(cancelation_token).await?; Ok(()) } diff --git a/tests/server.rs b/tests/server.rs index 070557451..61032bc26 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -4,11 +4,16 @@ use async_std::task; use std::time::Duration; use serde::{Deserialize, Serialize}; -use tide::{Body, Request}; +use tide::{Body, CancelationToken, Request}; +// Note: Async tests are now supported. Refactor to avoid tast::block_on? #[test] fn hello_world() -> tide::Result<()> { + task::block_on(async { + let cancelation_token = CancelationToken::new(); + let server_cancelation_token = cancelation_token.clone(); + let port = test_utils::find_port().await; let server = task::spawn(async move { let mut app = tide::new(); @@ -18,22 +23,20 @@ fn hello_world() -> tide::Result<()> { assert!(req.peer_addr().is_some()); Ok("says hello") }); - app.listen(("localhost", port)).await?; + app.listen_with_cancelation_token(("localhost", port), server_cancelation_token).await?; Result::<(), http_types::Error>::Ok(()) }); - let client = task::spawn(async move { - task::sleep(Duration::from_millis(100)).await; - let string = surf::get(format!("http://localhost:{}", port)) - .body(Body::from_string("nori".to_string())) - .recv_string() - .await - .unwrap(); - assert_eq!(string, "says hello"); - Ok(()) - }); + task::sleep(Duration::from_millis(100)).await; + let string = surf::get(format!("http://localhost:{}", port)) + .body(Body::from_string("nori".to_string())) + .recv_string() + .await + .unwrap(); + assert_eq!(string, "says hello"); - server.race(client).await + cancelation_token.complete(); + server.await }) }