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