Fix event-listener gap in UdpTransport.connect() dropping late network events
Between cancelling the initial "available" subscription and attaching the network-loss listener, no listener was attached to the broadcast event stream for a brief window. Events landing in that gap (notably a late SSID update, which real hardware without STA concurrency often fires very close after "available") were silently lost since broadcast streams don't buffer for late subscribers. Now uses one continuous subscription for the whole connection lifetime instead.
This commit is contained in:
@@ -129,8 +129,28 @@ class UdpTransport implements LinkTransport {
|
|||||||
final ssidPrefix = await _getSsidPrefix();
|
final ssidPrefix = await _getSsidPrefix();
|
||||||
final rememberedSsid = await _getRememberedSsid();
|
final rememberedSsid = await _getRememberedSsid();
|
||||||
final firstEvent = Completer<MlrsNetworkEvent>();
|
final firstEvent = Completer<MlrsNetworkEvent>();
|
||||||
final requestSub = _networkController.events.listen((event) {
|
// EIN durchgehendes Abonnement ab hier, statt vorher ein temporaeres
|
||||||
if (!firstEvent.isCompleted) firstEvent.complete(event);
|
// request-Abo zu registrieren, es nach dem ersten Ereignis wieder
|
||||||
|
// abzumelden und danach durch ein zweites (fuer Netzverlust) zu
|
||||||
|
// ersetzen: zwischen `requestSub.cancel()` und dem Aufsetzen des
|
||||||
|
// zweiten Listeners lag eine (kurze, aber reale) Luecke ganz ohne
|
||||||
|
// Listener auf dem Broadcast-EventChannel-Stream - Ereignisse, die
|
||||||
|
// genau in diesem Fenster ankamen, gingen unwiderruflich verloren
|
||||||
|
// (Broadcast-Streams puffern nicht fuer spaeter hinzukommende
|
||||||
|
// Listener). Betroffen war insbesondere das nachtraeglich eintreffende
|
||||||
|
// SSID-Ereignis (`onCapabilitiesChanged`, siehe
|
||||||
|
// MlrsNetworkSsidUpdated-Doku): auf echter Hardware (Pixel-Geraet ohne
|
||||||
|
// STA-Concurrency) trifft es haeufig so kurz nach dem "available"-
|
||||||
|
// Ereignis ein, dass es fast immer in genau diese Luecke fiel - die
|
||||||
|
// Verbindungsprotokoll-Pille zeigte dauerhaft "Connected to unknown",
|
||||||
|
// obwohl `dumpsys wifi` auf dem Geraet die korrekte SSID zeigte.
|
||||||
|
_networkLossSub = _networkController.events.listen((event) {
|
||||||
|
if (!firstEvent.isCompleted &&
|
||||||
|
(event is MlrsNetworkAvailable || event is MlrsNetworkUnavailable)) {
|
||||||
|
firstEvent.complete(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_onNetworkEvent(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -139,7 +159,6 @@ class UdpTransport implements LinkTransport {
|
|||||||
preferredSsid: rememberedSsid,
|
preferredSsid: rememberedSsid,
|
||||||
);
|
);
|
||||||
final event = await firstEvent.future;
|
final event = await firstEvent.future;
|
||||||
await requestSub.cancel();
|
|
||||||
|
|
||||||
if (event is! MlrsNetworkAvailable) {
|
if (event is! MlrsNetworkAvailable) {
|
||||||
_fail(
|
_fail(
|
||||||
@@ -150,10 +169,6 @@ class UdpTransport implements LinkTransport {
|
|||||||
}
|
}
|
||||||
final available = event;
|
final available = event;
|
||||||
|
|
||||||
// Ab hier auf Netzverlust reagieren - auch waehrend des Socket-Aufbaus
|
|
||||||
// unten, nicht erst danach (Doku Abschnitt 8).
|
|
||||||
_networkLossSub = _networkController.events.listen(_onNetworkEvent);
|
|
||||||
|
|
||||||
_connectedSsid = available.ssid;
|
_connectedSsid = available.ssid;
|
||||||
_ssidController.add(_connectedSsid);
|
_ssidController.add(_connectedSsid);
|
||||||
if (available.ssid != null) {
|
if (available.ssid != null) {
|
||||||
@@ -219,13 +234,13 @@ class UdpTransport implements LinkTransport {
|
|||||||
|
|
||||||
_setState(LinkState.connected);
|
_setState(LinkState.connected);
|
||||||
} on MissingPluginException {
|
} on MissingPluginException {
|
||||||
await requestSub.cancel();
|
await _networkLossSub?.cancel();
|
||||||
|
_networkLossSub = null;
|
||||||
_fail(
|
_fail(
|
||||||
LinkErrorReason.unknown,
|
LinkErrorReason.unknown,
|
||||||
'mLRS network channel unavailable on this platform.',
|
'mLRS network channel unavailable on this platform.',
|
||||||
);
|
);
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
await requestSub.cancel();
|
|
||||||
await _networkLossSub?.cancel();
|
await _networkLossSub?.cancel();
|
||||||
_networkLossSub = null;
|
_networkLossSub = null;
|
||||||
await _networkController.releaseNetwork();
|
await _networkController.releaseNetwork();
|
||||||
@@ -263,8 +278,10 @@ class UdpTransport implements LinkTransport {
|
|||||||
'The mLRS WiFi network was lost.',
|
'The mLRS WiFi network was lost.',
|
||||||
);
|
);
|
||||||
case MlrsNetworkAvailable() || MlrsNetworkUnavailable():
|
case MlrsNetworkAvailable() || MlrsNetworkUnavailable():
|
||||||
// Wird hier nicht erwartet - diese Ereignisse werden nur waehrend
|
// Werden bereits im Listener in connect() ueber firstEvent
|
||||||
// des initialen connect() ueber firstEvent ausgewertet (siehe oben).
|
// abgefangen, bevor sie hierher durchgereicht werden (siehe dort) -
|
||||||
|
// hier nur zur Vollstaendigkeit des switch, kein weiteres Handling
|
||||||
|
// noetig.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -374,6 +374,42 @@ void main() {
|
|||||||
transport.dispose();
|
transport.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'a late-arriving SSID update immediately after "available" is not lost '
|
||||||
|
'(regression: connect() used to briefly detach its event listener '
|
||||||
|
'between the "available" and "network loss" subscriptions, dropping '
|
||||||
|
'any event that landed exactly in that gap - real hardware without '
|
||||||
|
'STA concurrency often fires the SSID update this close together)',
|
||||||
|
() async {
|
||||||
|
final fake = FakeMlrsNetworkController();
|
||||||
|
final transport = UdpTransport(
|
||||||
|
getHost: () async => null,
|
||||||
|
getPort: () async => 0,
|
||||||
|
getSsidPrefix: () async => 'mLRS-',
|
||||||
|
getRememberedSsid: () async => null,
|
||||||
|
rememberSsid: (ssid) async => fake.rememberedSsids.add(ssid),
|
||||||
|
networkController: fake,
|
||||||
|
);
|
||||||
|
|
||||||
|
final connectFuture = transport.connect();
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 10));
|
||||||
|
// Android liefert die SSID bei WifiNetworkSpecifier-Netzen oft nicht
|
||||||
|
// im "available"-Ereignis selbst, sondern in einem unmittelbar
|
||||||
|
// (synchron im selben Event-Loop-Takt) nachfolgenden "ssid"-Ereignis -
|
||||||
|
// ohne SSID im ersten Ereignis nachgebildet, beide direkt
|
||||||
|
// hintereinander emittiert.
|
||||||
|
fake.emit(const MlrsNetworkAvailable());
|
||||||
|
fake.emit(const MlrsNetworkSsidUpdated('mLRS-5287 AP UDP'));
|
||||||
|
await connectFuture;
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 10));
|
||||||
|
|
||||||
|
expect(transport.connectedSsid, 'mLRS-5287 AP UDP');
|
||||||
|
expect(fake.rememberedSsids, contains('mLRS-5287 AP UDP'));
|
||||||
|
|
||||||
|
transport.dispose();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test('connectedSsid is cleared on disconnect and network loss', () async {
|
test('connectedSsid is cleared on disconnect and network loss', () async {
|
||||||
final fake = FakeMlrsNetworkController();
|
final fake = FakeMlrsNetworkController();
|
||||||
final transport = await connectWithFake(
|
final transport = await connectWithFake(
|
||||||
|
|||||||
Reference in New Issue
Block a user