apply generic executor to actix-server

This commit is contained in:
fakeshadow 2020-10-30 06:17:02 +08:00
parent 9a7a626f7b
commit bb4b0d1a63
16 changed files with 353 additions and 128 deletions

View File

@ -130,8 +130,14 @@ impl<E: ExecFactory> SystemRunner<E> {
}
}
pub fn spawn<F>(&mut self, fut: F)
where
F: Future<Output = ()> + 'static,
{
E::spawn_ref(&mut self.rt, fut);
}
/// Execute a future and wait for result.
#[inline]
pub fn block_on<F, O>(&mut self, fut: F) -> O
where
F: Future<Output = O>,

View File

@ -33,7 +33,7 @@ pub fn spawn<F>(f: F)
where
F: Future<Output = ()> + 'static,
{
DefaultExec::spawn(f)
DefaultExec::spawn(f);
}
/// Asynchronous signal handling

View File

@ -1,13 +1,16 @@
use std::future::Future;
use std::io;
use std::time::Duration;
use tokio::{runtime, task::LocalSet};
/// A trait for construct async executor and run future on it.
///
/// A factory trait is necessary as `actix` and `actix-web` can run on multiple instances of
/// executors. Therefore the executor would be constructed multiple times
pub trait ExecFactory: Sized + Unpin + 'static {
pub trait ExecFactory: Sized + Send + Sync + Unpin + 'static {
type Executor;
type Sleep: Future<Output = ()> + Send + Unpin + 'static;
fn build() -> io::Result<Self::Executor>;
@ -64,6 +67,9 @@ pub trait ExecFactory: Sized + Unpin + 'static {
///
/// *. `spawn_ref` is preferred when you can choose between it and `spawn`.
fn spawn_ref<F: Future<Output = ()> + 'static>(exec: &mut Self::Executor, f: F);
/// Get a timeout sleep future with given duration.
fn sleep(dur: Duration) -> Self::Sleep;
}
/// Default Single-threaded tokio executor on the current thread.
@ -78,6 +84,7 @@ pub type DefaultExecutor = (runtime::Runtime, LocalSet);
impl ExecFactory for DefaultExec {
type Executor = DefaultExecutor;
type Sleep = tokio::time::Delay;
fn build() -> io::Result<Self::Executor> {
let rt = runtime::Builder::new()
@ -95,6 +102,7 @@ impl ExecFactory for DefaultExec {
rt.block_on(local.run_until(f))
}
#[inline]
fn spawn<F: Future<Output = ()> + 'static>(f: F) {
tokio::task::spawn_local(f);
}
@ -102,4 +110,9 @@ impl ExecFactory for DefaultExec {
fn spawn_ref<F: Future<Output = ()> + 'static>(exec: &mut Self::Executor, f: F) {
exec.1.spawn_local(f);
}
#[inline]
fn sleep(dur: Duration) -> Self::Sleep {
tokio::time::delay_for(dur)
}
}

View File

