Skip to content

Commit f68a4a3

Browse files
authored
Remove obsolete FunctionCode::Disconnect (#273)
Use AsyncWriteExt::shutdown() to release all resources.
1 parent 9611c5f commit f68a4a3

File tree

14 files changed

+129
-157
lines changed

14 files changed

+129
-157
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
`FunctionCode`.
1616
- Added `ExceptionCode::Custom`.
1717
- Removed `TryFrom<u8>` and `#[repr(u8)]` for `Exception`.
18+
- Removed `FunctionCode::Disconnect`.
19+
- Client: Added new `disconnect()` method to trait that returns
20+
`std::io::Result`.
1821

1922
## v0.14.1 (2024-09-10)
2023

@@ -32,7 +35,7 @@
3235

3336
### Breaking Changes
3437

35-
- Add `FunctionCode::Disconnect`.
38+
- Added `FunctionCode::Disconnect`.
3639

3740
## v0.13.0 (2024-06-23, yanked)
3841

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ log = "0.4.20"
3434
smallvec = { version = "1.13.1", optional = true, default-features = false }
3535
socket2 = { version = "0.5.5", optional = true, default-features = false }
3636
thiserror = "1.0.58"
37-
tokio = { version = "1.35.1", default-features = false }
37+
tokio = { version = "1.35.1", default-features = false, features = ["io-util"] }
3838
# Disable default-features to exclude unused dependency on libudev
3939
tokio-serial = { version = "5.4.4", optional = true, default-features = false }
4040
tokio-util = { version = "0.7.10", optional = true, default-features = false, features = [

examples/rtu-client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
2121
println!("Sensor value is: {rsp:?}");
2222

2323
println!("Disconnecting");
24-
ctx.disconnect().await??;
24+
ctx.disconnect().await?;
2525

2626
Ok(())
2727
}

examples/tcp-client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
2323
println!("The coupler ID is '{id}'");
2424

2525
println!("Disconnecting");
26-
ctx.disconnect().await??;
26+
ctx.disconnect().await?;
2727

2828
Ok(())
2929
}

examples/tls-client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
117117
println!("Reading Holding Registers");
118118
let data = ctx.read_holding_registers(40000, 68).await?;
119119
println!("Holding Registers Data is '{:?}'", data);
120-
ctx.disconnect().await??;
120+
ctx.disconnect().await?;
121121

122122
Ok(())
123123
}

src/client/mod.rs

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::{borrow::Cow, fmt::Debug, io};
77

88
use async_trait::async_trait;
99

10-
use crate::{frame::*, slave::*, Error, Result};
10+
use crate::{frame::*, slave::*, Result};
1111

