Skip to content

Commit 451a6a7

Browse files
committed
Simplification+name adjustments
1 parent f9def19 commit 451a6a7

File tree

2 files changed

+81
-102
lines changed

2 files changed

+81
-102
lines changed

lib/src/socket_connection.dart

+77-98
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,10 @@ class SocketConnectionManager {
4242
int _connectionAttempts = 0;
4343

4444
SocketConnectionAttempt _currentAttempt = SocketConnectionAttempt.aborted();
45-
SocketConnectionAttempt _setNewAttempt(Duration delay) {
46-
return _currentAttempt = SocketConnectionAttempt(delay: delay);
47-
}
4845

4946
bool _disposed = false;
47+
bool get _shouldAttemptReconnection =>
48+
!_disposed && _pendingConnection != null;
5049

5150
/// Requests to start connecting to the socket.
5251
///
@@ -61,15 +60,15 @@ class SocketConnectionManager {
6160
}
6261

6362
if (immediately) {
64-
if (_pendingConnection != null && !_currentAttempt.done) {
65-
_currentAttempt.completeNow();
63+
if (_pendingConnection != null && !_currentAttempt.delayDone) {
64+
_currentAttempt.skipDelay();
6665
return;
6766
}
6867

6968
_stopConnecting(4002, 'Immediate connection requested');
7069
_connectionAttempts = 0;
7170
}
72-
_connect();
71+
_maybeConnect();
7372
}
7473

7574
/// Sends a message to the socket. Will start connecting to the socket if
@@ -81,7 +80,7 @@ class SocketConnectionManager {
8180
/// If after call to [addMessage] a call to [dispose] or [stop] is made, then
8281
/// this future will complete with an error instead.
8382
Future<void> addMessage(String message) {
84-
return _connect().then((connection) => connection.send(message));
83+
return _maybeConnect().then((connection) => connection.send(message));
8584
}
8685

8786
/// Stops the attempts to connect, and closes the current connection if one is
@@ -113,7 +112,7 @@ class SocketConnectionManager {
113112
_pendingConnection = null;
114113

115114
// Make sure that no old attempt will begin emitting messages.
116-
if (!_currentAttempt.done) {
115+
if (!_currentAttempt.delayDone) {
117116
_currentAttempt.abort();
118117
}
119118
_currentAttempt = SocketConnectionAttempt.aborted();
@@ -123,33 +122,19 @@ class SocketConnectionManager {
123122
}).ignore();
124123
}
125124

126-
Future<_WebSocketConnection> _connect() async {
125+
/// Establishes a new connection unless one is already available/in progress.
126+
Future<_WebSocketConnection> _maybeConnect() {
127127
if (_disposed) {
128128
throw StateError('Cannot connect: WebSocket connection manager disposed');
129129
}
130130

131-
final pendingConnection =
132-
_pendingConnection ??= _attemptConnection(_reconnectDelay());
133-
final connection = await pendingConnection;
134-
if (_disposed) {
135-
throw StateError('Cannot connect: WebSocket connection manager disposed');
136-
} else if (_pendingConnection != pendingConnection) {
137-
// Something changed while waiting, try again from the start.
138-
_logger.warning('Connection changed while connecting');
139-
connection.close(normalClosure, 'New connection is being established');
140-
return _connect();
141-
} else {
142-
return connection;
143-
}
131+
return _pendingConnection ?? _connect();
144132
}
145133

146-
/// Returns a future that completes with a _WsConnection.
147-
///
148-
/// If [completer] is null, then a new connection attempt will replace
149-
/// _pendingConnection.
134+
/// Starts connection attempts.
150135
///
151-
/// If [completer] is not null, then the returned future will be that
152-
/// completer's future.
136+
/// Upon completiong, the [_pendingConnection] field will be set to the newly
137+
/// established connection Future, and the same Future will be returned.
153138
///
154139
/// Can throw/complete with an exception if:
155140
/// - during any asynchronous operation, this [SocketConnectionManager] is
@@ -161,66 +146,68 @@ class SocketConnectionManager {
161146
/// [ConnectionInitializationException] in case the initialization of
162147
/// connection fails. However, the reconnection will be triggered until it is
163148
/// established, or interrupted by call to [stop] or [dispose].
164-
Future<_WebSocketConnection> _attemptConnection(Duration delayDuration,
165-
[Completer<_WebSocketConnection>? completer]) async {
166-
_connectionAttempts++;
167-
168-
final Completer<_WebSocketConnection> connectionCompleter;
169-
if (completer == null) {
170-
connectionCompleter = Completer<_WebSocketConnection>();
171-
_pendingConnection = connectionCompleter.future;
172-
} else {
173-
connectionCompleter = completer;
149+
Future<_WebSocketConnection> _connect() async {
150+
if (_disposed) {
151+
throw StateError('Cannot connect: WebSocket connection manager disposed');
174152
}
175153

176-
final attempt = _setNewAttempt(delayDuration);
177-
if (_logger.isLoggable(Level.FINE)) {
178-
_logger.fine(() {
179-
final durationString = delayDuration == Duration.zero
180-
? 'now'
181-
: 'in ${delayDuration.inMilliseconds} milliseconds';
182-
return 'Triggering attempt #$_connectionAttempts (id: ${attempt.idAsString}) to connect $durationString';
183-
});
154+
final connectionCompleter = Completer<_WebSocketConnection>();
155+
final connectionFuture = _pendingConnection = connectionCompleter.future;
156+
157+
while (!connectionCompleter.isCompleted) {
158+
final delay = _reconnectDelay();
159+
_connectionAttempts++;
160+
161+
_WebSocketConnection? connection;
162+
try {
163+
connection = await _runConnectionAttempt(delay);
164+
} catch (error, stackTrace) {
165+
_logger.warning('Failed to initialize connection', error, stackTrace);
166+
} finally {
167+
if (_disposed) {
168+
// Manager was disposed while running connection attempt.
169+
connection?.close(goingAway, 'Client disposed');
170+
connectionCompleter.completeError(StateError('Client disposed'));
171+
} else if (connectionFuture != _pendingConnection) {
172+
connection?.close(normalClosure, 'Closing obsolete connection');
173+
if (_pendingConnection == null) {
174+
// stop() was called during connection attempt.
175+
connectionCompleter
176+
.completeError(StateError('Connection attempt aborted'));
177+
} else {
178+
// _startConnecting() was called during connection attempt, return the
179+
// new Future instead.
180+
connectionCompleter.complete(_pendingConnection);
181+
}
182+
} else if (connection != null) {
183+
// Correctly established connection.
184+
_logger.fine('Established WebSocket connection');
185+
_connectionAttempts = 0;
186+
connectionCompleter.complete(connection);
187+
}
188+
}
184189
}
185190

186-
attempt.completionFuture
187-
.then(
188-
(_) => _attemptConnectionWithCompleter(connectionCompleter, attempt),
189-
)
190-
.catchError(
191-
(error) => _logger.info(
192-
'Pending connection attempt ${attempt.idAsString} aborted',
193-
),
194-
);
195-
196191
return connectionCompleter.future;
197192
}
198193

199-
void _attemptConnectionWithCompleter(
200-
Completer<_WebSocketConnection> connectionCompleter,
201-
SocketConnectionAttempt attempt,
194+
Future<_WebSocketConnection> _runConnectionAttempt(
195+
Duration delay,
202196
) async {
203-
if (_disposed) {
204-
connectionCompleter.completeError(StateError(
205-
'WebSocket connection manager disposed during connection delay',
206-
));
207-
return;
197+
final attempt = _currentAttempt = SocketConnectionAttempt(delay: delay);
198+
if (_logger.isLoggable(Level.FINE)) {
199+
_logger.fine(() {
200+
final durationString = delay == Duration.zero
201+
? 'now'
202+
: 'in ${delay.inMilliseconds} milliseconds';
203+
return 'Triggering attempt #$_connectionAttempts (id: ${attempt.idAsString}) to connect $durationString';
204+
});
208205
}
209206

207+
await attempt.delayFuture;
208+
210209
if (attempt != _currentAttempt) {
211-
// This would be odd, but I've seen enough.
212-
if (_pendingConnection != null) {
213-
_logger.warning('Pending connection updated while delaying');
214-
connectionCompleter.complete(_pendingConnection!);
215-
return;
216-
} else {
217-
// We probably don't want to connect anymore.
218-
connectionCompleter.completeError(
219-
StateError('Connection aborted'),
220-
StackTrace.current,
221-
);
222-
return;
223-
}
210+
throw StateError('Current attempt obsoleted while delaying');
224211
}
225212

226213
try {
@@ -229,29 +216,20 @@ class SocketConnectionManager {
229216
callbacks: _ConnectionCallbacks(attempt: attempt, manager: this),
230217
);
231218
if (attempt == _currentAttempt) {
232-
connectionCompleter.complete(connection);
219+
return connection;
233220
} else {
234221
connection.close(normalClosure, 'Closing unnecessary connection');
222+
throw ConnectionInitializationException(
223+
'Current attempt obsoleted while delaying',
224+
StackTrace.current,
225+
);
235226
}
236227
} catch (error, stackTrace) {
237-
_logger.warning('Failed to initialize connection', error, stackTrace);
238-
239-
_onError(error, stackTrace);
240-
241-
// Checks just as above.
242228
if (attempt == _currentAttempt) {
243-
_attemptConnection(_reconnectDelay(), connectionCompleter);
244-
} else if (_pendingConnection != null) {
245-
_logger.warning(
246-
'Pending connection updated while waiting for initialization',
247-
);
248-
connectionCompleter.complete(_pendingConnection);
249-
} else {
250-
connectionCompleter.completeError(
251-
StateError('Connection aborted'),
252-
StackTrace.current,
253-
);
229+
_onError(error, stackTrace);
254230
}
231+
232+
rethrow;
255233
}
256234
}
257235

@@ -262,7 +240,8 @@ class SocketConnectionManager {
262240
}
263241
}
264242

265-
/// Wraps upstream callbacks to filter out obsolete or invalid callbacks from _WsConnection
243+
/// Wraps upstream callbacks to filter out obsolete or invalid callbacks from
244+
/// _WebSocketConnection.
266245
final class _ConnectionCallbacks {
267246
_ConnectionCallbacks({
268247
required this.attempt,
@@ -331,11 +310,11 @@ final class _ConnectionCallbacks {
331310
case WebSocketClosed():
332311
if (_logger.isLoggable(Level.FINE)) {
333312
_logger.fine(
334-
'Socket closed, ${manager._disposed ? ' not ' : ''}attempting to reconnect',
313+
'Socket closed, ${manager._shouldAttemptReconnection ? ' not ' : ''}attempting to reconnect',
335314
);
336315
}
337-
if (!manager._disposed) {
338-
manager._attemptConnection(manager._reconnectDelay());
316+
if (!manager._shouldAttemptReconnection) {
317+
manager._connect();
339318
}
340319
case WebSocketReady():
341320
manager._connectionAttempts = 0;

lib/src/socket_connection_attempt.dart

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,20 @@ final class SocketConnectionAttempt {
1414

1515
SocketConnectionAttempt.aborted() {
1616
_delayTimer = Timer(Duration.zero, () {});
17-
completionFuture.ignore();
17+
delayFuture.ignore();
1818
abort();
1919
}
2020

2121
final int _id = _random.nextInt(1 << 32);
2222
late final idAsString = _id.toRadixString(16).padLeft(8, '0');
2323

2424
final Completer<void> _delayCompleter = Completer();
25-
Future<void> get completionFuture => _delayCompleter.future;
26-
bool get done => _delayCompleter.isCompleted;
25+
bool get delayDone => _delayCompleter.isCompleted;
26+
late final Future<void> delayFuture = _delayCompleter.future;
2727

2828
late Timer _delayTimer;
2929

30-
void completeNow() {
30+
void skipDelay() {
3131
if (_delayTimer.isActive) {
3232
_delayTimer.cancel();
3333
}

0 commit comments

Comments
 (0)