Closed akashvibhute closed 4 months ago
Also the phoenix server is behind nginx reverse proxy if that helps, so nginx returns 502 response if phoenix is down,
I experienced the same problem 2 days ago. Not sure if something changed or just coincidence as I never worked on reconnection before then...
The root cause is here in socket.dart:
When the websocket is closed either by the server going offline or no internet connection (airplane mode will trigger it easily), _onSocketClosed()
is called from the _onSocketError
callback.
void _onSocketError(dynamic error, dynamic stacktrace) {
...
_onSocketClosed();
}
Currently, _onSocketClosed does not specify any reconnection delay:
void _onSocketClosed() {
if (_shouldReconnect) {
_delayedReconnect();
}
...
}
And _delayedReconnect
only takes the _options.reconnectDelays.first
when a delay
is not explicitly specified. The default value for _options.reconnectDelays.first
is 0 so with default options, we have an infinite loop, when a reconnectDelays
list is specified in PhoenixSocketOptions
, only the first value is taken into account.
Future<PhoenixSocket?> _delayedReconnect([Duration? delay]) async {
if (_reconnecting) return null;
_reconnecting = true;
await Future.delayed(delay ?? _options.reconnectDelays.first);
if (!_disposed) {
_reconnecting = false;
return connect();
}
return null;
}
I have found a temporary work around which is to listen to closeStream events on the socket, in my case, I just close the socket and start a reconnection logic somewhere else in my app which will create and connect with a brand new socket.
_socket = PhoenixSocket(_websocketURL, socketOptions: _socketOptions);
_socket!.closeStream.listen((event) {
// Catches all events, probably not ideal.
_customReconnectionLogicHere();
// Or can be more specific for the various code which may be received.
if (event.code == 1002) {
_customReconnectionLogicHere();
}
});
Digging a bit further but I'm not familiar enough with the code to understand the intent, there may be a asynchronous error somewhere in the reconnection logic.
As we see above, a socket error triggers a chain of events leading ultimately to a call to connect()
in _delayedReconnect()
.
connect()
does implement its own correct delayed reconnection logic IF no error was caught during the websocket creation and connection.
When no internet is available, onError
callback is triggered which calls _onSocketError
in the catch clause and restarts the loop by calling _onSocketClosed() which has no reconnection delay.
Future<PhoenixSocket?> connect() async {
...
try {
_ws = WebSocketChannel.connect(_mountPoint);
_ws!.stream
.where(_shouldPipeMessage)
.listen(_onSocketData, cancelOnError: true)
..onError(_onSocketError)
..onDone(_onSocketClosed);
} catch (error, stacktrace) {
_onSocketError(error, stacktrace);
}
Since _delayedReconnect()
(a future) is not awaited by _onSocketClosed()
, _onSocketClosed()
and _onSocketClosed()
do complete and some of the below code is executed (as seen in the logs) and quickly fails since the websocket _ws
is already set back to null in _onSocketClosed()
.
_socketState = SocketState.connecting;
try {
_socketState = SocketState.connected;
_logger.finest('Waiting for initial heartbeat roundtrip');
if (await _sendHeartbeat(ignorePreviousHeartbeat: true)) {
_stateStreamController.add(PhoenixSocketOpenEvent());
_logger.info('Socket open');
completer.complete(this);
} else {
throw PhoenixException();
}
The below reconnection logic is never reached
} on PhoenixException catch (err) {
print(err);
final durationIdx = _reconnectAttempts++;
_ws = null;
_socketState = SocketState.closed;
Duration duration;
if (durationIdx >= _reconnects.length) {
duration = _reconnects.last;
} else {
duration = _reconnects[durationIdx];
}
// Some random number to prevent many clients from retrying to
// connect at exactly the same time.
duration += Duration(milliseconds: _random.nextInt(1000));
completer.complete(_delayedReconnect(duration));
}
return completer.future;
}
I'm not sure if the intent was for the reconnection logic inside connect()
to handle all cases but the call to _onSocketError()
short-circuits that intent or if the delayed reconnection logic should simply be duplicated somewhere inside _onSocketClosed()
. I guess the latter I think @matehat will know better.
hello @matehat, i don't know if it's related to this open issue, but i can't leave the connection
Fixed by #50
Released in 0.6.1
hello @matehat, i don't know if it's related to this open issue, but i can't leave the connection
@redDwarf03 I don't think it is. Can you retry with the latest version? It also includes a fix for the heartbeat.
hello @matehat, i don't know if it's related to this open issue, but i can't leave the connection
@redDwarf03 I don't think it is. Can you retry with the latest version? It also includes a fix for the heartbeat.
Hello. Cool, i'm happy you're working on your lib !! Sadly, i just test with then new version and i have always the heartbeat
There was a recent patch in 1.7.0-dev that fixed heartbeat being sent after disconnect and causing abnormal disconnects
Thank you for the information. Any impacts for the dart lib?
@redDwarf03 can you retry with the latest release?
And if it's still the case, can you reopen a new ticket, as the original issue was fixed.
@matehat I still get this issue. If my phoenix server is stopped, the client keeps on reconnecting every millisecond if I don't pass any value to reconnectDelays. If I pass a list of Durations, only the first duration is taken and it keeps on reconnecting with the same interval instead of increasing as per the list. On flutter 2.2 and latest version of the package. I thought this was an issue with riverpod providers, but I can reproduce it with initializing the socket connection directly in the main method itself or as a global variable imported to join a channel.
Originally posted by @akashvibhute in https://github.com/braverhealth/phoenix-socket-dart/issues/6#issuecomment-845200357
Configuration -
I'm just calling useProvider(socketProvider); in the main method. Same issue without riverpod as well.
I set the minium retry time to 1 sec, as by default it was attempting every millisecond and producing too many logs.
Reproduction logs
Initially phoenix server was running and connection was fine on app startup. Observed issue when server was stopped and reconnect attempts did not respect the specified durations. Connection established at the end again when phoenix server was restarted.
Please let me know if you need anything else.