1212
#[cfg(feature = "rtu")]
1313
pub mod rtu;
@@ -21,11 +21,22 @@ pub mod sync;
2121
/// Transport independent asynchronous client trait
2222
#[async_trait]
2323
pub trait Client: SlaveContext + Send + Debug {
24-
/// Invoke a _Modbus_ function
24+
/// Invokes a _Modbus_ function.
2525
async fn call(&mut self, request: Request<'_>) -> Result<Response>;
26+
27+
/// Disconnects the client.
28+
///
29+
/// Permanently disconnects the client by shutting down the
30+
/// underlying stream in a graceful manner (`AsyncDrop`).
31+
///
32+
/// Dropping the client without explicitly disconnecting it
33+
/// beforehand should also work and free all resources. The
34+
/// actual behavior might depend on the underlying transport
35+
/// protocol (RTU/TCP) that is used by the client.
36+
async fn disconnect(&mut self) -> io::Result<()>;
2637
}
2738

28-
/// Asynchronous Modbus reader
39+
/// Asynchronous _Modbus_ reader
2940
#[async_trait]
3041
pub trait Reader: Client {
3142
/// Read multiple coils (0x01)
@@ -83,22 +94,6 @@ pub struct Context {
8394
client: Box<dyn Client>,
8495
}
8596

86-
impl Context {
87-
/// Disconnect the client
88-
pub async fn disconnect(&mut self) -> Result<()> {
89-
// Disconnecting is expected to fail!
90-
let res = self.client.call(Request::Disconnect).await;
91-
match res {
92-
Ok(_) => unreachable!(),
93-
Err(Error::Transport(err)) => match err.kind() {
94-
io::ErrorKind::NotConnected | io::ErrorKind::BrokenPipe => Ok(Ok(())),
95-
_ => Err(Error::Transport(err)),
96-
},
97-
Err(err) => Err(err),
98-
}
99-
}
100-
}
101-
10297
impl From<Box<dyn Client>> for Context {
10398
fn from(client: Box<dyn Client>) -> Self {
10499
Self { client }
@@ -116,6 +111,10 @@ impl Client for Context {
116111
async fn call(&mut self, request: Request<'_>) -> Result<Response> {
117112
self.client.call(request).await
118113
}
114+
115+
async fn disconnect(&mut self) -> io::Result<()> {
116+
self.client.disconnect().await
117+
}
119118
}
120119

121120
impl SlaveContext for Context {
@@ -319,10 +318,10 @@ impl Writer for Context {
319318

320319
#[cfg(test)]
321320
mod tests {
322-
use crate::Result;
321+
use crate::{Error, Result};
323322

324323
use super::*;
325-
use std::sync::Mutex;
324+
use std::{io, sync::Mutex};
326325

327326
#[derive(Default, Debug)]
328327
pub(crate) struct ClientMock {
@@ -358,6 +357,10 @@ mod tests {
358357
Err(err) => Err(err),
359358
}
360359
}
360+
361+
async fn disconnect(&mut self) -> io::Result<()> {
362+
Ok(())
363+
}
361364
}
362365

363366
impl SlaveContext for ClientMock {

src/codec/mod.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ impl<'a> TryFrom<Request<'a>> for Bytes {
103103
data.put_u8(*d);
104104
}
105105
}
106-
Disconnect => unreachable!(),
107106
}
108107
Ok(data.freeze())
109108
}
@@ -460,7 +459,6 @@ fn request_byte_count(req: &Request<'_>) -> usize {
460459
MaskWriteRegister(_, _, _) => 7,
461460
ReadWriteMultipleRegisters(_, _, _, ref data) => 10 + data.len() * 2,
462461
Custom(_, ref data) => 1 + data.len(),
463-
Disconnect => unreachable!(),
464462
}
465463
}
466464

src/codec/rtu.rs

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -315,13 +315,7 @@ impl Decoder for ServerCodec {
315315
// to transmission errors, because the frame's bytes
316316
// have already been verified with the CRC.
317317
RequestPdu::try_from(pdu_data)
318-
.map(|pdu| {
319-
Some(RequestAdu {
320-
hdr,
321-
pdu,
322-
disconnect: false,
323-
})
324-
})
318+
.map(|pdu| Some(RequestAdu { hdr, pdu }))
325319
.map_err(|err| {
326320
// Unrecoverable error
327321
log::error!("Failed to decode request PDU: {}", err);
@@ -334,21 +328,7 @@ impl<'a> Encoder<RequestAdu<'a>> for ClientCodec {
334328
type Error = Error;
335329

336330
fn encode(&mut self, adu: RequestAdu<'a>, buf: &mut BytesMut) -> Result<()> {
337-
let RequestAdu {
338-
hdr,
339-
pdu,
340-
disconnect,
341-
} = adu;
342-
if disconnect {
343-
// The disconnect happens implicitly after letting this request
344-
// fail by returning an error. This will drop the attached
345-
// transport, e.g. for closing a stale, exclusive connection
346-
// to a serial port before trying to reconnect.
347-
return Err(Error::new(
348-
ErrorKind::NotConnected,
349-
"Disconnecting - not an error",
350-
));
351-
}
331+
let RequestAdu { hdr, pdu } = adu;
352332
let pdu_data: Bytes = pdu.try_into()?;
353333
buf.reserve(pdu_data.len() + 3);
354334
buf.put_u8(hdr.slave_id);
@@ -741,11 +721,7 @@ mod tests {
741721
let pdu = req.into();
742722
let slave_id = 0x01;
743723
let hdr = Header { slave_id };
744-
let adu = RequestAdu {
745-
hdr,
746-
pdu,
747-
disconnect: false,
748-
};
724+
let adu = RequestAdu { hdr, pdu };
749725
codec.encode(adu, &mut buf).unwrap();
750726

751727
assert_eq!(
@@ -761,11 +737,7 @@ mod tests {
761737
let pdu = req.into();
762738
let slave_id = 0x01;
763739
let hdr = Header { slave_id };
764-
let adu = RequestAdu {
765-
hdr,
766-
pdu,
767-
disconnect: false,
768-
};
740+
let adu = RequestAdu { hdr, pdu };
769741
let mut buf = BytesMut::with_capacity(40);
770742
#[allow(unsafe_code)]
771743
unsafe {

src/codec/tcp.rs

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,7 @@ impl Decoder for ServerCodec {
125125
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<RequestAdu<'static>>> {
126126
if let Some((hdr, pdu_data)) = self.decoder.decode(buf)? {
127127
let pdu = RequestPdu::try_from(pdu_data)?;
128-
Ok(Some(RequestAdu {
129-
hdr,
130-
pdu,
131-
disconnect: false,
132-
}))
128+
Ok(Some(RequestAdu { hdr, pdu }))
133129
} else {
134130
Ok(None)
135131
}
@@ -140,20 +136,7 @@ impl<'a> Encoder<RequestAdu<'a>> for ClientCodec {
140136
type Error = Error;
141137

142138
fn encode(&mut self, adu: RequestAdu<'a>, buf: &mut BytesMut) -> Result<()> {
143-
let RequestAdu {
144-
hdr,
145-
pdu,
146-
disconnect,
147-
} = adu;
148-
if disconnect {
149-
// The disconnect happens implicitly after letting this request
150-
// fail by returning an error. This will drop the attached
151-
// transport, e.g. for terminating an open connection.
152-
return Err(Error::new(
153-
ErrorKind::NotConnected,
154-
"Disconnecting - not an error",
155-
));
156-
}
139+
let RequestAdu { hdr, pdu } = adu;
157140
let pdu_data: Bytes = pdu.try_into()?;
158141
buf.reserve(pdu_data.len() + 7);
159142
buf.put_u16(hdr.transaction_id);
@@ -288,11 +271,7 @@ mod tests {
288271
transaction_id: TRANSACTION_ID,
289272
unit_id: UNIT_ID,
290273
};
291-
let adu = RequestAdu {
292-
hdr,
293-
pdu,
294-
disconnect: false,
295-
};
274+
let adu = RequestAdu { hdr, pdu };
296275
codec.encode(adu, &mut buf).unwrap();
297276
// header
298277
assert_eq!(buf[0], TRANSACTION_ID_HI);
@@ -316,11 +295,7 @@ mod tests {
316295
transaction_id: TRANSACTION_ID,
317296
unit_id: UNIT_ID,
318297
};
319-
let adu = RequestAdu {
320-
hdr,
321-
pdu,
322-
disconnect: false,
323-
};
298+
let adu = RequestAdu { hdr, pdu };
324299
let mut buf = BytesMut::with_capacity(40);
325300
#[allow(unsafe_code)]
326301
unsafe {

src/frame/mod.rs

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,6 @@ pub enum FunctionCode {
7979

8080
/// Custom Modbus Function Code.
8181
Custom(u8),
82-
83-
Disconnect,
8482
}
8583

8684
impl FunctionCode {
@@ -111,11 +109,7 @@ impl FunctionCode {
111109
}
112110
}
113111

114-
/// Get the [`u8`] value of the current [`FunctionCode`].
115-
///
116-
/// # Panics
117-
///
118-
/// Panics on [`Disconnect`](Self::Disconnect) which has no corresponding Modbus function code.
112+
/// Gets the [`u8`] value of the current [`FunctionCode`].
119113
#[must_use]
120114
pub const fn value(self) -> u8 {
121115
match self {
@@ -139,7 +133,6 @@ impl FunctionCode {
139133
Self::ReadFifoQueue => 0x18,
140134
Self::EncapsulatedInterfaceTransport => 0x2B,
141135
Self::Custom(code) => code,
142-
Self::Disconnect => unreachable!(),
143136
}
144137
}
145138
}
@@ -235,17 +228,6 @@ pub enum Request<'a> {
235228
/// The first parameter is the Modbus function code.
236229
/// The second parameter is the raw bytes of the request.
237230
Custom(u8, Cow<'a, [u8]>),
238-
239-
/// A poison pill for stopping the client service and to release
240-
/// the underlying transport, e.g. for disconnecting from an
241-
/// exclusively used serial port.
242-
///
243-
/// This is an ugly workaround, because `tokio-proto` does not
244-
/// provide other means to gracefully shut down the `ClientService`.
245-
/// Otherwise the bound transport is never freed as long as the
246-
/// executor is active, even when dropping the Modbus client
247-
/// context.
248-
Disconnect,
249231
}
250232

251233
impl<'a> Request<'a> {
@@ -275,7 +257,6 @@ impl<'a> Request<'a> {
275257
ReadWriteMultipleRegisters(addr, qty, write_addr, Cow::Owned(words.into_owned()))
276258
}
277259
Custom(func, bytes) => Custom(func, Cow::Owned(bytes.into_owned())),
278-
Disconnect => Disconnect,
279260
}
280261
}
281262

@@ -304,8 +285,6 @@ impl<'a> Request<'a> {
304285
ReadWriteMultipleRegisters(_, _, _, _) => FunctionCode::ReadWriteMultipleRegisters,
305286

306287
Custom(code, _) => FunctionCode::Custom(*code),
307-
308-
Disconnect => unreachable!(),
309288
}
310289
}
311290
}

0 commit comments

Comments
 (0)