Skip to content

Commit f96d354

Browse files
committed
feat: failover on relay auth timeout
1 parent d386518 commit f96d354

4 files changed

Lines changed: 65 additions & 15 deletions

File tree

lib/app/features/ion_connect/providers/mixins/relay_create_mixin.dart

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,16 @@ import 'package:ion/app/exceptions/exceptions.dart';
99
import 'package:ion/app/features/core/providers/internet_connection_checker_provider.r.dart';
1010
import 'package:ion/app/features/core/providers/internet_status_stream_provider.r.dart';
1111
import 'package:ion/app/features/ion_connect/ion_connect.dart';
12+
import 'package:ion/app/features/ion_connect/providers/relays/relay_disliked_connect_urls_provider.r.dart';
1213
import 'package:ion/app/features/ion_connect/providers/relays/relay_proxy_domain_preference_provider.r.dart';
1314
import 'package:ion/app/features/ion_connect/providers/relays/relay_proxy_domains_provider.r.dart';
1415
import 'package:ion/app/features/user/providers/relays/relays_reachability_provider.r.dart';
1516
import 'package:ion/app/services/logger/logger.dart';
1617
import 'package:ion/app/utils/logging.dart';
1718

1819
mixin RelayCreateMixin {
19-
Future<IonConnectRelay> createRelay(
20-
Ref ref,
21-
String url, {
22-
Set<String> dislikedConnectUrls = const <String>{},
23-
}) async {
20+
Future<IonConnectRelay> createRelay(Ref ref, String url) async {
21+
final dislikedConnectUrls = ref.read(relayDislikedConnectUrlsProvider(url));
2422
final candidates = ref.read(relayConnectUrisProvider(url));
2523
final proxyDomains = ref.read(relaysProxyDomainsProvider);
2624
final savedPreferredDomain = ref.read(relayProxyDomainPreferenceProvider(url));

lib/app/features/ion_connect/providers/relays/relay_auth_provider.r.dart

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,26 @@ import 'package:ion/app/features/ion_connect/ion_connect.dart' hide requestEvent
1212
import 'package:ion/app/features/ion_connect/model/action_source.f.dart';
1313
import 'package:ion/app/features/ion_connect/model/auth_event.f.dart';
1414
import 'package:ion/app/features/ion_connect/providers/ion_connect_notifier.r.dart';
15+
import 'package:ion/app/features/ion_connect/providers/relays/relay_disliked_connect_urls_provider.r.dart';
1516
import 'package:ion/app/features/ion_connect/providers/relays/relays_replica_delay_provider.m.dart';
1617
import 'package:ion/app/features/user/providers/relays/user_relays_manager.r.dart';
1718
import 'package:ion/app/features/user/providers/user_delegation_provider.r.dart';
19+
import 'package:ion/app/utils/logging.dart';
1820
import 'package:riverpod_annotation/riverpod_annotation.dart';
1921

2022
part 'relay_auth_provider.r.g.dart';
2123

24+
const _defaultTimeout = Duration(seconds: 30);
25+
2226
@riverpod
2327
class RelayAuth extends _$RelayAuth {
2428
@override
2529
RelayAuthService build(IonConnectRelay relay) {
2630
final service = RelayAuthService(
2731
relay: relay,
32+
addDislikedConnectUrl: (connectUrl) {
33+
ref.read(relayDislikedConnectUrlsProvider(relay.url).notifier).add(connectUrl);
34+
},
2835
createAuthEvent: ({
2936
required String challenge,
3037
required String relayUrl,
@@ -97,13 +104,16 @@ class RelayAuth extends _$RelayAuth {
97104
class RelayAuthService {
98105
RelayAuthService({
99106
required this.relay,
107+
required this.addDislikedConnectUrl,
100108
required this.createAuthEvent,
101109
required this.onError,
102110
this.completer,
103111
});
104112

105113
final IonConnectRelay relay;
106114

115+
final void Function(String connectUrl) addDislikedConnectUrl;
116+
107117
final Future<EventMessage> Function({required String challenge, required String relayUrl})
108118
createAuthEvent;
109119

@@ -179,7 +189,24 @@ class RelayAuthService {
179189
final okMessage = await relay.messages
180190
.where((message) => message is OkMessage)
181191
.cast<OkMessage>()
182-
.firstWhere((message) => signedAuthEvent.id == message.eventId);
192+
.firstWhere((message) => signedAuthEvent.id == message.eventId)
193+
.timeout(
194+
_defaultTimeout,
195+
onTimeout: () {
196+
addDislikedConnectUrl(relay.connectUrl);
197+
relay.close();
198+
reportFailover(
199+
Exception(
200+
'[RELAY] Relay connection failover for logical URL: ${relay.url} and connect URL ${relay.connectUrl} with reason: AUTH OK timeout}',
201+
),
202+
StackTrace.current,
203+
tag: 'relay_failover_auth_timeout',
204+
);
205+
throw SendEventException(
206+
'auth-required: AUTH OK timeout after ${_defaultTimeout.inSeconds}s',
207+
);
208+
},
209+
);
183210

184211
if (!okMessage.accepted) {
185212
throw SendEventException(okMessage.message);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// SPDX-License-Identifier: ice License 1.0
2+
3+
import 'package:riverpod_annotation/riverpod_annotation.dart';
4+
5+
part 'relay_disliked_connect_urls_provider.r.g.dart';
6+
7+
/// Holds a per-logical-relay set of connect URLs that should be avoided for the
8+
/// current relay build / failover attempt.
9+
///
10+
/// This is intentionally separated from the Relay provider so other layers
11+
/// (e.g. auth handling) can mark a connect URL as bad and force failover.
12+
@Riverpod(keepAlive: true)
13+
class RelayDislikedConnectUrls extends _$RelayDislikedConnectUrls {
14+
@override
15+
Set<String> build(String logicalRelayUrl) => <String>{};
16+
17+
void reset() {
18+
state = <String>{};
19+
}
20+
21+
/// Adds [connectUrl] to the disliked set.
22+
///
23+
/// Returns `true` if the url was newly added.
24+
bool add(String connectUrl) {
25+
if (state.contains(connectUrl)) return false;
26+
state = {...state, connectUrl};
27+
return true;
28+
}
29+
}

lib/app/features/ion_connect/providers/relays/relay_provider.r.dart

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:ion/app/features/ion_connect/providers/mixins/relay_closed_mixin
99
import 'package:ion/app/features/ion_connect/providers/mixins/relay_create_mixin.dart';
1010
import 'package:ion/app/features/ion_connect/providers/mixins/relay_timer_mixin.dart';
1111
import 'package:ion/app/features/ion_connect/providers/relays/relay_auth_provider.r.dart';
12+
import 'package:ion/app/features/ion_connect/providers/relays/relay_disliked_connect_urls_provider.r.dart';
1213
import 'package:ion/app/services/logger/logger.dart';
1314
import 'package:ion/app/utils/logging.dart';
1415
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -22,16 +23,11 @@ class Relay extends _$Relay
2223
with RelayTimerMixin, RelayCreateMixin, RelayAuthMixin, RelayClosedMixin, RelayActiveMixin {
2324
@override
2425
Future<IonConnectRelay> build(String url, {bool anonymous = false}) async {
25-
final dislikedConnectUrls = <String>{};
26+
final dislikedConnectUrlsNotifier = ref.read(relayDislikedConnectUrlsProvider(url).notifier);
2627

2728
try {
2829
while (true) {
29-
final relay = await createRelay(
30-
ref,
31-
url,
32-
dislikedConnectUrls: dislikedConnectUrls,
33-
);
34-
30+
final relay = await createRelay(ref, url);
3531
try {
3632
// Treat auth init as part of relay creation.
3733
// If auth fails with an auth-required loop, we failover by retrying with the next connect URL.
@@ -50,7 +46,7 @@ class Relay extends _$Relay
5046
// If we are stuck in an auth-required loop, consider this connect URL unusable and retry.
5147
if (!anonymous && RelayAuthService.isRelayAuthError(e)) {
5248
final connectUrl = relay.connectUrl;
53-
final added = dislikedConnectUrls.add(connectUrl);
49+
final added = dislikedConnectUrlsNotifier.add(connectUrl);
5450

5551
// Close the bad relay before retrying.
5652
relay.close();
@@ -62,7 +58,7 @@ class Relay extends _$Relay
6258

6359
reportFailover(
6460
Exception(
65-
'[RELAY] Relay auth failover for logical URL: $connectUrl and connect URL: $connectUrl; reason: $e',
61+
'[RELAY] Relay auth failover for logical URL: $url and connect URL: $connectUrl; reason: $e',
6662
),
6763
st,
6864
tag: 'relay_failover_auth',

0 commit comments

Comments
 (0)