mirror of https://github.com/fafhrd91/actix-net
Merge branch 'master' into ups/system-run-until-stop
This commit is contained in:
commit
a9eccaa26f
|
@ -196,13 +196,6 @@ jobs:
|
|||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v1.3.0
|
||||
|
||||
- name: Install cargo-hack
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: cargo-hack
|
||||
|
||||
- name: doc tests
|
||||
uses: actions-rs/cargo@v1
|
||||
timeout-minutes: 40
|
||||
with: { command: ci-doctest }
|
||||
- name: doc tests io-uring
|
||||
run: |
|
||||
sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin && RUSTUP_TOOLCHAIN=nightly cargo ci-doctest"
|
||||
|
|
|
@ -111,7 +111,7 @@
|
|||
[#129]: https://github.com/actix/actix-net/issues/129
|
||||
|
||||
|
||||
## 1.1.0 - 2020-04-08 (YANKED)
|
||||
## 1.1.0 - 2020-04-08 _(YANKED)_
|
||||
* Expose `System::is_set` to check if current system has ben started [#99]
|
||||
* Add `Arbiter::is_running` to check if event loop is running [#124]
|
||||
* Add `Arbiter::local_join` associated function
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 2.0.0-beta.9 - 2021-11-15
|
||||
* Restore `Arbiter` support lost in `beta.8`. [#417]
|
||||
|
||||
[#417]: https://github.com/actix/actix-net/pull/417
|
||||
|
||||
|
||||
## 2.0.0-beta.8 - 2021-11-05 _(YANKED)_
|
||||
* Fix non-unix signal handler. [#410]
|
||||
|
||||
[#410]: https://github.com/actix/actix-net/pull/410
|
||||
|
||||
|
||||
## 2.0.0-beta.7 - 2021-11-05
|
||||
## 2.0.0-beta.7 - 2021-11-05 _(YANKED)_
|
||||
* Server can be started in regular Tokio runtime. [#408]
|
||||
* Expose new `Server` type whose `Future` impl resolves when server stops. [#408]
|
||||
* Rename `Server` to `ServerHandle`. [#407]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "actix-server"
|
||||
version = "2.0.0-beta.7"
|
||||
version = "2.0.0-beta.9"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"fakeshadow <24548779@qq.com>",
|
||||
|
@ -18,7 +18,7 @@ path = "src/lib.rs"
|
|||
|
||||
[features]
|
||||
default = []
|
||||
io-uring = ["actix-rt/io-uring"]
|
||||
io-uring = ["tokio-uring", "actix-rt/io-uring"]
|
||||
|
||||
[dependencies]
|
||||
actix-rt = { version = "2.4.0", default-features = false }
|
||||
|
@ -26,11 +26,16 @@ actix-service = "2.0.0"
|
|||
actix-utils = "3.0.0"
|
||||
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
log = "0.4"
|
||||
mio = { version = "0.7.6", features = ["os-poll", "net"] }
|
||||
num_cpus = "1.13"
|
||||
socket2 = "0.4.2"
|
||||
tokio = { version = "1.5.1", features = ["sync"] }
|
||||
|
||||
# runtime for io-uring feature
|
||||
tokio-uring = { version = "0.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-codec = "0.4.0"
|
||||
actix-rt = "2.0.0"
|
||||
|
|
|
@ -127,7 +127,7 @@ impl Accept {
|
|||
let mut events = mio::Events::with_capacity(256);
|
||||
|
||||
loop {
|
||||
if let Err(e) = self.poll.poll(&mut events, None) {
|
||||
if let Err(e) = self.poll.poll(&mut events, self.timeout) {
|
||||
match e.kind() {
|
||||
io::ErrorKind::Interrupted => {}
|
||||
_ => panic!("Poll error: {}", e),
|
||||
|
|
|
@ -8,7 +8,8 @@ use crate::{
|
|||
server::ServerCommand,
|
||||
service::{InternalServiceFactory, ServiceFactory, StreamNewService},
|
||||
socket::{
|
||||
MioListener, MioTcpListener, MioTcpSocket, StdSocketAddr, StdTcpListener, ToSocketAddrs,
|
||||
create_mio_tcp_listener, MioListener, MioTcpListener, StdSocketAddr, StdTcpListener,
|
||||
ToSocketAddrs,
|
||||
},
|
||||
worker::ServerWorkerConfig,
|
||||
Server,
|
||||
|
@ -112,7 +113,7 @@ impl ServerBuilder {
|
|||
self.max_concurrent_connections(num)
|
||||
}
|
||||
|
||||
/// Stop Actix system.
|
||||
/// Stop Actix `System` after server shutdown.
|
||||
pub fn system_exit(mut self) -> Self {
|
||||
self.exit = true;
|
||||
self
|
||||
|
@ -263,7 +264,7 @@ pub(super) fn bind_addr<S: ToSocketAddrs>(
|
|||
let mut success = false;
|
||||
let mut sockets = Vec::new();
|
||||
for addr in addr.to_socket_addrs()? {
|
||||
match create_tcp_listener(addr, backlog) {
|
||||
match create_mio_tcp_listener(addr, backlog) {
|
||||
Ok(lst) => {
|
||||
success = true;
|
||||
sockets.push(lst);
|
||||
|
@ -283,14 +284,3 @@ pub(super) fn bind_addr<S: ToSocketAddrs>(
|
|||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tcp_listener(addr: StdSocketAddr, backlog: u32) -> io::Result<MioTcpListener> {
|
||||
let socket = match addr {
|
||||
StdSocketAddr::V4(_) => MioTcpSocket::new_v4()?,
|
||||
StdSocketAddr::V6(_) => MioTcpSocket::new_v6()?,
|
||||
};
|
||||
|
||||
socket.set_reuseaddr(true)?;
|
||||
socket.bind(addr)?;
|
||||
socket.listen(backlog)
|
||||
}
|
||||
|
|
|
@ -42,10 +42,12 @@ impl ServerHandle {
|
|||
/// Stop incoming connection processing, stop all workers and exit.
|
||||
pub fn stop(&self, graceful: bool) -> impl Future<Output = ()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
let _ = self.cmd_tx.send(ServerCommand::Stop {
|
||||
graceful,
|
||||
completion: Some(tx),
|
||||
});
|
||||
|
||||
async {
|
||||
let _ = rx.await;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::{
|
|||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use futures_core::future::{BoxFuture, LocalBoxFuture};
|
||||
use futures_core::future::BoxFuture;
|
||||
|
||||
// a poor man's join future. joined future is only used when starting/stopping the server.
|
||||
// pin_project and pinned futures are overkill for this task.
|
||||
|
@ -61,63 +61,6 @@ impl<T> Future for JoinAll<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn join_all_local<T>(
|
||||
fut: Vec<impl Future<Output = T> + 'static>,
|
||||
) -> JoinAllLocal<T> {
|
||||
let fut = fut
|
||||
.into_iter()
|
||||
.map(|f| JoinLocalFuture::LocalFuture(Box::pin(f)))
|
||||
.collect();
|
||||
|
||||
JoinAllLocal { fut }
|
||||
}
|
||||
|
||||
// a poor man's join future. joined future is only used when starting/stopping the server.
|
||||
// pin_project and pinned futures are overkill for this task.
|
||||
pub(crate) struct JoinAllLocal<T> {
|
||||
fut: Vec<JoinLocalFuture<T>>,
|
||||
}
|
||||
|
||||
enum JoinLocalFuture<T> {
|
||||
LocalFuture(LocalBoxFuture<'static, T>),
|
||||
Result(Option<T>),
|
||||
}
|
||||
|
||||
impl<T> Unpin for JoinAllLocal<T> {}
|
||||
|
||||
impl<T> Future for JoinAllLocal<T> {
|
||||
type Output = Vec<T>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let mut ready = true;
|
||||
|
||||
let this = self.get_mut();
|
||||
for fut in this.fut.iter_mut() {
|
||||
if let JoinLocalFuture::LocalFuture(f) = fut {
|
||||
match f.as_mut().poll(cx) {
|
||||
Poll::Ready(t) => {
|
||||
*fut = JoinLocalFuture::Result(Some(t));
|
||||
}
|
||||
Poll::Pending => ready = false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ready {
|
||||
let mut res = Vec::new();
|
||||
for fut in this.fut.iter_mut() {
|
||||
if let JoinLocalFuture::Result(f) = fut {
|
||||
res.push(f.take().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
Poll::Ready(res)
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
@ -132,13 +75,4 @@ mod test {
|
|||
assert_eq!(Err(3), res.next().unwrap());
|
||||
assert_eq!(Ok(9), res.next().unwrap());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_join_all_local() {
|
||||
let futs = vec![ready(Ok(1)), ready(Err(3)), ready(Ok(9))];
|
||||
let mut res = join_all_local(futs).await.into_iter();
|
||||
assert_eq!(Ok(1), res.next().unwrap());
|
||||
assert_eq!(Err(3), res.next().unwrap());
|
||||
assert_eq!(Ok(9), res.next().unwrap());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -132,12 +132,12 @@ impl Server {
|
|||
.collect();
|
||||
|
||||
// Give log information on what runtime will be used.
|
||||
let is_tokio = tokio::runtime::Handle::try_current().is_ok();
|
||||
let is_actix = actix_rt::System::try_current().is_some();
|
||||
let is_tokio = tokio::runtime::Handle::try_current().is_ok();
|
||||
|
||||
match (is_tokio, is_actix) {
|
||||
(true, false) => info!("Tokio runtime found. Starting in existing Tokio runtime"),
|
||||
(_, true) => info!("Actix runtime found. Starting in Actix runtime"),
|
||||
match (is_actix, is_tokio) {
|
||||
(false, true) => info!("Tokio runtime found. Starting in existing Tokio runtime"),
|
||||
(true, _) => info!("Actix runtime found. Starting in Actix runtime"),
|
||||
(_, _) => info!(
|
||||
"Actix/Tokio runtime not found. Starting in newt Tokio current-thread runtime"
|
||||
),
|
||||
|
@ -196,11 +196,11 @@ impl Future for Server {
|
|||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.as_mut().get_mut() {
|
||||
Server::Error(err) => Poll::Ready(Err(err
|
||||
Self::Error(err) => Poll::Ready(Err(err
|
||||
.take()
|
||||
.expect("Server future cannot be polled after error"))),
|
||||
|
||||
Server::Server(inner) => {
|
||||
Self::Server(inner) => {
|
||||
// poll Signals
|
||||
if let Some(ref mut signals) = inner.signals {
|
||||
if let Poll::Ready(signal) = Pin::new(signals).poll(cx) {
|
||||
|
|
|
@ -2,7 +2,7 @@ pub(crate) use std::net::{
|
|||
SocketAddr as StdSocketAddr, TcpListener as StdTcpListener, ToSocketAddrs,
|
||||
};
|
||||
|
||||
pub(crate) use mio::net::{TcpListener as MioTcpListener, TcpSocket as MioTcpSocket};
|
||||
pub(crate) use mio::net::TcpListener as MioTcpListener;
|
||||
#[cfg(unix)]
|
||||
pub(crate) use {
|
||||
mio::net::UnixListener as MioUnixListener,
|
||||
|
@ -223,6 +223,22 @@ mod unix_impl {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create_mio_tcp_listener(
|
||||
addr: StdSocketAddr,
|
||||
backlog: u32,
|
||||
) -> io::Result<MioTcpListener> {
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
|
||||
let socket = Socket::new(Domain::for_address(addr), Type::STREAM, Some(Protocol::TCP))?;
|
||||
|
||||
socket.set_reuse_address(true)?;
|
||||
socket.set_nonblocking(true)?;
|
||||
socket.bind(&addr.into())?;
|
||||
socket.listen(backlog as i32)?;
|
||||
|
||||
Ok(MioTcpListener::from_std(StdTcpListener::from(socket)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -234,11 +250,8 @@ mod tests {
|
|||
assert_eq!(format!("{}", addr), "127.0.0.1:8080");
|
||||
|
||||
let addr: StdSocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||
let socket = MioTcpSocket::new_v4().unwrap();
|
||||
socket.set_reuseaddr(true).unwrap();
|
||||
socket.bind(addr).unwrap();
|
||||
let tcp = socket.listen(128).unwrap();
|
||||
let lst = MioListener::Tcp(tcp);
|
||||
let lst = create_mio_tcp_listener(addr, 128).unwrap();
|
||||
let lst = MioListener::Tcp(lst);
|
||||
assert!(format!("{:?}", lst).contains("TcpListener"));
|
||||
assert!(format!("{}", lst).contains("127.0.0.1"));
|
||||
}
|
||||
|
|
|
@ -147,3 +147,16 @@ impl Drop for TestServerRuntime {
|
|||
self.stop()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_service::fn_service;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn plain_tokio_runtime() {
|
||||
let srv = TestServer::with(|| fn_service(|_sock| async move { Ok::<_, ()>(()) }));
|
||||
assert!(srv.connect().is_ok());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ use tokio::sync::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
join_all::join_all_local,
|
||||
service::{BoxedServerService, InternalServiceFactory},
|
||||
socket::MioStream,
|
||||
waker_queue::{WakerInterest, WakerQueue},
|
||||
|
@ -202,8 +201,8 @@ impl WorkerHandleServer {
|
|||
pub(crate) struct ServerWorker {
|
||||
// UnboundedReceiver<Conn> should always be the first field.
|
||||
// It must be dropped as soon as ServerWorker dropping.
|
||||
rx: UnboundedReceiver<Conn>,
|
||||
rx2: UnboundedReceiver<Stop>,
|
||||
conn_rx: UnboundedReceiver<Conn>,
|
||||
stop_rx: UnboundedReceiver<Stop>,
|
||||
counter: WorkerCounter,
|
||||
services: Box<[WorkerService]>,
|
||||
factories: Box<[Box<dyn InternalServiceFactory>]>,
|
||||
|
@ -212,7 +211,7 @@ pub(crate) struct ServerWorker {
|
|||
}
|
||||
|
||||
struct WorkerService {
|
||||
factory: usize,
|
||||
factory_idx: usize,
|
||||
status: WorkerServiceStatus,
|
||||
service: BoxedServerService,
|
||||
}
|
||||
|
@ -234,6 +233,12 @@ enum WorkerServiceStatus {
|
|||
Stopped,
|
||||
}
|
||||
|
||||
impl Default for WorkerServiceStatus {
|
||||
fn default() -> Self {
|
||||
Self::Unavailable
|
||||
}
|
||||
}
|
||||
|
||||
/// Config for worker behavior passed down from server builder.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct ServerWorkerConfig {
|
||||
|
@ -277,105 +282,196 @@ impl ServerWorker {
|
|||
) -> io::Result<(WorkerHandleAccept, WorkerHandleServer)> {
|
||||
trace!("starting server worker {}", idx);
|
||||
|
||||
let (tx1, rx) = unbounded_channel();
|
||||
let (tx2, rx2) = unbounded_channel();
|
||||
let (tx1, conn_rx) = unbounded_channel();
|
||||
let (tx2, stop_rx) = unbounded_channel();
|
||||
|
||||
let counter = Counter::new(config.max_concurrent_connections);
|
||||
|
||||
let counter_clone = counter.clone();
|
||||
// every worker runs in it's own arbiter.
|
||||
// use a custom tokio runtime builder to change the settings of runtime.
|
||||
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||
let arbiter = {
|
||||
// TODO: pass max blocking thread config when tokio-uring enable configuration
|
||||
// on building runtime.
|
||||
let _ = config.max_blocking_threads;
|
||||
Arbiter::new()
|
||||
};
|
||||
let pair = handle_pair(idx, tx1, tx2, counter.clone());
|
||||
|
||||
// get actix system context if it is set
|
||||
let sys = System::try_current();
|
||||
let actix_system = System::try_current();
|
||||
|
||||
// get tokio runtime handle if it is set
|
||||
let tokio_handle = tokio::runtime::Handle::try_current().ok();
|
||||
|
||||
// service factories initialization channel
|
||||
let (factory_tx, factory_rx) = std::sync::mpsc::sync_channel(1);
|
||||
let (factory_tx, factory_rx) = std::sync::mpsc::sync_channel::<io::Result<()>>(1);
|
||||
|
||||
std::thread::Builder::new()
|
||||
.name(format!("actix-server worker {}", idx))
|
||||
.spawn(move || {
|
||||
// forward existing actix system context
|
||||
if let Some(sys) = sys {
|
||||
System::set_current(sys);
|
||||
}
|
||||
// outline of following code:
|
||||
//
|
||||
// if system exists
|
||||
// if uring enabled
|
||||
// start arbiter using uring method
|
||||
// else
|
||||
// start arbiter with regular tokio
|
||||
// else
|
||||
// if uring enabled
|
||||
// start uring in spawned thread
|
||||
// else
|
||||
// start regular tokio in spawned thread
|
||||
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.max_blocking_threads(config.max_blocking_threads)
|
||||
.build()
|
||||
.unwrap();
|
||||
// every worker runs in it's own thread and tokio runtime.
|
||||
// use a custom tokio runtime builder to change the settings of runtime.
|
||||
|
||||
rt.block_on(tokio::task::LocalSet::new().run_until(async move {
|
||||
let fut = factories
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, factory)| {
|
||||
let fut = factory.create();
|
||||
async move { fut.await.map(|(t, s)| (idx, t, s)) }
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
match (actix_system, tokio_handle) {
|
||||
(None, None) => {
|
||||
panic!("No runtime detected. Start a Tokio (or Actix) runtime.");
|
||||
}
|
||||
|
||||
// a second spawn to run !Send future tasks.
|
||||
spawn(async move {
|
||||
let res = join_all_local(fut)
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>();
|
||||
// no actix system
|
||||
(None, Some(rt_handle)) => {
|
||||
std::thread::Builder::new()
|
||||
.name(format!("actix-server worker {}", idx))
|
||||
.spawn(move || {
|
||||
let (worker_stopped_tx, worker_stopped_rx) = oneshot::channel();
|
||||
|
||||
let services = match res {
|
||||
Ok(res) => res
|
||||
.into_iter()
|
||||
.fold(Vec::new(), |mut services, (factory, token, service)| {
|
||||
assert_eq!(token, services.len());
|
||||
services.push(WorkerService {
|
||||
factory,
|
||||
service,
|
||||
status: WorkerServiceStatus::Unavailable,
|
||||
});
|
||||
services
|
||||
})
|
||||
.into_boxed_slice(),
|
||||
// local set for running service init futures and worker services
|
||||
let ls = tokio::task::LocalSet::new();
|
||||
|
||||
Err(e) => {
|
||||
error!("Can not start worker: {:?}", e);
|
||||
Arbiter::try_current().as_ref().map(ArbiterHandle::stop);
|
||||
// init services using existing Tokio runtime (so probably on main thread)
|
||||
let services = rt_handle.block_on(ls.run_until(async {
|
||||
let mut services = Vec::new();
|
||||
|
||||
for (idx, factory) in factories.iter().enumerate() {
|
||||
match factory.create().await {
|
||||
Ok((token, svc)) => services.push((idx, token, svc)),
|
||||
|
||||
Err(err) => {
|
||||
error!("Can not start worker: {:?}", err);
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("can not start server service {}", idx),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(services)
|
||||
}));
|
||||
|
||||
let services = match services {
|
||||
Ok(services) => {
|
||||
factory_tx.send(Ok(())).unwrap();
|
||||
services
|
||||
}
|
||||
Err(err) => {
|
||||
factory_tx.send(Err(err)).unwrap();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
factory_tx.send(()).unwrap();
|
||||
let worker_services = wrap_worker_services(services);
|
||||
|
||||
// a third spawn to make sure ServerWorker runs as non boxed future.
|
||||
let worker_fut = async move {
|
||||
// spawn to make sure ServerWorker runs as non boxed future.
|
||||
spawn(async move {
|
||||
ServerWorker {
|
||||
conn_rx,
|
||||
stop_rx,
|
||||
services: worker_services.into_boxed_slice(),
|
||||
counter: WorkerCounter::new(idx, waker_queue, counter),
|
||||
factories: factories.into_boxed_slice(),
|
||||
state: WorkerState::default(),
|
||||
shutdown_timeout: config.shutdown_timeout,
|
||||
}
|
||||
.await;
|
||||
|
||||
// wake up outermost task waiting for shutdown
|
||||
worker_stopped_tx.send(()).unwrap();
|
||||
});
|
||||
|
||||
worker_stopped_rx.await.unwrap();
|
||||
};
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||
{
|
||||
// TODO: pass max blocking thread config when tokio-uring enable configuration
|
||||
// on building runtime.
|
||||
let _ = config.max_blocking_threads;
|
||||
tokio_uring::start(worker_fut);
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
|
||||
{
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.max_blocking_threads(config.max_blocking_threads)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
rt.block_on(ls.run_until(worker_fut));
|
||||
}
|
||||
})
|
||||
.expect("cannot spawn server worker thread");
|
||||
}
|
||||
|
||||
// with actix system
|
||||
(Some(_sys), _) => {
|
||||
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||
let arbiter = {
|
||||
// TODO: pass max blocking thread config when tokio-uring enable configuration
|
||||
// on building runtime.
|
||||
let _ = config.max_blocking_threads;
|
||||
Arbiter::new()
|
||||
};
|
||||
|
||||
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
|
||||
let arbiter = {
|
||||
Arbiter::with_tokio_rt(move || {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.max_blocking_threads(config.max_blocking_threads)
|
||||
.build()
|
||||
.unwrap()
|
||||
})
|
||||
};
|
||||
|
||||
arbiter.spawn(async move {
|
||||
// spawn_local to run !Send future tasks.
|
||||
spawn(async move {
|
||||
let mut services = Vec::new();
|
||||
|
||||
for (idx, factory) in factories.iter().enumerate() {
|
||||
match factory.create().await {
|
||||
Ok((token, svc)) => services.push((idx, token, svc)),
|
||||
|
||||
Err(err) => {
|
||||
error!("Can not start worker: {:?}", err);
|
||||
Arbiter::current().stop();
|
||||
factory_tx
|
||||
.send(Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("can not start server service {}", idx),
|
||||
)))
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
factory_tx.send(Ok(())).unwrap();
|
||||
|
||||
let worker_services = wrap_worker_services(services);
|
||||
|
||||
// spawn to make sure ServerWorker runs as non boxed future.
|
||||
spawn(ServerWorker {
|
||||
rx,
|
||||
rx2,
|
||||
services,
|
||||
counter: WorkerCounter::new(idx, waker_queue, counter_clone),
|
||||
conn_rx,
|
||||
stop_rx,
|
||||
services: worker_services.into_boxed_slice(),
|
||||
counter: WorkerCounter::new(idx, waker_queue, counter),
|
||||
factories: factories.into_boxed_slice(),
|
||||
state: Default::default(),
|
||||
shutdown_timeout: config.shutdown_timeout,
|
||||
})
|
||||
.await
|
||||
.expect("task 3 panic");
|
||||
})
|
||||
.await
|
||||
.expect("task 2 panic");
|
||||
}))
|
||||
})
|
||||
.expect("worker thread error/panic");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// wait for service factories initialization
|
||||
factory_rx.recv().unwrap();
|
||||
factory_rx.recv().unwrap()?;
|
||||
|
||||
Ok(handle_pair(idx, tx1, tx2, counter))
|
||||
Ok(pair)
|
||||
}
|
||||
|
||||
fn restart_service(&mut self, idx: usize, factory_id: usize) {
|
||||
|
@ -413,7 +509,7 @@ impl ServerWorker {
|
|||
if srv.status == WorkerServiceStatus::Unavailable {
|
||||
trace!(
|
||||
"Service {:?} is available",
|
||||
self.factories[srv.factory].name(idx)
|
||||
self.factories[srv.factory_idx].name(idx)
|
||||
);
|
||||
srv.status = WorkerServiceStatus::Available;
|
||||
}
|
||||
|
@ -424,7 +520,7 @@ impl ServerWorker {
|
|||
if srv.status == WorkerServiceStatus::Available {
|
||||
trace!(
|
||||
"Service {:?} is unavailable",
|
||||
self.factories[srv.factory].name(idx)
|
||||
self.factories[srv.factory_idx].name(idx)
|
||||
);
|
||||
srv.status = WorkerServiceStatus::Unavailable;
|
||||
}
|
||||
|
@ -432,10 +528,10 @@ impl ServerWorker {
|
|||
Poll::Ready(Err(_)) => {
|
||||
error!(
|
||||
"Service {:?} readiness check returned error, restarting",
|
||||
self.factories[srv.factory].name(idx)
|
||||
self.factories[srv.factory_idx].name(idx)
|
||||
);
|
||||
srv.status = WorkerServiceStatus::Failed;
|
||||
return Err((idx, srv.factory));
|
||||
return Err((idx, srv.factory_idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -478,7 +574,6 @@ impl Default for WorkerState {
|
|||
|
||||
impl Drop for ServerWorker {
|
||||
fn drop(&mut self) {
|
||||
trace!("stopping ServerWorker Arbiter");
|
||||
Arbiter::try_current().as_ref().map(ArbiterHandle::stop);
|
||||
}
|
||||
}
|
||||
|
@ -490,7 +585,8 @@ impl Future for ServerWorker {
|
|||
let this = self.as_mut().get_mut();
|
||||
|
||||
// `StopWorker` message handler
|
||||
if let Poll::Ready(Some(Stop { graceful, tx })) = Pin::new(&mut this.rx2).poll_recv(cx)
|
||||
if let Poll::Ready(Some(Stop { graceful, tx })) =
|
||||
Pin::new(&mut this.stop_rx).poll_recv(cx)
|
||||
{
|
||||
let num = this.counter.total();
|
||||
if num == 0 {
|
||||
|
@ -552,6 +648,14 @@ impl Future for ServerWorker {
|
|||
self.poll(cx)
|
||||
}
|
||||
WorkerState::Shutdown(ref mut shutdown) => {
|
||||
// drop all pending connections in rx channel.
|
||||
while let Poll::Ready(Some(conn)) = Pin::new(&mut this.conn_rx).poll_recv(cx) {
|
||||
// WorkerCounterGuard is needed as Accept thread has incremented counter.
|
||||
// It's guard's job to decrement the counter together with drop of Conn.
|
||||
let guard = this.counter.guard();
|
||||
drop((conn, guard));
|
||||
}
|
||||
|
||||
// wait for 1 second
|
||||
ready!(shutdown.timer.as_mut().poll(cx));
|
||||
|
||||
|
@ -592,7 +696,7 @@ impl Future for ServerWorker {
|
|||
}
|
||||
|
||||
// handle incoming io stream
|
||||
match ready!(Pin::new(&mut this.rx).poll_recv(cx)) {
|
||||
match ready!(Pin::new(&mut this.conn_rx).poll_recv(cx)) {
|
||||
Some(msg) => {
|
||||
let guard = this.counter.guard();
|
||||
let _ = this.services[msg.token].service.call((guard, msg.io));
|
||||
|
@ -603,3 +707,19 @@ impl Future for ServerWorker {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_worker_services(
|
||||
services: Vec<(usize, usize, BoxedServerService)>,
|
||||
) -> Vec<WorkerService> {
|
||||
services
|
||||
.into_iter()
|
||||
.fold(Vec::new(), |mut services, (idx, token, service)| {
|
||||
assert_eq!(token, services.len());
|
||||
services.push(WorkerService {
|
||||
factory_idx: idx,
|
||||
service,
|
||||
status: WorkerServiceStatus::Unavailable,
|
||||
});
|
||||
services
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5,14 +5,17 @@ use std::{net, thread, time::Duration};
|
|||
use actix_rt::{net::TcpStream, time::sleep};
|
||||
use actix_server::Server;
|
||||
use actix_service::fn_service;
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
|
||||
fn unused_addr() -> net::SocketAddr {
|
||||
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||
let socket = mio::net::TcpSocket::new_v4().unwrap();
|
||||
socket.bind(addr).unwrap();
|
||||
socket.set_reuseaddr(true).unwrap();
|
||||
let tcp = socket.listen(32).unwrap();
|
||||
tcp.local_addr().unwrap()
|
||||
let socket =
|
||||
Socket::new(Domain::for_address(addr), Type::STREAM, Some(Protocol::TCP)).unwrap();
|
||||
socket.set_reuse_address(true).unwrap();
|
||||
socket.set_nonblocking(true).unwrap();
|
||||
socket.bind(&addr.into()).unwrap();
|
||||
socket.listen(32).unwrap();
|
||||
net::TcpListener::from(socket).local_addr().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -30,28 +33,63 @@ fn test_bind() {
|
|||
})?
|
||||
.run();
|
||||
|
||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
||||
let _ = tx.send(srv.handle());
|
||||
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
let (srv, sys) = rx.recv().unwrap();
|
||||
let srv = rx.recv().unwrap();
|
||||
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
|
||||
let _ = srv.stop(true);
|
||||
h.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_tokio_runtime() {
|
||||
let addr = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
rt.block_on(async {
|
||||
let srv = Server::build()
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
.bind("test", addr, move || {
|
||||
fn_service(|_| async { Ok::<_, ()>(()) })
|
||||
})?
|
||||
.run();
|
||||
|
||||
tx.send(srv.handle()).unwrap();
|
||||
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let srv = rx.recv().unwrap();
|
||||
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
|
||||
let _ = srv.stop(true);
|
||||
sys.stop();
|
||||
h.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_listen() {
|
||||
let addr = unused_addr();
|
||||
let lst = net::TcpListener::bind(addr).unwrap();
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
let lst = net::TcpListener::bind(addr)?;
|
||||
actix_rt::System::new().block_on(async {
|
||||
let srv = Server::build()
|
||||
.disable_signals()
|
||||
|
@ -61,19 +99,18 @@ fn test_listen() {
|
|||
})?
|
||||
.run();
|
||||
|
||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
||||
let _ = tx.send(srv.handle());
|
||||
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let (srv, sys) = rx.recv().unwrap();
|
||||
let srv = rx.recv().unwrap();
|
||||
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
|
||||
let _ = srv.stop(true);
|
||||
sys.stop();
|
||||
h.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
|
@ -280,12 +317,12 @@ async fn test_service_restart() {
|
|||
.workers(1)
|
||||
.run();
|
||||
|
||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
||||
let _ = tx.send(srv.handle());
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let (srv, sys) = rx.recv().unwrap();
|
||||
let srv = rx.recv().unwrap();
|
||||
|
||||
for _ in 0..5 {
|
||||
TcpStream::connect(addr1)
|
||||
|
@ -308,7 +345,6 @@ async fn test_service_restart() {
|
|||
assert!(num2_clone.load(Ordering::SeqCst) > 5);
|
||||
|
||||
let _ = srv.stop(false);
|
||||
sys.stop();
|
||||
h.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
|
@ -385,13 +421,13 @@ async fn worker_restart() {
|
|||
.workers(2)
|
||||
.run();
|
||||
|
||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
||||
let _ = tx.send(srv.handle());
|
||||
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let (srv, sys) = rx.recv().unwrap();
|
||||
let srv = rx.recv().unwrap();
|
||||
|
||||
sleep(Duration::from_secs(3)).await;
|
||||
|
||||
|
@ -449,6 +485,5 @@ async fn worker_restart() {
|
|||
stream.shutdown().await.unwrap();
|
||||
|
||||
let _ = srv.stop(false);
|
||||
sys.stop();
|
||||
h.join().unwrap().unwrap();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/// An implementation of [`poll_ready`]() that always signals readiness.
|
||||
///
|
||||
/// This should only be used for basic leaf services that have no concept of un-readiness.
|
||||
/// For wrapper or other serivice types, use [`forward_ready!`] for simple cases or write a bespoke
|
||||
/// For wrapper or other service types, use [`forward_ready!`] for simple cases or write a bespoke
|
||||
/// `poll_ready` implementation.
|
||||
///
|
||||
/// [`poll_ready`]: crate::Service::poll_ready
|
||||
|
|
|
@ -3,6 +3,12 @@
|
|||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 3.0.0-beta.8 - 2021-11-15
|
||||
* Add `Connect::request` for getting a reference to the connection request. [#415]
|
||||
|
||||
[#415]: https://github.com/actix/actix-net/pull/415
|
||||
|
||||
|
||||
## 3.0.0-beta.7 - 2021-10-20
|
||||
* Add `webpki_roots_cert_store()` to get rustls compatible webpki roots cert store. [#401]
|
||||
* Alias `connect::ssl` to `connect::tls`. [#401]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "actix-tls"
|
||||
version = "3.0.0-beta.7"
|
||||
version = "3.0.0-beta.8"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "TLS acceptor and connector services for Actix ecosystem"
|
||||
keywords = ["network", "tls", "ssl", "async", "transport"]
|
||||
|
@ -62,7 +62,7 @@ tokio-native-tls = { version = "0.3", optional = true }
|
|||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2.0"
|
||||
actix-server = "2.0.0-beta.7"
|
||||
actix-server = "2.0.0-beta.9"
|
||||
bytes = "1"
|
||||
env_logger = "0.9"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
||||
|
|
|
@ -63,16 +63,16 @@ impl From<Option<SocketAddr>> for ConnectAddrs {
|
|||
|
||||
/// Connection info.
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Connect<T> {
|
||||
pub(crate) req: T,
|
||||
pub struct Connect<R> {
|
||||
pub(crate) req: R,
|
||||
pub(crate) port: u16,
|
||||
pub(crate) addr: ConnectAddrs,
|
||||
pub(crate) local_addr: Option<IpAddr>,
|
||||
}
|
||||
|
||||
impl<T: Address> Connect<T> {
|
||||
impl<R: Address> Connect<R> {
|
||||
/// Create `Connect` instance by splitting the string by ':' and convert the second part to u16
|
||||
pub fn new(req: T) -> Connect<T> {
|
||||
pub fn new(req: R) -> Connect<R> {
|
||||
let (_, port) = parse_host(req.hostname());
|
||||
|
||||
Connect {
|
||||
|
@ -85,7 +85,7 @@ impl<T: Address> Connect<T> {
|
|||
|
||||
/// Create new `Connect` instance from host and address. Connector skips name resolution stage
|
||||
/// for such connect messages.
|
||||
pub fn with_addr(req: T, addr: SocketAddr) -> Connect<T> {
|
||||
pub fn with_addr(req: R, addr: SocketAddr) -> Connect<R> {
|
||||
Connect {
|
||||
req,
|
||||
port: 0,
|
||||
|
@ -155,15 +155,20 @@ impl<T: Address> Connect<T> {
|
|||
ConnectAddrs::Multi(addrs) => ConnectAddrsIter::MultiOwned(addrs.into_iter()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the connection request.
|
||||
pub fn request(&self) -> &R {
|
||||
&self.req
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> From<T> for Connect<T> {
|
||||
fn from(addr: T) -> Self {
|
||||
impl<R: Address> From<R> for Connect<R> {
|
||||
fn from(addr: R) -> Self {
|
||||
Connect::new(addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> fmt::Display for Connect<T> {
|
||||
impl<R: Address> fmt::Display for Connect<R> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}:{}", self.hostname(), self.port())
|
||||
}
|
||||
|
@ -347,4 +352,10 @@ mod tests {
|
|||
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_ref() {
|
||||
let conn = Connect::new("hello");
|
||||
assert_eq!(conn.request(), &"hello")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,3 +23,4 @@ local-waker = "0.1"
|
|||
[dev-dependencies]
|
||||
actix-rt = "2.0.0"
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
static_assertions = "1.1"
|
||||
|
|
|
@ -103,10 +103,16 @@ pub fn err<T, E>(err: E) -> Ready<Result<T, E>> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rc::Rc;
|
||||
|
||||
use futures_util::task::noop_waker;
|
||||
use static_assertions::{assert_impl_all, assert_not_impl_all};
|
||||
|
||||
use super::*;
|
||||
|
||||
assert_impl_all!(Ready<()>: Send, Sync, Clone);
|
||||
assert_not_impl_all!(Ready<Rc<()>>: Send, Sync);
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn multiple_poll_panics() {
|
||||
|
|
Loading…
Reference in New Issue