Skip to content
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

fix tcp retransmission by reading up to 10 frames #172

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

chenxinghua
Copy link

No description provided.

Signed-off-by: chenxinghua <chenxinghua@bsa.org>
@chenxinghua chenxinghua marked this pull request as ready for review March 13, 2023 03:11
@chenxinghua
Copy link
Author

when verify_response_header failed, try reading more frames

Copy link
Member

@uklotzde uklotzde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking care. I have left some comments with questions.

if retries >= MAX_RETRIES {
return Err(e);
}
retries += 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error should at least be logged instead of silently ignoring it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
retries += 1;
retries += 1;
log::info!("Try to recover from header mismatch: expected/request = {:?}, actual/response = {:?}, retries = {:?}", req_hdr, res_adu.hdr, retries);

log like this is acceptable?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please modify the code directly instead of replying to comments. Commits can be squashed later before merging.

@@ -18,6 +18,7 @@ use crate::{
};

const INITIAL_TRANSACTION_ID: TransactionId = 0;
const MAX_RETRIES: usize = 10;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Max retries of what? Why 10? If this is an arbitrary number then it should better be configurable instead of a hard-coded constant.

A comment with references why this retry algorithm is needed is also required. Either by documenting the constant or the corresponding configuration option.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let MAX_RETRIES be configurable is more suitable. Can you give me some advice? I have not found how to add configurable code in this crate.

The retry algorithm is inspired by libmodbus's error recovery mode, see https://libmodbus.org/reference/modbus_set_error_recovery/ for details.

When MODBUS_ERROR_RECOVERY_PROTOCOL is set, a sleep and flush sequence will be used to clean up the ongoing communication, this can occurs when the message length is invalid, the TID is wrong or the received function code is not the expected one. The response timeout delay will be used to sleep.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add all information that is required reason about design decision to the code. No one will read this PR later after it has been merged.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this parameter should be configurable then make a proposal. Otherwise use a constant and comment (in the code) why.

chenxinghua added 2 commits March 14, 2023 09:18
Signed-off-by: chenxinghua <chenxinghua@bsa.org>
Signed-off-by: chenxinghua <chenxinghua@bsa.org>
/// response frame can be discarded. This strategy is inspired by
/// libmodbus's error recovery:
/// https://libmodbus.org/reference/modbus_set_error_recovery/
pub async fn connect_slave_recover(socket_addr: SocketAddr, slave: Slave, max_recover_retries: usize) -> Result<Context, Error> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The number of factory functions for creating a client context gets confusing with all the possible combinations. I guess switching to a builder pattern would be more appropriate, although this is bigger tasks apart from this PR.

@flosse Your thoughts?

@@ -26,6 +26,20 @@ pub async fn connect_slave(socket_addr: SocketAddr, slave: Slave) -> Result<Cont
Ok(context)
}

/// Connect to a physical, broadcast, or custom Modbus device,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Please try to use short sentences
  • The initial line or sentence should be the summary, followed by one or more paragraphs with details.

But let's wait until we decided if you use an implicit constant for the time being before we are switching to a configurable parameter when introducing the builder pattern.

/// reponse frame will arrived, with this strategy the mismatched
/// response frame can be discarded. This strategy is inspired by
/// libmodbus's error recovery:
/// https://libmodbus.org/reference/modbus_set_error_recovery/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When following the link I don't see any connection between the recovery modes offered by libmodus and this parameter. They don't even mention any retires? This is more confusing than it helps.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I quickly checked the rodbus implementation and didn't find anything similar.

Please elaborate why this retry is needed and desired. We should not introduce common parameters that only solve a particular use case.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In libmodbus, discard ready frames when transcation id mismatch and error recover strategy enabled.

        rc = ctx->backend->pre_check_confirmation(ctx, req, rsp, rsp_length);
        if (rc == -1) {
            if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) {
                _sleep_response_timeout(ctx);
                modbus_flush(ctx);
            }
            return -1;
        }

I assume that, first several (1 or 2) frames are mismatch, the ok frame may in the remaining.

For situation in #111 discard one frame, the remaining can work.

@uklotzde
Copy link
Member

I am concerned that this might only be a quick band aid for a more general issue, namely that response messages could arrive out of order. Is this even possible or allowed over a single connection? I am not deeply familiar with the Modbus specifications.

Signed-off-by: chenxinghua <chenxinghua@bsa.org>
@chenxinghua
Copy link
Author

any further suggestion? should I let MAX_RETRIES constant hardcoded for accepted. @uklotzde

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants