-
Notifications
You must be signed in to change notification settings - Fork 25
TcpChannelTask Refactoring #162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
58ff150
cd0b999
aeb22ce
122f88e
7e1a380
a4c3303
f803fbc
3a2f731
7e96390
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
use std::error::Error; | ||
|
||
use tracing::Instrument; | ||
|
||
use crate::client::{Channel, ClientState, HostAddr, Listener}; | ||
|
@@ -9,38 +11,37 @@ use crate::client::task::{ClientLoop, SessionError, StateChange}; | |
use crate::common::frame::{FrameWriter, FramedReader}; | ||
use crate::error::Shutdown; | ||
use crate::retry::RetryStrategy; | ||
use crate::{ClientOptions, ConnectionLoggingStrategy}; | ||
|
||
use tokio::net::TcpStream; | ||
|
||
pub(crate) fn spawn_tcp_channel( | ||
host: HostAddr, | ||
max_queued_requests: usize, | ||
connect_retry: Box<dyn RetryStrategy>, | ||
decode: DecodeLevel, | ||
listener: Box<dyn Listener<ClientState>>, | ||
client_options: ClientOptions, | ||
) -> Channel { | ||
let (handle, task) = | ||
create_tcp_channel(host, max_queued_requests, connect_retry, decode, listener); | ||
let (handle, task) = create_tcp_channel(host, connect_retry, listener, client_options); | ||
tokio::spawn(task); | ||
handle | ||
} | ||
|
||
pub(crate) fn create_tcp_channel( | ||
host: HostAddr, | ||
max_queued_requests: usize, | ||
connect_retry: Box<dyn RetryStrategy>, | ||
decode: DecodeLevel, | ||
listener: Box<dyn Listener<ClientState>>, | ||
options: ClientOptions, | ||
) -> (Channel, impl std::future::Future<Output = ()>) { | ||
let (tx, rx) = tokio::sync::mpsc::channel(max_queued_requests); | ||
let (tx, rx) = tokio::sync::mpsc::channel(options.max_queued_requests); | ||
let task = async move { | ||
TcpChannelTask::new( | ||
host.clone(), | ||
rx.into(), | ||
TcpTaskConnectionHandler::Tcp, | ||
connect_retry, | ||
decode, | ||
options.decode, | ||
listener, | ||
options.connection_logging_strategy, | ||
) | ||
.run() | ||
.instrument(tracing::info_span!("Modbus-Client-TCP", endpoint = ?host)) | ||
|
@@ -75,6 +76,7 @@ pub(crate) struct TcpChannelTask { | |
connection_handler: TcpTaskConnectionHandler, | ||
client_loop: ClientLoop, | ||
listener: Box<dyn Listener<ClientState>>, | ||
_logging_strategy: ConnectionLoggingStrategy, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The field If this is for future use, please add a |
||
} | ||
|
||
impl TcpChannelTask { | ||
|
@@ -85,13 +87,15 @@ impl TcpChannelTask { | |
connect_retry: Box<dyn RetryStrategy>, | ||
decode: DecodeLevel, | ||
listener: Box<dyn Listener<ClientState>>, | ||
_logging_strategy: ConnectionLoggingStrategy, | ||
) -> Self { | ||
Self { | ||
host, | ||
connect_retry, | ||
connection_handler, | ||
client_loop: ClientLoop::new(rx, FrameWriter::tcp(), FramedReader::tcp(), decode), | ||
listener, | ||
_logging_strategy, | ||
} | ||
} | ||
|
||
|
@@ -133,20 +137,7 @@ impl TcpChannelTask { | |
async fn try_connect_and_run(&mut self) -> Result<(), StateChange> { | ||
self.listener.update(ClientState::Connecting).get().await; | ||
match self.connect().await? { | ||
Err(err) => { | ||
let delay = self.connect_retry.after_failed_connect(); | ||
tracing::warn!( | ||
"failed to connect to {}: {} - waiting {} ms before next attempt", | ||
self.host, | ||
err, | ||
delay.as_millis() | ||
); | ||
self.listener | ||
.update(ClientState::WaitAfterFailedConnect(delay)) | ||
.get() | ||
.await; | ||
self.client_loop.fail_requests_for(delay).await | ||
} | ||
Err(err) => self.failed_tcp_stream_connection(err).await, | ||
Ok(socket) => { | ||
if let Ok(addr) = socket.peer_addr() { | ||
tracing::info!("connected to: {}", addr); | ||
|
@@ -155,44 +146,61 @@ impl TcpChannelTask { | |
tracing::warn!("unable to enable TCP_NODELAY: {}", err); | ||
} | ||
match self.connection_handler.handle(socket, &self.host).await { | ||
Err(err) => { | ||
let delay = self.connect_retry.after_failed_connect(); | ||
tracing::warn!( | ||
"{} - waiting {} ms before next attempt", | ||
err, | ||
delay.as_millis() | ||
); | ||
self.listener | ||
.update(ClientState::WaitAfterFailedConnect(delay)) | ||
.get() | ||
.await; | ||
self.client_loop.fail_requests_for(delay).await | ||
} | ||
Ok(mut phys) => { | ||
self.listener.update(ClientState::Connected).get().await; | ||
// reset the retry strategy now that we have a successful connection | ||
// we do this here so that the reset happens after a TLS handshake | ||
self.connect_retry.reset(); | ||
// run the physical layer independent processing loop | ||
match self.client_loop.run(&mut phys).await { | ||
// the mpsc was closed, end the task | ||
SessionError::Shutdown => Err(StateChange::Shutdown), | ||
// re-establish the connection | ||
SessionError::Disabled | ||
| SessionError::IoError(_) | ||
| SessionError::BadFrame => { | ||
let delay = self.connect_retry.after_disconnect(); | ||
tracing::warn!("waiting {:?} to reconnect", delay); | ||
self.listener | ||
.update(ClientState::WaitAfterDisconnect(delay)) | ||
.get() | ||
.await; | ||
self.client_loop.fail_requests_for(delay).await | ||
} | ||
} | ||
} | ||
Err(err) => self.failed_tcp_connection(err).await, | ||
Ok(phys) => self.connected(phys).await, | ||
} | ||
} | ||
} | ||
} | ||
|
||
async fn connected(&mut self, mut phys: PhysLayer) -> Result<(), StateChange> { | ||
self.listener.update(ClientState::Connected).get().await; | ||
// reset the retry strategy now that we have a successful connection | ||
// we do this here so that the reset happens after a TLS handshake | ||
self.connect_retry.reset(); | ||
// run the physical layer independent processing loop | ||
match self.client_loop.run(&mut phys).await { | ||
// the mpsc was closed, end the task | ||
SessionError::Shutdown => Err(StateChange::Shutdown), | ||
// re-establish the connection | ||
SessionError::Disabled | SessionError::IoError(_) | SessionError::BadFrame => { | ||
let delay = self.connect_retry.after_disconnect(); | ||
tracing::warn!("waiting {:?} to reconnect", delay); | ||
self.listener | ||
.update(ClientState::WaitAfterDisconnect(delay)) | ||
.get() | ||
.await; | ||
self.client_loop.fail_requests_for(delay).await | ||
} | ||
} | ||
} | ||
|
||
async fn failed_tcp_connection(&mut self, err: String) -> Result<(), StateChange> { | ||
let delay = self.connect_retry.after_failed_connect(); | ||
tracing::warn!( | ||
"{} - waiting {} ms before next attempt", | ||
err, | ||
delay.as_millis() | ||
); | ||
self.listener | ||
.update(ClientState::WaitAfterFailedConnect(delay)) | ||
.get() | ||
.await; | ||
self.client_loop.fail_requests_for(delay).await | ||
} | ||
|
||
async fn failed_tcp_stream_connection<T: Error>(&mut self, err: T) -> Result<(), StateChange> { | ||
let delay = self.connect_retry.after_failed_connect(); | ||
tracing::warn!( | ||
"failed to connect to {}: {} - waiting {} ms before next attempt", | ||
self.host, | ||
err, | ||
delay.as_millis() | ||
); | ||
self.listener | ||
.update(ClientState::WaitAfterFailedConnect(delay)) | ||
.get() | ||
.await; | ||
self.client_loop.fail_requests_for(delay).await | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,7 +15,7 @@ use crate::common::phys::PhysLayer; | |
use crate::tcp::client::{TcpChannelTask, TcpTaskConnectionHandler}; | ||
use crate::tcp::tls::{CertificateMode, MinTlsVersion, TlsError}; | ||
|
||
use crate::DecodeLevel; | ||
use crate::{ConnectionLoggingStrategy, DecodeLevel}; | ||
|
||
/// TLS configuration | ||
pub struct TlsClientConfig { | ||
|
@@ -60,6 +60,7 @@ pub(crate) fn create_tls_channel( | |
connect_retry, | ||
decode, | ||
listener, | ||
ConnectionLoggingStrategy::All, | ||
) | ||
Comment on lines
60
to
64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The refactoring to use For API consistency, the TLS client creation functions ( |
||
.run() | ||
.instrument(tracing::info_span!("Modbus-Client-TCP", endpoint = ?host)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function name
spawn_tcp_client_task_2
is not idiomatic. A more descriptive name likespawn_tcp_client_task_with_options
would better reflect its purpose. Consider also deprecating the olderspawn_tcp_client_task
function to guide users towards the new, more flexible API.