Skip to content

Commit e99a66a

Browse files
[flutter_tools] check if stream is open before sending message in ios device (#99947)
1 parent 2386fd9 commit e99a66a

File tree

3 files changed

+68
-13
lines changed

3 files changed

+68
-13
lines changed

packages/flutter_tools/lib/src/ios/devices.dart

+21-10
Original file line numberDiff line numberDiff line change
@@ -647,14 +647,25 @@ class IOSDeviceLogReader extends DeviceLogReader {
647647
// Logging from the dart code has no prefixing metadata.
648648
final RegExp _debuggerLoggingRegex = RegExp(r'^\S* \S* \S*\[[0-9:]*] (.*)');
649649

650-
late final StreamController<String> _linesController = StreamController<String>.broadcast(
650+
@visibleForTesting
651+
late final StreamController<String> linesController = StreamController<String>.broadcast(
651652
onListen: _listenToSysLog,
652653
onCancel: dispose,
653654
);
655+
656+
// Sometimes (race condition?) we try to send a log after the controller has
657+
// been closed. See https://github.com/flutter/flutter/issues/99021 for more
658+
// context.
659+
void _addToLinesController(String message) {
660+
if (!linesController.isClosed) {
661+
linesController.add(message);
662+
}
663+
}
664+
654665
final List<StreamSubscription<void>> _loggingSubscriptions = <StreamSubscription<void>>[];
655666

656667
@override
657-
Stream<String> get logLines => _linesController.stream;
668+
Stream<String> get logLines => linesController.stream;
658669

659670
@override
660671
FlutterVmService? get connectedVMService => _connectedVMService;
@@ -694,7 +705,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
694705
}
695706
final String message = processVmServiceMessage(event);
696707
if (message.isNotEmpty) {
697-
_linesController.add(message);
708+
_addToLinesController(message);
698709
}
699710
}
700711

@@ -717,9 +728,9 @@ class IOSDeviceLogReader extends DeviceLogReader {
717728
}
718729
// Add the debugger logs to the controller created on initialization.
719730
_loggingSubscriptions.add(debugger.logLines.listen(
720-
(String line) => _linesController.add(_debuggerLineHandler(line)),
721-
onError: _linesController.addError,
722-
onDone: _linesController.close,
731+
(String line) => _addToLinesController(_debuggerLineHandler(line)),
732+
onError: linesController.addError,
733+
onDone: linesController.close,
723734
cancelOnError: true,
724735
));
725736
}
@@ -737,8 +748,8 @@ class IOSDeviceLogReader extends DeviceLogReader {
737748
process.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen(_newSyslogLineHandler());
738749
process.stderr.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen(_newSyslogLineHandler());
739750
process.exitCode.whenComplete(() {
740-
if (_linesController.hasListener) {
741-
_linesController.close();
751+
if (linesController.hasListener) {
752+
linesController.close();
742753
}
743754
});
744755
assert(idevicesyslogProcess == null);
@@ -761,7 +772,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
761772
return (String line) {
762773
if (printing) {
763774
if (!_anyLineRegex.hasMatch(line)) {
764-
_linesController.add(decodeSyslog(line));
775+
_addToLinesController(decodeSyslog(line));
765776
return;
766777
}
767778

@@ -773,7 +784,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
773784
if (match != null) {
774785
final String logLine = line.substring(match.end);
775786
// Only display the log line after the initial device and executable information.
776-
_linesController.add(decodeSyslog(logLine));
787+
_addToLinesController(decodeSyslog(logLine));
777788

778789
printing = true;
779790
}

packages/flutter_tools/lib/src/ios/ios_deploy.dart

+7-3
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ class IOSDeployDebugger {
383383
// To avoid all lines being double spaced, if the last line from the
384384
// debugger was not an empty line, skip this empty line.
385385
// This will still cause "legit" logged newlines to be doubled...
386-
} else {
386+
} else if (!_debuggerOutput.isClosed) {
387387
_debuggerOutput.add(line);
388388
}
389389
lastLineFromDebugger = line;
@@ -413,11 +413,15 @@ class IOSDeployDebugger {
413413
} on ProcessException catch (exception, stackTrace) {
414414
_logger.printTrace('ios-deploy failed: $exception');
415415
_debuggerState = _IOSDeployDebuggerState.detached;
416-
_debuggerOutput.addError(exception, stackTrace);
416+
if (!_debuggerOutput.isClosed) {
417+
_debuggerOutput.addError(exception, stackTrace);
418+
}
417419
} on ArgumentError catch (exception, stackTrace) {
418420
_logger.printTrace('ios-deploy failed: $exception');
419421
_debuggerState = _IOSDeployDebuggerState.detached;
420-
_debuggerOutput.addError(exception, stackTrace);
422+
if (!_debuggerOutput.isClosed) {
423+
_debuggerOutput.addError(exception, stackTrace);
424+
}
421425
}
422426
// Wait until the debugger attaches, or the attempt fails.
423427
return debuggerCompleter.future;

packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart

+40
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import 'dart:async';
88

99
import 'package:flutter_tools/src/artifacts.dart';
10+
import 'package:flutter_tools/src/base/async_guard.dart';
1011
import 'package:flutter_tools/src/base/logger.dart';
1112
import 'package:flutter_tools/src/cache.dart';
1213
import 'package:flutter_tools/src/convert.dart';
@@ -309,6 +310,45 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
309310
logReader.dispose();
310311
expect(iosDeployDebugger.detached, true);
311312
});
313+
314+
testWithoutContext('Does not throw if debuggerStream set after logReader closed', () async {
315+
final Stream<String> debuggingLogs = Stream<String>.fromIterable(<String>[
316+
'2020-09-15 19:15:10.931434-0700 Runner[541:226276] Did finish launching.',
317+
'2020-09-15 19:15:10.931434-0700 Runner[541:226276] [Category] Did finish launching from logging category.',
318+
'stderr from dart',
319+
'',
320+
]);
321+
322+
final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
323+
iMobileDevice: IMobileDevice(
324+
artifacts: artifacts,
325+
processManager: processManager,
326+
cache: fakeCache,
327+
logger: logger,
328+
),
329+
useSyslog: false,
330+
);
331+
Object exception;
332+
StackTrace trace;
333+
await asyncGuard(
334+
() async {
335+
await logReader.linesController.close();
336+
final FakeIOSDeployDebugger iosDeployDebugger = FakeIOSDeployDebugger();
337+
iosDeployDebugger.logLines = debuggingLogs;
338+
logReader.debuggerStream = iosDeployDebugger;
339+
await logReader.logLines.drain<void>();
340+
},
341+
onError: (Object err, StackTrace stackTrace) {
342+
exception = err;
343+
trace = stackTrace;
344+
}
345+
);
346+
expect(
347+
exception,
348+
isNull,
349+
reason: trace.toString(),
350+
);
351+
});
312352
});
313353
}
314354

0 commit comments

Comments
 (0)