@ -74,6 +74,7 @@ struct TokioCompatExec;
impl ExecFactory for TokioCompatExec {
type Executor = tokio_compat::runtime::current_thread::Runtime;
type Sleep = tokio::time::Delay;
fn build() -> std::io::Result<Self::Executor> {
let rt = tokio_compat::runtime::current_thread::Runtime::new()?;
@ -94,6 +95,10 @@ impl ExecFactory for TokioCompatExec {
fn spawn_ref<F: Future<Output = ()> + 'static>(exec: &mut Self::Executor, f: F) {
exec.spawn_std(f);
}
fn sleep(dur: Duration) -> Self::Sleep {
tokio::time::delay_for(dur)
}
}
#[test]

View File

@ -41,4 +41,5 @@ mio-uds = { version = "0.6.7" }
bytes = "0.5"
env_logger = "0.7"
actix-testing = "1.0.0"
async-std = { version = "1.6.5", features = ["unstable", "tokio02"] }
tokio = { version = "0.2", features = ["io-util"] }

View File

@ -0,0 +1,128 @@
//! Simple composite-service TCP echo server.
//!
//! Using the following command:
//!
//! ```sh
//! nc 127.0.0.1 8080
//! ```
//!
//! Start typing. When you press enter the typed line will be echoed back. The server will log
//! the length of each line it echos and the total size of data sent when the connection is closed.
use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use std::{env, io};
use actix_rt::ExecFactory;
use actix_server::{FromStream, Server, StdStream};
use actix_service::pipeline_factory;
use futures_util::future::ok;
use log::{error, info};
fn main() -> io::Result<()> {
actix_rt::System::new_with::<AsyncStdExec, _>("actix").block_on(async {
env::set_var("RUST_LOG", "actix=trace,basic=trace");
env_logger::init();
let count = Arc::new(AtomicUsize::new(0));
let addr = ("127.0.0.1", 8080);
info!("starting server on port: {}", &addr.0);
// Bind socket address and start worker(s). By default, the server uses the number of available
// logical CPU cores as the worker count. For this reason, the closure passed to bind needs
// to return a service *factory*; so it can be created once per worker.
Server::build_with::<AsyncStdExec>()
.bind("echo", addr, move || {
let count = Arc::clone(&count);
let num2 = Arc::clone(&count);
pipeline_factory(move |mut stream: AsyncStdTcpStream| {
let count = Arc::clone(&count);
async move {
let num = count.fetch_add(1, Ordering::SeqCst);
let num = num + 1;
let mut size = 0;
let mut buf = vec![0; 1024];
use async_std::prelude::*;
loop {
match stream.0.read(&mut buf).await {
// end of stream; bail from loop
Ok(0) => break,
// more bytes to process
Ok(bytes_read) => {
info!("[{}] read {} bytes", num, bytes_read);
stream.0.write_all(&buf[size..]).await.unwrap();
size += bytes_read;
}
// stream error; bail from loop with error
Err(err) => {
error!("Stream Error: {:?}", err);
return Err(());
}
}
}
// send data down service pipeline
Ok((buf.len(), size))
}
})
.map_err(|err| error!("Service Error: {:?}", err))
.and_then(move |(_, size)| {
let num = num2.load(Ordering::SeqCst);
info!("[{}] total bytes read: {}", num, size);
ok(size)
})
})?
.workers(1)
.run()
.await
})
}
struct AsyncStdExec;
struct AsyncStdTcpStream(async_std::net::TcpStream);
impl FromStream for AsyncStdTcpStream {
fn from_stdstream(stream: StdStream) -> std::io::Result<Self> {
match stream {
StdStream::Tcp(tcp) => Ok(AsyncStdTcpStream(async_std::net::TcpStream::from(tcp))),
_ => unimplemented!(),
}
}
}
impl ExecFactory for AsyncStdExec {
type Executor = ();
type Sleep = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
fn build() -> std::io::Result<Self::Executor> {
Ok(())
}
fn block_on<F: Future>(_: &mut Self::Executor, f: F) -> <F as Future>::Output {
async_std::task::block_on(f)
}
fn spawn<F: Future<Output = ()> + 'static>(f: F) {
async_std::task::spawn_local(f);
}
fn spawn_ref<F: Future<Output = ()> + 'static>(_: &mut Self::Executor, f: F) {
async_std::task::spawn_local(f);
}
fn sleep(dur: Duration) -> Self::Sleep {
Box::pin(async_std::task::sleep(dur))
}
}

View File

