Skip to content

Conversation

@Lash-L
Copy link
Collaborator

@Lash-L Lash-L commented Dec 4, 2025

Tested twice:

Without pinging:
9 disconnects in 5 minutes

With pinging:
0 disconnects in 5 minutes

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a keep-alive mechanism to the local client to prevent disconnections. Testing showed a significant improvement: 9 disconnects in 5 minutes without pinging versus 0 disconnects with pinging enabled.

Key Changes

  • Added periodic ping functionality to maintain connection stability
  • Implemented keep-alive task lifecycle management (creation on connect, cancellation on disconnect)
  • Added exception handling for ping failures to ensure loop continues

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +147 to +157
async def _keep_alive_loop(self) -> None:
while self._is_connected:
try:
await asyncio.sleep(_PING_INTERVAL)
if self._is_connected:
await self._ping()
except asyncio.CancelledError:
break
except Exception:
_LOGGER.debug("Keep-alive ping failed", exc_info=True)
# Retry next interval
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The new keep-alive functionality lacks test coverage. Consider adding tests to verify:

  1. That _keep_alive_task is created when connect() is called
  2. That the task is properly canceled when close() or _connection_lost() is called
  3. That the ping loop continues to execute periodically while connected
  4. That exceptions in the ping loop are handled gracefully without stopping the loop

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Copy link
Contributor

Copilot AI commented Dec 4, 2025

@Lash-L I've opened a new pull request, #628, to work on those changes. Once the pull request is ready, I'll request review from you.

* Initial plan

* feat: Add comprehensive test coverage for keep-alive functionality

Co-authored-by: Lash-L <20257911+Lash-L@users.noreply.github.com>

* refactor: Address code review feedback on keep-alive tests

Co-authored-by: Lash-L <20257911+Lash-L@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Lash-L <20257911+Lash-L@users.noreply.github.com>
Copy link
Contributor

@allenporter allenporter left a comment

Choose a reason for hiding this comment

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

I have requested changes for a follow up PR -- approving now so we can merge.

# Perform protocol negotiation
try:
await self._hello()
self._keep_alive_task = asyncio.create_task(self._keep_alive_loop())
Copy link
Contributor

Choose a reason for hiding this comment

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

Move this outside the scope of the try/catch. It won't throw RoborockException

await self._ping()
except asyncio.CancelledError:
break
except Exception:
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's catch RoborockException (expected) separate from uncaught exceptions so we can log them separately e.g. "ping failed" vs "Uncaught exception" implying a bug/shouldn't happen case we need to fix. Similar to _background_reconnect in v1 channel?

_LOGGER = logging.getLogger(__name__)
_PORT = 58867
_TIMEOUT = 5.0
_PING_INTERVAL = 10
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you use a timedelta here to be more explicit on the unit?

Suggested change
_PING_INTERVAL = 10
_PING_INTERVAL = datetime.timedelta(seconds=10)

async def _keep_alive_loop(self) -> None:
while self._is_connected:
try:
await asyncio.sleep(_PING_INTERVAL)
Copy link
Contributor

Choose a reason for hiding this comment

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

Assuming you accept the above suggestion

Suggested change
await asyncio.sleep(_PING_INTERVAL)
await asyncio.sleep(_PING_INTERVAL.total_seconds())

assert channel._is_connected is True


async def test_keep_alive_task_created_on_connect(local_channel: LocalChannel, mock_loop: Mock) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

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

We should avoid these tests of internal state like this generally, i'm not sure its worhtwhile to keep?

@Lash-L Lash-L merged commit a802f66 into main Dec 4, 2025
7 checks passed
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.

3 participants