Skip to content

agent: reconnect to the servers when the network changes - #1927

Draft
Narasimha-sc wants to merge 2 commits into
nd/net-socket-timeoutfrom
nd/net-close-clients
Draft

Narasimha-sc wants to merge 2 commits into
nd/net-socket-timeoutfrom
nd/net-close-clients

Conversation

@Narasimha-sc

@Narasimha-sc Narasimha-sc commented Oct 5, 2026 •

Copy link
Copy Markdown

After the network changes (e.g. the IP address changed after internet loss) the connections to the servers are dead, but the OS reports it only after TCP retransmissions time out - ~15 minutes on Linux/Android while the agent keeps writing to the dead socket. In a measured trace the online event woke the sender immediately and it then retried SEND on the dead client 35 times, every ~23 s, until the kernel gave up.

setUserNetworkInfo on every online network event now:

  • closes the connected SMP and NTF clients and fails their pending requests, so that the workers that resume do not wait for responses that will not arrive - the requests are failed only once the client's threads terminated, which the client now records (each client type bounded by 1 second);
  • discards the errors of the clients that failed to connect, cached for persistErrorInterval, and removes a client that is still connecting, in the transaction that reads it, so that resumed workers create a new client instead of waiting for a connection made via the previous network;
  • closes the XFTP clients asynchronously, removing them from the map.

A client that is no longer the one in the map is rejected in the same transaction that would publish it, so the requests made on it do not wait for the timeout. The clients are closed in a forked thread so the API does not block, and the closes of an event that a newer one superseded are skipped. When the agent was offline the reported info is applied when the clients are closed, so the workers waiting for the network do not resume before then.

As the clients are closed, a network change now produces DISCONNECT/DOWN followed by CONNECT/UP, as any other reconnection.

The retry loops are not restarted here, so a worker already waiting out its retry interval resumes only when it expires - that is a separate PR in this stack.


Stack

Reducing reconnection delay after a network change. Each PR is based on the previous one and can be merged on its own once the ones below it are in.

PR base
#1926 transport: bound unacknowledged-data failure time master 894 s -> 95 s, no app change, independent
#1927 agent: reconnect to the servers when the network changes #1926 the 15-minute stall
#1928 agent: fix three races closing clients makes reachable #1927 correctness guard for #1927
#1929 agent: restart the retry loops when the network changes #1928 retry interval -> ~3 s
#1930 agent: restart retry loops when reconnecting all servers #1929 manual reconnect only
simplex-chat/simplex-chat#7650 android, ios: pass the chat controller to the network info command master crash fix, independent
simplex-chat/simplex-chat#7651 android, ios: report a network change when the network identity changes #7650 app side of simplexmq#1927
simplex-chat/simplex-chat#7652 desktop: report network changes to the core #7651 new capability

… data takes to fail

TCP keep-alive probes are only sent while there is no unacknowledged data, so
a connection that died with data in flight is only reported as failed when the
OS gives up retransmitting - ~15 minutes on Linux and Android. When no network
change is reported to the agent (a silent route change, a client without a
network observer) that is how long delivery keeps failing on the dead client.

KeepAliveOpts gets unackedDataTimeout: when it is set, the sockets that set TCP
keep-alive also set TCP_USER_TIMEOUT (TCP_RXT_CONNDROPTIME on Darwin, not set
on Windows) to the keep-alive detection time, so that such a connection fails
after the same time as an idle one. On Linux this timeout also bounds the
zero-window persist timer, so a server that stops reading for that long then
fails the connection.

The option is off unless the client sets it, so that it can be enabled by the
users who test it before it becomes the default. It is a Maybe to keep the
clients that do not send it working. As it is derived from the keep-alive
options where they are set, the two detection times cannot diverge and lowering
the keep-alive settings lowers both.

It is removed for the connections via SOCKS proxy - the socket is then
connected to the proxy, its acknowledgements only mean that the proxy buffered
the data, and a slow circuit should not fail the connection.
After the network changes (e.g. the IP address changed after internet loss) the
connections to the servers are dead, but the OS reports it only after TCP
retransmissions time out - ~15 minutes on Linux/Android while the agent keeps
writing to the dead socket. Until then message delivery, subscriptions and file
transfers keep failing on the same client.

setUserNetworkInfo on every online network event (the clients report one when
the network type changes and when it goes offline to online):

- closes the connected SMP and NTF clients and fails their pending requests, so
  that the workers that resume do not wait for the responses that will not
  arrive - the requests are failed only once the client's threads terminated,
  which the client now records, as its session var is also removed when a
  resubscription fails (each client type bounded by 1 second; the requests of a
  client that did not terminate in this time are left to time out as before);
- discards the errors of the clients that failed to connect, cached for
  persistErrorInterval, and removes a client that is still connecting, in the
  transaction that reads it, so that the resumed workers create a new client
  instead of waiting for a connection made via the previous network - that
  client is closed by newProtocolClient when it finds it is no longer in the map;
- closes the XFTP clients asynchronously, removing them from the map.

A client that is no longer the one in the map is rejected in the same
transaction that would publish it, so that the requests made on it do not wait
for the timeout, and it is closed in a forked thread, as closing an XFTP client
waits for its thread to terminate; it sets its session id while its var is the
one in the map and updates its service only if it was.

The clients are closed in a forked thread, so that the API does not block, and
the closes of an event that a newer one superseded are skipped. When the agent
was offline the reported network info is applied when the clients are closed,
so that the workers waiting for the network do not resume before then; when it
was online it is applied immediately.

As the clients are closed, the network change now results in DISCONNECT and
DOWN events followed by CONNECT and UP, as any other reconnection.

The retry loops are not restarted, so a worker already waiting out its retry
interval resumes only when it expires.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant