From b3f7e53d0e9c81f8d1f5d52d31f721dc81a3eecb Mon Sep 17 00:00:00 2001 From: chrisp Date: Wed, 26 Aug 2020 17:35:48 -0400 Subject: [PATCH] Added a receive flush timeout just prior to launching the next poll when using a TCP connection. Without this, a slow response can cause transaction ID mismatches for the life of this TCP connection (it never recovers). This occurs when a remote device does answer, but it answers too slowly. The request has already timed out, and when the next request is launched the previous response is read (rather than the new one) causing this error. --- README.md | 9 ++++++++- tcpclient.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e952636..e1e4af4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ go modbus [![Build Status](https://travis-ci.org/goburrow/modbus.svg?branch=master)](https://travis-ci.org/goburrow/modbus) [![GoDoc](https://godoc.org/github.com/goburrow/modbus?status.svg)](https://godoc.org/github.com/goburrow/modbus) ========= -Fault-tolerant, fail-fast implementation of Modbus protocol in Go. +This is a fork of https://github.com/gburrow/modbus (possibly abandoned), +a Fault-tolerant, fail-fast implementation of Modbus protocol in Go. + +Bug fixes +------------------- +* Add a read buffer flush for tcp clients just prior to launching the next call. This + resolves transaction ID mismatches that cause the connection to never work + again. Supported functions ------------------- diff --git a/tcpclient.go b/tcpclient.go index 4e53c73..5cbd532 100644 --- a/tcpclient.go +++ b/tcpclient.go @@ -156,6 +156,18 @@ func (mb *tcpTransporter) Send(aduRequest []byte) (aduResponse []byte, err error // Set timer to close when idle mb.lastActivity = time.Now() mb.startCloseTimer() + + /* + * If an answer to a previously timed-out request is already in teh buffer, this will result + * in a transaction ID mismatch from which we will never recover. To prevent this, just + * flush any previous reponses before launching the next poll. That's throwing away + * possibly useful data, but the previous request was already satisfied with a timeout + * error so that probably makes the most sense here. + * + * Be aware that this call resets the read deadline. + */ + mb.flushAll() + // Set write and read timeout var timeout time.Time if mb.Timeout > 0 { @@ -252,6 +264,31 @@ func (mb *tcpTransporter) flush(b []byte) (err error) { return } +/** + * This function implements a non-blocking read flush. Be warned it resets + * the read deadline. + */ +func (mb *tcpTransporter) flushAll() (int, error) { + if err := mb.conn.SetReadDeadline(time.Now()); err != nil { + return 0, err + } + + count := 0 + buffer := make([]byte, 1024) + + for { + n, err := mb.conn.Read(buffer) + + if err != nil { + return count + n, err + } else if n > 0 { + count = count + n + } else { + /* didn't flush any new bytes, return */ + return count, err + } + } +} func (mb *tcpTransporter) logf(format string, v ...interface{}) { if mb.Logger != nil { mb.Logger.Printf(format, v...)