xmpp: Add support for setting reconnection policies
This commit is contained in:
		
							parent
							
								
									dfacbca446
								
							
						
					
					
						commit
						aae126a3de
					
				@ -9,6 +9,7 @@ import "package:moxxyv2/xmpp/connection.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/presence.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/message.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/ping.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/reconnect.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/xeps/xep_0054.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/xeps/xep_0060.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/xeps/xep_0066.dart";
 | 
			
		||||
@ -166,7 +167,7 @@ Future<void> entrypoint() async {
 | 
			
		||||
  // Init the UDPLogger
 | 
			
		||||
  await initUDPLogger();
 | 
			
		||||
 | 
			
		||||
  final connection = XmppConnection();
 | 
			
		||||
  final connection = XmppConnection(ExponentialBackoffReconnectionPolicy());
 | 
			
		||||
  connection.registerManagers([
 | 
			
		||||
      MoxxyStreamManagementManager(),
 | 
			
		||||
      MoxxyDiscoManager(),
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
import "dart:async";
 | 
			
		||||
import "dart:math";
 | 
			
		||||
 | 
			
		||||
import "package:moxxyv2/xmpp/socket.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/buffer.dart";
 | 
			
		||||
@ -12,6 +11,7 @@ import "package:moxxyv2/xmpp/events.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/iq.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/presence.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/roster.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/reconnect.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/sasl/authenticator.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/sasl/authenticators.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/managers/base.dart";
 | 
			
		||||
@ -30,6 +30,7 @@ import "package:uuid/uuid.dart";
 | 
			
		||||
import "package:synchronized/synchronized.dart";
 | 
			
		||||
import "package:logging/logging.dart";
 | 
			
		||||
import "package:meta/meta.dart";
 | 
			
		||||
import "package:moxlib/moxlib.dart";
 | 
			
		||||
 | 
			
		||||
enum XmppConnectionState {
 | 
			
		||||
  notConnected,
 | 
			
		||||
@ -94,6 +95,7 @@ class XmppConnection {
 | 
			
		||||
  XmppConnectionState _connectionState;
 | 
			
		||||
  late final Stream<String> _socketStream;
 | 
			
		||||
  late ConnectionSettings _connectionSettings;
 | 
			
		||||
  final ReconnectionPolicy _reconnectionPolicy;
 | 
			
		||||
  
 | 
			
		||||
  /// Stream properties
 | 
			
		||||
  ///
 | 
			
		||||
@ -112,8 +114,6 @@ class XmppConnection {
 | 
			
		||||
  RoutingState _routingState;
 | 
			
		||||
  /// The currently bound resource or "" if none has been bound yet.
 | 
			
		||||
  String _resource;
 | 
			
		||||
  /// Counter for how manyy we have tried to reconnect.
 | 
			
		||||
  int _currentBackoffAttempt;
 | 
			
		||||
  /// For indicating in a [ConnectionStateChangedEvent] that the event occured because we
 | 
			
		||||
  /// did a reconnection.
 | 
			
		||||
  bool _resuming;
 | 
			
		||||
@ -122,9 +122,8 @@ class XmppConnection {
 | 
			
		||||
  bool _disconnecting;
 | 
			
		||||
  /// For indicating whether we expect a socket closure due to StartTLS.
 | 
			
		||||
  bool _performingStartTLS;
 | 
			
		||||
  /// Timers for the keep-alive ping and the backoff connection process.
 | 
			
		||||
  /// Timers for the keep-alive ping.
 | 
			
		||||
  Timer? _connectionPingTimer;
 | 
			
		||||
  Timer? _backoffTimer;
 | 
			
		||||
  /// Completers for certain actions
 | 
			
		||||
  Completer<XmppConnectionResult>? _connectionCompleter;
 | 
			
		||||
 | 
			
		||||
@ -137,16 +136,18 @@ class XmppConnection {
 | 
			
		||||
  /// [socket] is for debugging purposes.
 | 
			
		||||
  /// [connectionPingDuration] is the duration after which a ping will be sent to keep
 | 
			
		||||
  /// the connection open. Defaults to 15 minutes.
 | 
			
		||||
  XmppConnection({
 | 
			
		||||
  XmppConnection(
 | 
			
		||||
    ReconnectionPolicy reconnectionPolicy,
 | 
			
		||||
    {
 | 
			
		||||
      BaseSocketWrapper? socket,
 | 
			
		||||
      this.connectionPingDuration = const Duration(minutes: 5)
 | 
			
		||||
  }) :
 | 
			
		||||
    }
 | 
			
		||||
  ) :
 | 
			
		||||
    _connectionState = XmppConnectionState.notConnected,
 | 
			
		||||
    _routingState = RoutingState.unauthenticated,
 | 
			
		||||
    _eventStreamController = StreamController.broadcast(),
 | 
			
		||||
    _resource = "",
 | 
			
		||||
    _streamBuffer = XmlStreamBuffer(),
 | 
			
		||||
    _currentBackoffAttempt = 0,
 | 
			
		||||
    _resuming = true,
 | 
			
		||||
    _performingStartTLS = false,
 | 
			
		||||
    _disconnecting = false,
 | 
			
		||||
@ -159,11 +160,15 @@ class XmppConnection {
 | 
			
		||||
    _incomingStanzaHandlers = List.empty(growable: true),
 | 
			
		||||
    _outgoingPreStanzaHandlers = List.empty(growable: true),
 | 
			
		||||
    _outgoingPostStanzaHandlers = List.empty(growable: true),
 | 
			
		||||
    _reconnectionPolicy = reconnectionPolicy,
 | 
			
		||||
    _log = Logger("XmppConnection") {
 | 
			
		||||
    _socketStream = _socket.getDataStream();
 | 
			
		||||
    // TODO: Handle on done
 | 
			
		||||
    _socketStream.transform(_streamBuffer).forEach(handleXmlStream);
 | 
			
		||||
    _socket.getEventStream().listen(_handleSocketEvent);
 | 
			
		||||
      // Allow the reconnection policy to perform reconnections by itself
 | 
			
		||||
      _reconnectionPolicy.register(_attemptReconnection);
 | 
			
		||||
 | 
			
		||||
      _socketStream = _socket.getDataStream();
 | 
			
		||||
      // TODO: Handle on done
 | 
			
		||||
      _socketStream.transform(_streamBuffer).forEach(handleXmlStream);
 | 
			
		||||
      _socket.getEventStream().listen(_handleSocketEvent);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  List<String> get streamFeatures => _streamFeatures;
 | 
			
		||||
@ -295,15 +300,7 @@ class XmppConnection {
 | 
			
		||||
  void _attemptReconnection() {
 | 
			
		||||
    _setConnectionState(XmppConnectionState.notConnected);
 | 
			
		||||
    _socket.close();
 | 
			
		||||
 | 
			
		||||
    if (_currentBackoffAttempt == 0) {
 | 
			
		||||
      // TODO: This may to too long
 | 
			
		||||
      final minutes = pow(2, _currentBackoffAttempt).toInt();
 | 
			
		||||
      _currentBackoffAttempt++;
 | 
			
		||||
      _backoffTimer = Timer(Duration(minutes: minutes), () {
 | 
			
		||||
          connect();
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    connect();
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /// Called when a stream ending error has occurred
 | 
			
		||||
@ -316,7 +313,7 @@ class XmppConnection {
 | 
			
		||||
    } 
 | 
			
		||||
 | 
			
		||||
    // TODO: This may be too harsh for every error
 | 
			
		||||
    _attemptReconnection();
 | 
			
		||||
    _reconnectionPolicy.onFailure();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Called whenever the socket creates an event
 | 
			
		||||
@ -892,6 +889,11 @@ class XmppConnection {
 | 
			
		||||
  Future<void> _sendEvent(XmppEvent event) async {
 | 
			
		||||
    _log.finest("Event: ${event.toString()}");
 | 
			
		||||
 | 
			
		||||
    // Specific event handling
 | 
			
		||||
    if (event is AckRequestResponseTimeoutEvent) {
 | 
			
		||||
      _reconnectionPolicy.onFailure();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    for (var manager in _xmppManagers.values) {
 | 
			
		||||
      await manager.onXmppEvent(event);
 | 
			
		||||
    }
 | 
			
		||||
@ -967,10 +969,7 @@ class XmppConnection {
 | 
			
		||||
      _resource = lastResource;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (_backoffTimer != null) {
 | 
			
		||||
      _backoffTimer!.cancel();
 | 
			
		||||
      _backoffTimer = null;
 | 
			
		||||
    }
 | 
			
		||||
    _reconnectionPolicy.reset();
 | 
			
		||||
 | 
			
		||||
    _resuming = true;
 | 
			
		||||
    await _sendEvent(ConnectingEvent());
 | 
			
		||||
@ -993,7 +992,7 @@ class XmppConnection {
 | 
			
		||||
    if (!result) {
 | 
			
		||||
      handleError(null);
 | 
			
		||||
    } else {
 | 
			
		||||
      _currentBackoffAttempt = 0;
 | 
			
		||||
      _reconnectionPolicy.onSuccess();
 | 
			
		||||
      _log.fine("Preparing the internal state for a connection attempt");
 | 
			
		||||
      _performingStartTLS = false;
 | 
			
		||||
      _setConnectionState(XmppConnectionState.connecting);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										87
									
								
								lib/xmpp/reconnect.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								lib/xmpp/reconnect.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,87 @@
 | 
			
		||||
import "dart:async";
 | 
			
		||||
import "dart:math";
 | 
			
		||||
 | 
			
		||||
import "package:logging/logging.dart";
 | 
			
		||||
import "package:meta/meta.dart";
 | 
			
		||||
 | 
			
		||||
abstract class ReconnectionPolicy {
 | 
			
		||||
  /// Function provided by [XmppConnection] that allows the policy
 | 
			
		||||
  /// to perform a reconnection.
 | 
			
		||||
  void Function()? performReconnect;
 | 
			
		||||
 | 
			
		||||
  /// Called by [XmppConnection] to register the policy.
 | 
			
		||||
  void register(void Function() performReconnect) {
 | 
			
		||||
    this.performReconnect = performReconnect;
 | 
			
		||||
 | 
			
		||||
    reset();
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /// In case the policy depends on some internal state, this state must be reset
 | 
			
		||||
  /// to an initial state when [reset] is called. In case timers run, they must be
 | 
			
		||||
  /// terminated.
 | 
			
		||||
  void reset();
 | 
			
		||||
 | 
			
		||||
  /// Called by the [XmppConnection] when the reconnection failed.
 | 
			
		||||
  void onFailure();
 | 
			
		||||
 | 
			
		||||
  /// Caled by the [XmppConnection] when the reconnection was successful.
 | 
			
		||||
  void onSuccess();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A simple reconnection strategy: Make the reconnection delays exponentially longer
 | 
			
		||||
/// for every failed attempt.
 | 
			
		||||
class ExponentialBackoffReconnectionPolicy extends ReconnectionPolicy {
 | 
			
		||||
  int _counter;
 | 
			
		||||
  Timer? _timer;
 | 
			
		||||
  Logger _log;
 | 
			
		||||
 | 
			
		||||
  ExponentialBackoffReconnectionPolicy()
 | 
			
		||||
  : _counter = 0,
 | 
			
		||||
    _log = Logger("ExponentialBackoffReconnectionPolicy");
 | 
			
		||||
 | 
			
		||||
  /// Called when the backoff expired
 | 
			
		||||
  void _onTimerElapsed() {
 | 
			
		||||
    performReconnect!();
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  @override
 | 
			
		||||
  void reset() {
 | 
			
		||||
    _log.finest("Resetting internal state");
 | 
			
		||||
    _counter = 0;
 | 
			
		||||
 | 
			
		||||
    if (_timer != null) {
 | 
			
		||||
      _timer!.cancel();
 | 
			
		||||
      _timer = null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void onFailure() {
 | 
			
		||||
    _log.finest("Failure occured. Starting exponential backoff");
 | 
			
		||||
    _counter++;
 | 
			
		||||
 | 
			
		||||
    if (_timer != null) {
 | 
			
		||||
      _timer!.cancel();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _timer = Timer(Duration(seconds: pow(2, _counter).toInt()), _onTimerElapsed);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void onSuccess() {
 | 
			
		||||
    reset();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A stub reconnection policy for tests
 | 
			
		||||
@visibleForTesting
 | 
			
		||||
class TestingReconnectionPolicy extends ReconnectionPolicy {
 | 
			
		||||
  @override
 | 
			
		||||
  void onSuccess() {}
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void onFailure() {}
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void reset() {}
 | 
			
		||||
}
 | 
			
		||||
@ -4,6 +4,7 @@ import "package:moxxyv2/xmpp/stanza.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/settings.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/jid.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/connection.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/reconnect.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/managers/attributes.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/managers/data.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/xeps/xep_0198/xep_0198.dart";
 | 
			
		||||
@ -45,7 +46,7 @@ XmppManagerAttributes mkAttributes(void Function(Stanza) callback) {
 | 
			
		||||
    isFeatureSupported: (_) => false,
 | 
			
		||||
    getFullJID: () => JID.fromString("hallo@example.server/uwu"),
 | 
			
		||||
    getSocket: () => StubTCPSocket(play: []),
 | 
			
		||||
    getConnection: () => XmppConnection()
 | 
			
		||||
    getConnection: () => XmppConnection(TestingReconnectionPolicy())
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import "package:moxxyv2/xmpp/connection.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/jid.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/settings.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/stringxml.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/reconnect.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/managers/attributes.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/xeps/xep_0280.dart";
 | 
			
		||||
 | 
			
		||||
@ -31,7 +32,7 @@ void main() {
 | 
			
		||||
        isFeatureSupported: (_) => false,
 | 
			
		||||
        getFullJID: () => JID.fromString("bob@xmpp.example/uwu"),
 | 
			
		||||
        getSocket: () => StubTCPSocket(play: []),
 | 
			
		||||
        getConnection: () => XmppConnection()
 | 
			
		||||
        getConnection: () => XmppConnection(TestingReconnectionPolicy())
 | 
			
		||||
      );
 | 
			
		||||
      final manager = CarbonsManager();
 | 
			
		||||
      manager.register(attributes);
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ import "package:moxxyv2/xmpp/settings.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/namespaces.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/stringxml.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/jid.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/reconnect.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/managers/attributes.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/xeps/xep_0352.dart";
 | 
			
		||||
 | 
			
		||||
@ -33,7 +34,7 @@ void main() {
 | 
			
		||||
              isFeatureSupported: (_) => false,
 | 
			
		||||
              getFullJID: () => JID.fromString("some.user@example.server/aaaaa"),
 | 
			
		||||
              getSocket: () => StubTCPSocket(play: []),
 | 
			
		||||
              getConnection: () => XmppConnection()
 | 
			
		||||
              getConnection: () => XmppConnection(TestingReconnectionPolicy())
 | 
			
		||||
            )
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
@ -62,7 +63,7 @@ void main() {
 | 
			
		||||
              isFeatureSupported: (_) => false,
 | 
			
		||||
              getFullJID: () => JID.fromString("some.user@example.server/aaaaa"),
 | 
			
		||||
              getSocket: () => StubTCPSocket(play: []),
 | 
			
		||||
              getConnection: () => XmppConnection()
 | 
			
		||||
              getConnection: () => XmppConnection(TestingReconnectionPolicy())
 | 
			
		||||
          ));
 | 
			
		||||
 | 
			
		||||
          csi.setActive();
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ import "package:moxxyv2/xmpp/stanza.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/presence.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/roster.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/events.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/reconnect.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/managers/attributes.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/managers/data.dart";
 | 
			
		||||
import "package:moxxyv2/xmpp/xeps/xep_0030/xep_0030.dart";
 | 
			
		||||
@ -39,7 +40,7 @@ Future<bool> testRosterManager(String bareJid, String resource, String stanzaStr
 | 
			
		||||
      isFeatureSupported: (_) => false,
 | 
			
		||||
      getFullJID: () => JID.fromString("$bareJid/$resource"),
 | 
			
		||||
      getSocket: () => StubTCPSocket(play: []),
 | 
			
		||||
      getConnection: () => XmppConnection()
 | 
			
		||||
      getConnection: () => XmppConnection(TestingReconnectionPolicy())
 | 
			
		||||
  ));
 | 
			
		||||
 | 
			
		||||
  final stanza = Stanza.fromXMLNode(XMLNode.fromString(stanzaString));
 | 
			
		||||
@ -215,7 +216,7 @@ void main() {
 | 
			
		||||
        ]
 | 
			
		||||
      );
 | 
			
		||||
      // TODO: This test is broken since we query the server and enable carbons
 | 
			
		||||
      final XmppConnection conn = XmppConnection(socket: fakeSocket);
 | 
			
		||||
      final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), socket: fakeSocket);
 | 
			
		||||
      conn.setConnectionSettings(ConnectionSettings(
 | 
			
		||||
          jid: JID.fromString("polynomdivision@test.server"),
 | 
			
		||||
          password: "aaaa",
 | 
			
		||||
@ -294,7 +295,7 @@ void main() {
 | 
			
		||||
        ]
 | 
			
		||||
      );
 | 
			
		||||
      bool receivedEvent = false;
 | 
			
		||||
      final XmppConnection conn = XmppConnection(socket: fakeSocket);
 | 
			
		||||
      final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), socket: fakeSocket);
 | 
			
		||||
      conn.setConnectionSettings(ConnectionSettings(
 | 
			
		||||
          jid: JID.fromString("polynomdivision@test.server"),
 | 
			
		||||
          password: "aaaa",
 | 
			
		||||
@ -378,7 +379,7 @@ void main() {
 | 
			
		||||
        ]
 | 
			
		||||
      );
 | 
			
		||||
      bool receivedEvent = false;
 | 
			
		||||
      final XmppConnection conn = XmppConnection(socket: fakeSocket);
 | 
			
		||||
      final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), socket: fakeSocket);
 | 
			
		||||
      conn.setConnectionSettings(ConnectionSettings(
 | 
			
		||||
          jid: JID.fromString("polynomdivision@test.server"),
 | 
			
		||||
          password: "aaaa",
 | 
			
		||||
@ -466,7 +467,7 @@ void main() {
 | 
			
		||||
          )
 | 
			
		||||
        ]
 | 
			
		||||
      );
 | 
			
		||||
      final XmppConnection conn = XmppConnection(socket: fakeSocket);
 | 
			
		||||
      final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), socket: fakeSocket);
 | 
			
		||||
      conn.setConnectionSettings(ConnectionSettings(
 | 
			
		||||
          jid: JID.fromString("polynomdivision@test.server"),
 | 
			
		||||
          password: "aaaa",
 | 
			
		||||
@ -505,7 +506,7 @@ void main() {
 | 
			
		||||
              isFeatureSupported: (_) => false,
 | 
			
		||||
              getFullJID: () => JID.fromString("some.user@example.server/aaaaa"),
 | 
			
		||||
              getSocket: () => StubTCPSocket(play: []),
 | 
			
		||||
              getConnection: () => XmppConnection()
 | 
			
		||||
              getConnection: () => XmppConnection(TestingReconnectionPolicy())
 | 
			
		||||
          ));
 | 
			
		||||
 | 
			
		||||
          // NOTE: Based on https://gultsch.de/gajim_roster_push_and_message_interception.html
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user