Skip to content

Sync callback poller can spin after Engine.IO socket stops #508

Description

@jimmystridh

Summary

The sync client callback polling thread can spin indefinitely after the underlying Engine.IO socket has stopped.

When RawClient::poll() returns Ok(None), Iter::next() converts that into Some(Err(Error::StoppedEngineIoSocket)). The callback poller then receives that error, but poll_callback only treats IncompleteResponseFromEngineIo and Socket.IO Disconnect packets as reconnect/stop conditions. Other errors currently fall through as no-ops, so the for packet in self_clone.iter() loop immediately polls again.

That creates a tight loop when the Engine.IO socket stays stopped.

Impact

In a long-running sync client process using a realtime Socket.IO feed, this showed up as a background polling thread consuming a significant portion of a small ARMv6 device CPU for multiple days after the feed had stopped. The application itself was otherwise idle and waiting for events.

This is especially noticeable on small or embedded devices, but the same loop can waste CPU anywhere the stopped socket state persists.

Expected behavior

A stopped Engine.IO socket should be handled as a terminal or reconnect condition:

  • If reconnect is enabled, attempt reconnect using the existing reconnect path.
  • If reconnect is disabled, stop the callback poller instead of continuing to poll immediately.
  • Notify the close callback once when polling stops because the underlying Engine.IO socket is closed.

Actual behavior

StoppedEngineIoSocket is ignored by the sync callback poller. The iterator keeps yielding the same error and the polling thread spins without backoff.

Relevant code path

  • socketio/src/client/client.rs: Iter::next() maps Ok(None) to Error::StoppedEngineIoSocket.
  • socketio/src/client/client.rs: poll_callback() does not currently handle that error as a stop or reconnect condition.

Proposed fix

I opened a draft PR with a small fix and regression coverage. I will link it below.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions