From 058511cb668a1373059a6d6829cb1cbf3b9ef577 Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Wed, 01 Apr 2026 12:56:14 +0200
Subject: [PATCH] fix: race configured hosts first, scan network only as fallback, 500ms probe timeout
---
lib/services/mqtt_service.dart | 59 +++++++++++++++++++++++++++++++++++------------------------
1 files changed, 35 insertions(+), 24 deletions(-)
diff --git a/lib/services/mqtt_service.dart b/lib/services/mqtt_service.dart
index b88aa31..a872fd7 100644
--- a/lib/services/mqtt_service.dart
+++ b/lib/services/mqtt_service.dart
@@ -114,40 +114,51 @@
final clientId = await _getClientId();
- // Probe all hosts in parallel to find which one responds, then connect to the winner
- final hosts = <String>{};
+ // Phase 1: Race configured hosts (fast — just TLS probe, ~1s each)
+ final hosts = <String>[];
if (config.localHost != null && config.localHost!.isNotEmpty) hosts.add(config.localHost!);
- if (_lastDiscoveredHost != null) hosts.add(_lastDiscoveredHost!);
+ if (_lastDiscoveredHost != null && !hosts.contains(_lastDiscoveredHost)) hosts.add(_lastDiscoveredHost!);
if (config.vpnHost != null && config.vpnHost!.isNotEmpty) hosts.add(config.vpnHost!);
if (config.host.isNotEmpty) hosts.add(config.host);
- _mqttLog('MQTT: probing ${hosts.length} hosts in parallel: ${hosts.join(", ")}');
- onStatusDetail?.call('Probing ${hosts.length} hosts...');
+ _mqttLog('MQTT: racing ${hosts.length} configured hosts: ${hosts.join(", ")}');
+ onStatusDetail?.call('Connecting...');
- // Probe all configured hosts in parallel — first to respond wins
+ // Race: first probe to succeed wins, don't wait for others
String? winner;
if (hosts.isNotEmpty) {
- final probes = hosts.map((h) => _probeHost(h, config.port)).toList();
- // Also start discovery in parallel
- if (_lastDiscoveredHost == null) {
- probes.add(() async {
- final discovered = await _discoverViaMdns();
- if (discovered != null) {
- _lastDiscoveredHost = discovered;
- return discovered;
+ final completer = Completer<String?>();
+ int pending = hosts.length;
+ for (final host in hosts) {
+ _probeHost(host, config.port).then((result) {
+ if (result != null && !completer.isCompleted) {
+ completer.complete(result);
+ } else {
+ pending--;
+ if (pending <= 0 && !completer.isCompleted) {
+ completer.complete(null); // All failed
+ }
}
- return null;
- }());
+ });
}
- final results = await Future.wait(probes);
- winner = results.firstWhere((r) => r != null, orElse: () => null);
- } else if (_lastDiscoveredHost == null) {
- // No configured hosts — try discovery only
- winner = await _discoverViaMdns();
- if (winner != null) _lastDiscoveredHost = winner;
+ winner = await completer.future.timeout(
+ const Duration(seconds: 3),
+ onTimeout: () => null,
+ );
+ }
+
+ // Phase 2: If configured hosts failed, try Bonjour/subnet discovery
+ if (winner == null && !_intentionalClose) {
+ _mqttLog('MQTT: configured hosts failed, trying discovery...');
+ onStatusDetail?.call('Scanning network...');
+ final discovered = await _discoverViaMdns();
+ if (discovered != null) {
+ _lastDiscoveredHost = discovered;
+ winner = discovered;
+ }
}
if (winner != null && !_intentionalClose) {
- _mqttLog('MQTT: probe winner: $winner, connecting...');
+ _mqttLog('MQTT: winner: $winner, connecting...');
onStatusDetail?.call('Connecting to $winner...');
try {
if (await _tryConnect(winner, clientId, timeout: 5000)) return;
@@ -314,7 +325,7 @@
final socket = await SecureSocket.connect(
host,
port,
- timeout: const Duration(seconds: 1),
+ timeout: const Duration(milliseconds: 500),
onBadCertificate: (_) => true, // Accept self-signed cert during scan
);
await socket.close();
--
Gitblit v1.3.1