Skip to content

Do not resend reboot or speed test after a dropped connection - #555

Open
manueltarouca wants to merge 1 commit into
iMicknl:mainfrom
manueltarouca:fix/no-retry-after-disconnect-for-reboot
Open

manueltarouca wants to merge 1 commit into
iMicknl:mainfrom
manueltarouca:fix/no-retry-after-disconnect-for-reboot

Conversation

@manueltarouca

Copy link
Copy Markdown

Summary

reboot() can reboot the router up to 5 times. Same for run_speed_test().

Problem

__post retries every request up to 5 times on ServerDisconnectedError / ClientOSError.
A router that accepts a reboot drops the connection without replying. The client sees a
disconnect and sends the reboot again.

Reproduced with a local server that counts requests and drops each connection:
one reboot() call → 5 reboot requests received.

Fix

  • reboot() and run_speed_test() retry only when the connection could not be opened
    (ClientConnectorError). The request never left the client, so a retry is safe.
  • All other requests keep the current retry behaviour.

Trade-off: if a pooled keep-alive connection was closed by the router just before reboot(),
the call now raises ConnectionError instead of retrying. Callers usually login() right
before reboot(), so the connection is fresh.

Not changed: login() still uses the retrying path. A replayed login can use up router
sessions (XMO_MAX_SESSION_COUNT_ERR). Happy to look at that in a separate PR.

Tests

tests/unit/test_request_retries.py, using a local TCP server that reads the request, then drops the connection:

  • reboot / run_speed_test: sent once, then ConnectionError. Fails on main (sent 5 times).
  • get_value_by_xpath: still retried after a disconnect.

The transport retry in __post resent every request up to five times on
ServerDisconnectedError/ClientOSError. For a reboot the router drops the
connection because it accepted the request, so one reboot() call reached
the router five times. Non-idempotent actions now only retry when the
connection could not be opened (ClientConnectorError).
@github-actions github-actions Bot added the bug Something isn't working label Oct 21, 2026

@iMicknl iMicknl left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks!

Comment thread sagemcom_api/client.py
Comment on lines +220 to +225
@backoff.on_exception(
backoff.expo,
ClientConnectorError,
max_tries=5,
)
async def __post_once(self, url, data):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Should the on exception not be moved to the __send? It seems like it is now added to the wrong function?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It's intentional. __send does the request and has no retry. The two wrappers add different retry rules:

  • __post: retries on any connection error, same as before. Used by everything else (login, get, set, logout).
  • __post_once: retries only on ClientConnectorError, i.e. the request never left the client. Used by reboot and run_speed_test.

If the ClientConnectorError retry moved to __send, __post would retry it a second time (ClientConnectorError is a subclass of ClientOSError), so a connect failure could take up to 25 attempts.

I can add a short comment above __send if that helps, or restructure it if you prefer a different split.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants