Skip to content

Commit 25fa1da

Browse files
authored
add timeout to websocket connection and socket heartbeat (braverhealth#86)
* add timeout to websocket connection and socket heartbeat * added timeout to WebSocket ready future * closing sink before assigning null to ws * cancel heartbeat when socket is disposed
1 parent 6fcde10 commit 25fa1da

File tree

2 files changed

+38
-4
lines changed

2 files changed

+38
-4
lines changed

lib/src/socket.dart

+27-4
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class PhoenixSocket {
164164
String get endpoint => _endpoint;
165165

166166
/// The [Uri] containing all the parameters and options for the
167-
/// remote connection to occue.
167+
/// remote connection to occur.
168168
Uri get mountPoint => _mountPoint;
169169

170170
/// Whether the underlying socket is connected of not.
@@ -210,7 +210,7 @@ class PhoenixSocket {
210210
// Wait for the WebSocket to be ready before continuing. In case of a
211211
// failure to connect, the future will complete with an error and will be
212212
// caught.
213-
await _ws!.ready;
213+
await _ws!.ready.timeout(_options.timeout);
214214

215215
_socketState = SocketState.connected;
216216

@@ -222,9 +222,20 @@ class PhoenixSocket {
222222
} else {
223223
throw PhoenixException();
224224
}
225+
} on TimeoutException catch (err, stackTrace) {
226+
_logger.severe(
227+
'Timed out waiting for WebSocket to be ready', err, stackTrace);
228+
_closeSink();
229+
_ws = null;
230+
_socketState = SocketState.closed;
231+
// Calling this method will trigger a reconnect
232+
// making _shouldReconnect = false as we are manually calling _delayedReconnect()
233+
_shouldReconnect = false;
234+
_onSocketError(err, stackTrace);
235+
completer.complete(_delayedReconnect());
225236
} catch (err, stackTrace) {
226237
_logger.severe('Raised Exception', err, stackTrace);
227-
238+
_closeSink();
228239
_ws = null;
229240
_socketState = SocketState.closed;
230241

@@ -271,6 +282,7 @@ class PhoenixSocket {
271282

272283
_disposed = true;
273284
_ws?.sink.close();
285+
_cancelHeartbeat();
274286

275287
for (final sub in _subscriptions) {
276288
sub.cancel();
@@ -455,9 +467,19 @@ class PhoenixSocket {
455467
}
456468

457469
try {
458-
await sendMessage(_heartbeatMessage());
470+
await sendMessage(_heartbeatMessage()).timeout(_options.heartbeatTimeout);
459471
_logger.fine('[phoenix_socket] Heartbeat completed');
460472
return true;
473+
} on TimeoutException catch (err, stacktrace) {
474+
_logger.severe(
475+
'[phoenix_socket] Heartbeat message timed out',
476+
err,
477+
stacktrace,
478+
);
479+
if (_ws != null) {
480+
_closeSink(normalClosure, 'heartbeat timeout');
481+
}
482+
return false;
461483
} on WebSocketChannelException catch (err, stacktrace) {
462484
_logger.severe(
463485
'[phoenix_socket] Heartbeat message failed: WebSocketChannelException',
@@ -552,6 +574,7 @@ class PhoenixSocket {
552574
code: _ws?.closeCode,
553575
);
554576
final exc = PhoenixException(socketClosed: ev);
577+
_closeSink();
555578
_ws = null;
556579

557580
if (!_stateStreamController.isClosed) {

lib/src/socket_options.dart

+11
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ class PhoenixSocketOptions {
1313
/// The interval between heartbeat roundtrips
1414
Duration? heartbeat,
1515

16+
/// The duration after which a heartbeat request
17+
/// is considered timed out
18+
Duration? heartbeatTimeout,
19+
1620
/// The list of delays between reconnection attempts.
1721
///
1822
/// The last duration will be repeated until it works.
@@ -40,6 +44,7 @@ class PhoenixSocketOptions {
4044
}) : _timeout = timeout ?? const Duration(seconds: 10),
4145
serializer = serializer ?? const MessageSerializer(),
4246
_heartbeat = heartbeat ?? const Duration(seconds: 30),
47+
_heartbeatTimeout = heartbeatTimeout ?? const Duration(seconds: 10),
4348
assert(!(params != null && dynamicParams != null),
4449
"Can't set both params and dynamicParams");
4550

@@ -49,13 +54,19 @@ class PhoenixSocketOptions {
4954

5055
final Duration _timeout;
5156
final Duration _heartbeat;
57+
final Duration _heartbeatTimeout;
5258

5359
/// Duration after which a request is assumed to have timed out.
5460
Duration get timeout => _timeout;
5561

5662
/// Duration between heartbeats
5763
Duration get heartbeat => _heartbeat;
5864

65+
/// Duration after which a heartbeat request is considered timed out.
66+
/// If the server does not respond to a heartbeat request within this
67+
/// duration, the connection is considered lost.
68+
Duration get heartbeatTimeout => _heartbeatTimeout;
69+
5970
/// Optional list of Duration between reconnect attempts
6071
final List<Duration> reconnectDelays;
6172

0 commit comments

Comments
 (0)