@ -1,9 +1,9 @@
use std::marker::PhantomData;
use std::sync::mpsc as sync_mpsc;
use std::time::Duration;
use std::time::{Duration, Instant};
use std::{io, thread};
use actix_rt::time::{delay_until, Instant};
use actix_rt::System;
use actix_rt::{ExecFactory, System};
use log::{error, info};
use slab::Slab;
@ -81,14 +81,16 @@ impl AcceptLoop {
AcceptNotify::new(self.notify_ready.clone())
}
pub(crate) fn start(
pub(crate) fn start<Exec>(
&mut self,
socks: Vec<(Token, StdListener)>,
workers: Vec<WorkerClient>,
) {
) where
Exec: ExecFactory,
{
let srv = self.srv.take().expect("Can not re-use AcceptInfo");
Accept::start(
Accept::<Exec>::start(
self.rx.take().expect("Can not re-use AcceptInfo"),
self.cmd_reg.take().expect("Can not re-use AcceptInfo"),
self.notify_reg.take().expect("Can not re-use AcceptInfo"),
@ -99,7 +101,7 @@ impl AcceptLoop {
}
}
struct Accept {
struct Accept<Exec> {
poll: mio::Poll,
rx: sync_mpsc::Receiver<Command>,
sockets: Slab<ServerSocketInfo>,
@ -108,6 +110,7 @@ struct Accept {
timer: (mio::Registration, mio::SetReadiness),
next: usize,
backpressure: bool,
_exec: PhantomData<Exec>,
}
const DELTA: usize = 100;
@ -128,7 +131,10 @@ fn connection_error(e: &io::Error) -> bool {
|| e.kind() == io::ErrorKind::ConnectionReset
}
impl Accept {
impl<Exec> Accept<Exec>
where
Exec: ExecFactory,
{
#![allow(clippy::too_many_arguments)]
pub(crate) fn start(
rx: sync_mpsc::Receiver<Command>,
@ -145,7 +151,7 @@ impl Accept {
.name("actix-server accept loop".to_owned())
.spawn(move || {
System::set_current(sys);
let mut accept = Accept::new(rx, socks, workers, srv);
let mut accept = Accept::<Exec>::new(rx, socks, workers, srv);
// Start listening for incoming commands
if let Err(err) = accept.poll.register(
@ -176,7 +182,7 @@ impl Accept {
socks: Vec<(Token, StdListener)>,
workers: Vec<WorkerClient>,
srv: Server,
) -> Accept {
) -> Accept<Exec> {
// Create a poll instance
let poll = match mio::Poll::new() {
Ok(poll) => poll,
@ -227,6 +233,7 @@ impl Accept {
next: 0,
timer: (tm, tmr),
backpressure: false,
_exec: PhantomData,
}
}
@ -462,7 +469,7 @@ impl Accept {
let r = self.timer.1.clone();
System::current().arbiter().send(Box::pin(async move {
delay_until(Instant::now() + Duration::from_millis(510)).await;
Exec::sleep(Duration::from_millis(510)).await;
let _ = r.set_readiness(mio::Ready::readable());
}));
return;

View File

@ -1,11 +1,11 @@
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use std::{io, mem, net};
use actix_rt::net::TcpStream;
use actix_rt::time::{delay_until, Instant};
use actix_rt::{spawn, System};
use actix_rt::{DefaultExec, ExecFactory, System};
use futures_channel::mpsc::{unbounded, UnboundedReceiver};
use futures_channel::oneshot;
use futures_util::future::ready;
@ -21,10 +21,10 @@ use crate::service::{InternalServiceFactory, ServiceFactory, StreamNewService};
use crate::signals::{Signal, Signals};
use crate::socket::StdListener;
use crate::worker::{self, Worker, WorkerAvailability, WorkerClient};
use crate::Token;
use crate::{FromStream, Token};
/// Server builder
pub struct ServerBuilder {
pub struct ServerBuilder<Exec = DefaultExec> {
threads: usize,
token: Token,
backlog: i32,
@ -38,6 +38,7 @@ pub struct ServerBuilder {
cmd: UnboundedReceiver<ServerCommand>,
server: Server,
notify: Vec<oneshot::Sender<()>>,
_exec: PhantomData<Exec>,
}
impl Default for ServerBuilder {
@ -46,9 +47,20 @@ impl Default for ServerBuilder {
}
}
impl ServerBuilder {
/// Create new Server builder instance
pub fn new() -> ServerBuilder {
impl<Exec> ServerBuilder<Exec>
where
Exec: ExecFactory,
{
/// Create new Server builder instance with default tokio executor.
pub fn new() -> Self {
ServerBuilder::<DefaultExec>::new_with()
}
/// Create new Server builder instance with a generic executor.
pub fn new_with<E>() -> ServerBuilder<E>
where
E: ExecFactory,
{
let (tx, rx) = unbounded();
let server = Server::new(tx);
@ -66,6 +78,7 @@ impl ServerBuilder {
cmd: rx,
notify: Vec::new(),
server,
_exec: Default::default(),
}
}
@ -134,7 +147,7 @@ impl ServerBuilder {
///
/// This function is useful for moving parts of configuration to a
/// different module or even library.
pub fn configure<F>(mut self, f: F) -> io::Result<ServerBuilder>
pub fn configure<F>(mut self, f: F) -> io::Result<ServerBuilder<Exec>>
where
F: Fn(&mut ServiceConfig) -> io::Result<()>,
{
@ -143,7 +156,7 @@ impl ServerBuilder {
f(&mut cfg)?;
if let Some(apply) = cfg.apply {
let mut srv = ConfiguredService::new(apply);
let mut srv = ConfiguredService::<Exec>::new(apply);
for (name, lst) in cfg.services {
let token = self.token.next();
srv.stream(token, name.clone(), lst.local_addr()?);
@ -157,16 +170,18 @@ impl ServerBuilder {
}
/// Add new service to the server.
pub fn bind<F, U, N: AsRef<str>>(mut self, name: N, addr: U, factory: F) -> io::Result<Self>
pub fn bind<F, U, N, S>(mut self, name: N, addr: U, factory: F) -> io::Result<Self>
where
F: ServiceFactory<TcpStream>,
F: ServiceFactory<S>,
U: net::ToSocketAddrs,
N: AsRef<str>,
S: FromStream,
{
let sockets = bind_addr(addr, self.backlog)?;
for lst in sockets {
let token = self.token.next();
self.services.push(StreamNewService::create(
self.services.push(StreamNewService::<_, _, Exec>::create(
name.as_ref().to_string(),
token,
factory.clone(),
@ -180,11 +195,12 @@ impl ServerBuilder {
#[cfg(all(unix))]
/// Add new unix domain service to the server.
pub fn bind_uds<F, U, N>(self, name: N, addr: U, factory: F) -> io::Result<Self>
pub fn bind_uds<F, U, N, S>(self, name: N, addr: U, factory: F) -> io::Result<Self>
where
F: ServiceFactory<actix_rt::net::UnixStream>,
N: AsRef<str>,
F: ServiceFactory<S>,
U: AsRef<std::path::Path>,
N: AsRef<str>,
S: FromStream,
{
use std::os::unix::net::UnixListener;
@ -205,19 +221,21 @@ impl ServerBuilder {
/// Add new unix domain service to the server.
/// Useful when running as a systemd service and
/// a socket FD can be acquired using the systemd crate.
pub fn listen_uds<F, N: AsRef<str>>(
pub fn listen_uds<F, N, S>(
mut self,
name: N,
lst: std::os::unix::net::UnixListener,
factory: F,
) -> io::Result<Self>
where
F: ServiceFactory<actix_rt::net::UnixStream>,
F: ServiceFactory<S>,
N: AsRef<str>,
S: FromStream,
{
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
let token = self.token.next();
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
self.services.push(StreamNewService::create(
self.services.push(StreamNewService::<_, _, Exec>::create(
name.as_ref().to_string(),
token,
factory,
@ -239,7 +257,7 @@ impl ServerBuilder {
F: ServiceFactory<TcpStream>,
{
let token = self.token.next();
self.services.push(StreamNewService::create(
self.services.push(StreamNewService::<_, _, Exec>::create(
name.as_ref().to_string(),
token,
factory,
@ -276,7 +294,7 @@ impl ServerBuilder {
for sock in &self.sockets {
info!("Starting \"{}\" service on {}", sock.1, sock.2);
}
self.accept.start(
self.accept.start::<Exec>(
mem::take(&mut self.sockets)
.into_iter()
.map(|t| (t.0, t.2))
@ -286,12 +304,12 @@ impl ServerBuilder {
// handle signals
if !self.no_signals {
Signals::start(self.server.clone()).unwrap();
Signals::<Exec>::start(self.server.clone()).unwrap();
}
// start http server actor
let server = self.server.clone();
spawn(self);
Exec::spawn(self);
server
}
}
@ -301,7 +319,7 @@ impl ServerBuilder {
let services: Vec<Box<dyn InternalServiceFactory>> =
self.services.iter().map(|v| v.clone_factory()).collect();
Worker::start(idx, services, avail, self.shutdown_timeout)
Worker::<Exec>::start(idx, services, avail, self.shutdown_timeout)
}
fn handle_cmd(&mut self, item: ServerCommand) {
@ -360,7 +378,7 @@ impl ServerBuilder {
// stop workers
if !self.workers.is_empty() && graceful {
spawn(
Exec::spawn(
self.workers
.iter()
.map(move |worker| worker.1.stop(graceful))
@ -374,16 +392,10 @@ impl ServerBuilder {
let _ = tx.send(());
}
if exit {
spawn(
async {
delay_until(
Instant::now() + Duration::from_millis(300),
)
.await;
System::current().stop();
}
.boxed(),
);
Exec::spawn(async {
Exec::sleep(Duration::from_millis(300)).await;
System::current().stop();
});
}
ready(())
}),
@ -391,14 +403,10 @@ impl ServerBuilder {
} else {
// we need to stop system if server was spawned
if self.exit {
spawn(
delay_until(Instant::now() + Duration::from_millis(300)).then(
|_| {
System::current().stop();
ready(())
},
),
);
Exec::spawn(async {
Exec::sleep(Duration::from_millis(300)).await;
System::current().stop();
});
}
if let Some(tx) = completion {
let _ = tx.send(());
@ -441,7 +449,10 @@ impl ServerBuilder {
}
}
impl Future for ServerBuilder {
impl<Exec> Future for ServerBuilder<Exec>
where
Exec: ExecFactory,
{
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {

View File

@ -12,6 +12,8 @@ use super::service::{
BoxedServerService, InternalServiceFactory, ServerMessage, StreamService,
};
use super::Token;
use actix_rt::ExecFactory;
use std::marker::PhantomData;
pub struct ServiceConfig {
pub(crate) services: Vec<(String, net::TcpListener)>,
@ -72,20 +74,22 @@ impl ServiceConfig {
}
}
pub(super) struct ConfiguredService {
pub(super) struct ConfiguredService<Exec> {
rt: Box<dyn ServiceRuntimeConfiguration>,
names: HashMap<Token, (String, net::SocketAddr)>,
topics: HashMap<String, Token>,
services: Vec<Token>,
_exec: PhantomData<Exec>,
}
impl ConfiguredService {
impl<Exec> ConfiguredService<Exec> {
pub(super) fn new(rt: Box<dyn ServiceRuntimeConfiguration>) -> Self {
ConfiguredService {
rt,
names: HashMap::new(),
topics: HashMap::new(),
services: Vec::new(),
_exec: Default::default(),
}
}
@ -96,7 +100,10 @@ impl ConfiguredService {
}
}
impl InternalServiceFactory for ConfiguredService {
impl<Exec> InternalServiceFactory for ConfiguredService<Exec>
where
Exec: ExecFactory,
{
fn name(&self, token: Token) -> &str {
&self.names[&token].0
}
@ -107,6 +114,7 @@ impl InternalServiceFactory for ConfiguredService {
names: self.names.clone(),
topics: self.topics.clone(),
services: self.services.clone(),
_exec: PhantomData,
})
}
@ -142,7 +150,7 @@ impl InternalServiceFactory for ConfiguredService {
let name = names.remove(&token).unwrap().0;
res.push((
token,
Box::new(StreamService::new(actix::fn_service(
Box::new(StreamService::<_, Exec>::new(actix::fn_service(
move |_: TcpStream| {
error!("Service {:?} is not configured", name);
ok::<_, ()>(())
@ -207,20 +215,22 @@ impl ServiceRuntime {
///
/// Name of the service must be registered during configuration stage with
/// *ServiceConfig::bind()* or *ServiceConfig::listen()* methods.
pub fn service<T, F>(&mut self, name: &str, service: F)
pub fn service<T, F, Exec>(&mut self, name: &str, service: F)
where
F: actix::IntoServiceFactory<T>,
T: actix::ServiceFactory<Config = (), Request = TcpStream> + 'static,
T::Future: 'static,
T::Service: 'static,
T::InitError: fmt::Debug,
Exec: ExecFactory,
{
// let name = name.to_owned();
if let Some(token) = self.names.get(name) {
self.services.insert(
*token,
Box::new(ServiceFactory {
Box::new(ServiceFactory::<_, Exec> {
inner: service.into_factory(),
_exec: PhantomData,
}),
);
} else {
@ -249,17 +259,19 @@ type BoxedNewService = Box<
>,
>;
struct ServiceFactory<T> {
struct ServiceFactory<T, Exec> {
inner: T,
_exec: PhantomData<Exec>,
}
impl<T> actix::ServiceFactory for ServiceFactory<T>
impl<T, Exec> actix::ServiceFactory for ServiceFactory<T, Exec>
where
T: actix::ServiceFactory<Config = (), Request = TcpStream>,
T::Future: 'static,
T::Service: 'static,
T::Error: 'static,
T::InitError: fmt::Debug + 'static,
Exec: ExecFactory,
{
type Request = (Option<CounterGuard>, ServerMessage);
type Response = ();
@ -273,7 +285,7 @@ where
let fut = self.inner.new_service(());
async move {
match fut.await {
Ok(s) => Ok(Box::new(StreamService::new(s)) as BoxedServerService),
Ok(s) => Ok(Box::new(StreamService::<_, Exec>::new(s)) as BoxedServerService),
Err(e) => {
error!("Can not construct service: {:?}", e);
Err(())

View File

@ -15,6 +15,7 @@ pub use self::builder::ServerBuilder;
pub use self::config::{ServiceConfig, ServiceRuntime};
pub use self::server::Server;
pub use self::service::ServiceFactory;
pub use self::socket::StdStream;
#[doc(hidden)]
pub use self::socket::FromStream;

View File

@ -3,6 +3,7 @@ use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_rt::ExecFactory;
use futures_channel::mpsc::UnboundedSender;
use futures_channel::oneshot;
use futures_util::FutureExt;
@ -41,6 +42,11 @@ impl Server {
ServerBuilder::default()
}
/// Start server building process with a custom executor
pub fn build_with<Exec: ExecFactory>() -> ServerBuilder<Exec> {
ServerBuilder::<Exec>::new_with()
}
pub(crate) fn signal(&self, sig: Signal) {
let _ = self.0.unbounded_send(ServerCommand::Signal(sig));
}

View File

@ -3,7 +3,7 @@ use std::net::SocketAddr;
use std::task::{Context, Poll};
use std::time::Duration;
use actix_rt::spawn;
use actix_rt::ExecFactory;
use actix_service::{self as actix, Service, ServiceFactory as ActixServiceFactory};
use actix_utils::counter::CounterGuard;
use futures_util::future::{err, ok, LocalBoxFuture, Ready};
@ -48,22 +48,27 @@ pub(crate) type BoxedServerService = Box<
>,
>;
pub(crate) struct StreamService<T> {
pub(crate) struct StreamService<T, Exec> {
service: T,
_exec: PhantomData<Exec>,
}
impl<T> StreamService<T> {
impl<T, Exec> StreamService<T, Exec> {
pub(crate) fn new(service: T) -> Self {
StreamService { service }
StreamService {
service,
_exec: PhantomData,
}
}
}
impl<T, I> Service for StreamService<T>
impl<T, I, Exec> Service for StreamService<T, Exec>
where
T: Service<Request = I>,
T::Future: 'static,
T::Error: 'static,
I: FromStream,
Exec: ExecFactory,
{
type Request = (Option<CounterGuard>, ServerMessage);
type Response = ();
@ -83,7 +88,7 @@ where
if let Ok(stream) = stream {
let f = self.service.call(stream);
spawn(async move {
Exec::spawn(async move {
let _ = f.await;
drop(guard);
});
@ -97,18 +102,24 @@ where
}
}
pub(crate) struct StreamNewService<F: ServiceFactory<Io>, Io: FromStream> {
pub(crate) struct StreamNewService<F, Io, Exec>
where
F: ServiceFactory<Io>,
Io: FromStream,
Exec: ExecFactory,
{
name: String,
inner: F,
token: Token,
addr: SocketAddr,
_t: PhantomData<Io>,
_t: PhantomData<(Io, Exec)>,
}
impl<F, Io> StreamNewService<F, Io>
impl<F, Io, Exec> StreamNewService<F, Io, Exec>
where
F: ServiceFactory<Io>,
Io: FromStream + Send + 'static,
Exec: ExecFactory,
{
pub(crate) fn create(
name: String,
@ -126,10 +137,11 @@ where
}
}
impl<F, Io> InternalServiceFactory for StreamNewService<F, Io>
impl<F, Io, Exec> InternalServiceFactory for StreamNewService<F, Io, Exec>
where
F: ServiceFactory<Io>,
Io: FromStream + Send + 'static,
Exec: ExecFactory,
{
fn name(&self, _: Token) -> &str {
&self.name
@ -152,7 +164,8 @@ where
.new_service(())
.map_err(|_| ())
.map_ok(move |inner| {
let service: BoxedServerService = Box::new(StreamService::new(inner));
let service: BoxedServerService =
Box::new(StreamService::<_, Exec>::new(inner));
vec![(token, service)]
})
.boxed_local()

View File

@ -6,6 +6,8 @@ use std::task::{Context, Poll};
use futures_util::future::lazy;
use crate::server::Server;
use actix_rt::ExecFactory;
use std::marker::PhantomData;
/// Different types of process signals
#[allow(dead_code)]
@ -21,20 +23,24 @@ pub(crate) enum Signal {
Quit,
}
pub(crate) struct Signals {
pub(crate) struct Signals<Exec> {
srv: Server,
#[cfg(not(unix))]
stream: Pin<Box<dyn Future<Output = io::Result<()>>>>,
#[cfg(unix)]
streams: Vec<(Signal, actix_rt::signal::unix::Signal)>,
_exec: PhantomData<Exec>,
}
impl Signals {
impl<Exec> Signals<Exec>
where
Exec: ExecFactory,
{
pub(crate) fn start(srv: Server) -> io::Result<()> {
actix_rt::spawn(lazy(|_| {
Exec::spawn(lazy(|_| {
#[cfg(not(unix))]
{
actix_rt::spawn(Signals {
Exec::spawn(Signals {
srv,
stream: Box::pin(actix_rt::signal::ctrl_c()),
});
@ -63,7 +69,11 @@ impl Signals {
}
}
actix_rt::spawn(Signals { srv, streams })
Exec::spawn(Self {
srv,
streams,
_exec: PhantomData,
})
}
}));
@ -71,7 +81,10 @@ impl Signals {
}
}
impl Future for Signals {
impl<Exec> Future for Signals<Exec>
where
Exec: Unpin,
{
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {

View File

@ -1,6 +1,5 @@
use std::{fmt, io, net};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::net::TcpStream;
pub(crate) enum StdListener {
@ -143,7 +142,7 @@ impl mio::Evented for SocketListener {
}
}
pub trait FromStream: AsyncRead + AsyncWrite + Sized {
pub trait FromStream: Sized + Send + 'static {
fn from_stdstream(sock: StdStream) -> io::Result<Self>;
}

View File

@ -1,11 +1,11 @@
use std::marker::PhantomData;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time;
use actix_rt::time::{delay_until, Delay, Instant};
use actix_rt::{spawn, Arbiter};
use actix_rt::{Arbiter, ExecFactory};
use actix_utils::counter::Counter;
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures_channel::oneshot;
@ -125,15 +125,19 @@ impl WorkerAvailability {
///
/// Worker accepts Socket objects via unbounded channel and starts stream
/// processing.
pub(crate) struct Worker {
pub(crate) struct Worker<Exec>
where
Exec: ExecFactory,
{
rx: UnboundedReceiver<WorkerCommand>,
rx2: UnboundedReceiver<StopCommand>,
services: Vec<WorkerService>,
availability: WorkerAvailability,
conns: Counter,
factories: Vec<Box<dyn InternalServiceFactory>>,
state: WorkerState,
state: WorkerState<Exec>,
shutdown_timeout: time::Duration,
_exec: PhantomData<Exec>,
}
struct WorkerService {
@ -159,7 +163,10 @@ enum WorkerServiceStatus {
Stopped,
}
impl Worker {
impl<Exec> Worker<Exec>
where
Exec: ExecFactory,
{
pub(crate) fn start(
idx: usize,
factories: Vec<Box<dyn InternalServiceFactory>>,
@ -170,10 +177,10 @@ impl Worker {
let (tx2, rx2) = unbounded();
let avail = availability.clone();
Arbiter::new().send(
Arbiter::new_with::<Exec>().send(
async move {
availability.set(false);
let mut wrk = MAX_CONNS_COUNTER.with(move |conns| Worker {
let mut wrk: Worker<Exec> = MAX_CONNS_COUNTER.with(move |conns| Worker {
rx,
rx2,
availability,
@ -182,6 +189,7 @@ impl Worker {
services: Vec::new(),
conns: conns.clone(),
state: WorkerState::Unavailable(Vec::new()),
_exec: PhantomData,
});
let mut fut: Vec<MapOk<LocalBoxFuture<'static, _>, _>> = Vec::new();
@ -193,7 +201,7 @@ impl Worker {
}));
}
spawn(async move {
Exec::spawn(async move {
let res = join_all(fut).await;
let res: Result<Vec<_>, _> = res.into_iter().collect();
match res {
@ -228,7 +236,7 @@ impl Worker {
self.services.iter_mut().for_each(|srv| {
if srv.status == WorkerServiceStatus::Available {
srv.status = WorkerServiceStatus::Stopped;
actix_rt::spawn(
Exec::spawn(
srv.service
.call((None, ServerMessage::ForceShutdown))
.map(|_| ()),
@ -240,7 +248,7 @@ impl Worker {
self.services.iter_mut().for_each(move |srv| {
if srv.status == WorkerServiceStatus::Available {
srv.status = WorkerServiceStatus::Stopping;
actix_rt::spawn(
Exec::spawn(
srv.service
.call((None, ServerMessage::Shutdown(timeout)))
.map(|_| ()),
@ -297,7 +305,7 @@ impl Worker {
}
}
enum WorkerState {
enum WorkerState<Exec: ExecFactory> {
Available,
Unavailable(Vec<Conn>),
Restarting(
@ -306,14 +314,13 @@ enum WorkerState {
#[allow(clippy::type_complexity)]
Pin<Box<dyn Future<Output = Result<Vec<(Token, BoxedServerService)>, ()>>>>,
),
Shutdown(
Pin<Box<Delay>>,
Pin<Box<Delay>>,
Option<oneshot::Sender<bool>>,
),
Shutdown(Exec::Sleep, Exec::Sleep, Option<oneshot::Sender<bool>>),
}
impl Future for Worker {
impl<Exec> Future for Worker<Exec>
where
Exec: ExecFactory,
{
type Output = ();
// FIXME: remove this attribute
@ -335,8 +342,8 @@ impl Future for Worker {
if num != 0 {
info!("Graceful worker shutdown, {} connections", num);
self.state = WorkerState::Shutdown(
Box::pin(delay_until(Instant::now() + time::Duration::from_secs(1))),
Box::pin(delay_until(Instant::now() + self.shutdown_timeout)),
Exec::sleep(time::Duration::from_secs(1)),
Exec::sleep(self.shutdown_timeout),
Some(result),
);
} else {
@ -423,31 +430,24 @@ impl Future for Worker {
}
// check graceful timeout
match t2.as_mut().poll(cx) {
Poll::Pending => (),
Poll::Ready(_) => {
let _ = tx.take().unwrap().send(false);
self.shutdown(true);
Arbiter::current().stop();
return Poll::Ready(());
}
if Pin::new(t2).poll(cx).is_ready() {
let _ = tx.take().unwrap().send(false);
self.shutdown(true);
Arbiter::current().stop();
return Poll::Ready(());
}
// sleep for 1 second and then check again
match t1.as_mut().poll(cx) {
Poll::Pending => (),
Poll::Ready(_) => {
*t1 = Box::pin(delay_until(
Instant::now() + time::Duration::from_secs(1),
));
let _ = t1.as_mut().poll(cx);
}
if Pin::new(&mut *t1).poll(cx).is_ready() {
*t1 = Exec::sleep(time::Duration::from_secs(1));
let _ = Pin::new(t1).poll(cx);
}
Poll::Pending
}
WorkerState::Available => {
loop {
match Pin::new(&mut self.rx).poll_next(cx) {
return match Pin::new(&mut self.rx).poll_next(cx) {
// handle incoming io stream
Poll::Ready(Some(WorkerCommand(msg))) => {
match self.check_readiness(cx) {
@ -478,14 +478,14 @@ impl Future for Worker {
);
}
}
return self.poll(cx);
self.poll(cx)
}
Poll::Pending => {
self.state = WorkerState::Available;
return Poll::Pending;
Poll::Pending
}
Poll::Ready(None) => return Poll::Ready(()),
}
Poll::Ready(None) => Poll::Ready(()),
};
}
}
}

View File

@ -2,10 +2,12 @@ use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
use std::sync::{mpsc, Arc};
use std::{net, thread, time};
use actix_rt::DefaultExec;
use actix_server::Server;
use actix_service::fn_service;
use futures_util::future::{lazy, ok};
use socket2::{Domain, Protocol, Socket, Type};
use tokio::net::TcpStream;
fn unused_addr() -> net::SocketAddr {
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
@ -28,7 +30,9 @@ fn test_bind() {
Server::build()
.workers(1)
.disable_signals()
.bind("test", addr, move || fn_service(|_| ok::<_, ()>(())))
.bind("test", addr, move || {
fn_service(|_: TcpStream| ok::<_, ()>(()))
})
.unwrap()
.start()
}));
@ -165,8 +169,14 @@ fn test_configure() {
.listen("addr3", lst)
.apply(move |rt| {
let num = num.clone();
rt.service("addr1", fn_service(|_| ok::<_, ()>(())));
rt.service("addr3", fn_service(|_| ok::<_, ()>(())));
rt.service::<_, _, DefaultExec>(
"addr1",
fn_service(|_| ok::<_, ()>(())),
);
rt.service::<_, _, DefaultExec>(
"addr3",
fn_service(|_| ok::<_, ()>(())),
);
rt.on_start(lazy(move |_| {
let _ = num.fetch_add(1, Relaxed);
}))