@@ -10,6 +10,8 @@ import 'dart:convert';
1010import 'dart:io' as io;
1111import 'dart:math' as math;
1212
13+ import 'package:collection/collection.dart' ;
14+
1315import 'android.dart' ;
1416import 'browser_controller.dart' ;
1517import 'command.dart' ;
@@ -498,10 +500,6 @@ class CommandExecutorImpl implements CommandExecutor {
498500 final int maxBrowserProcesses;
499501 AdbDevicePool ? adbDevicePool;
500502
501- /// For dart2js and analyzer batch processing,
502- /// we keep a list of batch processes.
503- final _batchProcesses = < String , List <BatchRunnerProcess >> {};
504-
505503 /// We keep a BrowserTestRunner for every configuration.
506504 final _browserTestRunners = < TestConfiguration , Future <BrowserTestRunner >> {};
507505
@@ -516,22 +514,14 @@ class CommandExecutorImpl implements CommandExecutor {
516514 assert (! _finishing);
517515 _finishing = true ;
518516
519- Future terminateBatchRunners () {
520- var futures = < Future > [];
521- for (var runners in _batchProcesses.values) {
522- futures.addAll (runners.map ((runner) => runner.terminate ()));
523- }
524- return Future .wait (futures);
525- }
526-
527517 Future terminateBrowserRunners () async {
528518 var futures = _browserTestRunners.values
529519 .map ((runner) async => (await runner).terminate ());
530520 return Future .wait (futures);
531521 }
532522
533523 return Future .wait ([
534- terminateBatchRunners (),
524+ BatchRunnerProcess . terminateAll (),
535525 terminateBrowserRunners (),
536526 ]);
537527 }
@@ -562,18 +552,15 @@ class CommandExecutorImpl implements CommandExecutor {
562552 // For now, we always run vm_compile_to_kernel in batch mode.
563553 var name = command.displayName;
564554 assert (name == 'vm_compile_to_kernel' );
565- return _getBatchRunner (name)
566- .runCommand (name, command, timeout, command.arguments);
555+ return _getBatchRunner (name).runCommand (command, timeout);
567556 } else if (command is AnalysisCommand && globalConfiguration.batch) {
568- return _getBatchRunner (command.displayName)
569- .runCommand (command.displayName, command, timeout, command.arguments);
557+ return _getBatchRunner (command.displayName).runCommand (command, timeout);
570558 } else if (command is CompilationCommand &&
571559 (command.displayName == 'dart2js' ||
572560 command.displayName == 'ddc' ||
573561 command.displayName == 'fasta' ) &&
574562 globalConfiguration.batch) {
575- return _getBatchRunner (command.displayName)
576- .runCommand (command.displayName, command, timeout, command.arguments);
563+ return _getBatchRunner (command.displayName).runCommand (command, timeout);
577564 } else if (command is ScriptCommand ) {
578565 return command.run ();
579566 } else if (command is AdbPrecompilationCommand ||
@@ -776,18 +763,8 @@ class CommandExecutorImpl implements CommandExecutor {
776763 utf8.encode ('$writer ' ), [], stopwatch.elapsed, false );
777764 }
778765
779- BatchRunnerProcess _getBatchRunner (String identifier) {
780- // Start batch processes if needed.
781- var runners = _batchProcesses.putIfAbsent (
782- identifier,
783- () => List <BatchRunnerProcess >.generate (maxProcesses,
784- (_) => BatchRunnerProcess (useJson: identifier == "fasta" )));
785-
786- for (var runner in runners) {
787- if (! runner._currentlyRunning) return runner;
788- }
789- throw Exception ('Unable to find inactive batch runner.' );
790- }
766+ BatchRunnerProcess _getBatchRunner (String identifier) =>
767+ BatchRunnerProcess .byIdentifier (identifier, maxProcesses);
791768
792769 Future <CommandOutput > _startBrowserControllerTest (
793770 BrowserTestCommand browserCommand, int timeout) async {
@@ -966,14 +943,16 @@ class TestCaseCompleter {
966943}
967944
968945class BatchRunnerProcess {
946+ /// For dart2js and analyzer batch processing,
947+ /// we keep a list of batch processes.
948+ static final _batchProcesses = < String , List <BatchRunnerProcess >> {};
949+
969950 /// When true, the command line is passed to the test runner as a
970951 /// JSON-encoded list of strings.
971952 final bool _useJson;
972953
973- Completer <CommandOutput >? _completer;
974- late ProcessCommand _command;
954+ ProcessCommand ? _command;
975955 late List <String > _arguments;
976- String ? _runnerType;
977956
978957 bool _processJustStarted = false ;
979958 io.Process ? _process;
@@ -982,69 +961,68 @@ class BatchRunnerProcess {
982961 late Completer <void > _stderrCompleter;
983962 late StreamSubscription <String > _stdoutSubscription;
984963 late StreamSubscription <String > _stderrSubscription;
985- late Function _processExitHandler;
964+ late Function ( int ) _processExitHandler;
986965
987- bool _currentlyRunning = false ;
988966 late OutputLog _testStdout;
989967 late OutputLog _testStderr;
990968 String ? _status;
991969 late DateTime _startTime;
992970 Timer ? _timer;
993- int _testCount = 0 ;
994971
995- static const int extraStartupTimeout = 60 ;
972+ static const int _extraStartupTimeout = 60 ;
973+
974+ static Future terminateAll () => Future .wait ([
975+ for (var runners in _batchProcesses.values)
976+ for (var runner in runners) runner.terminate ()
977+ ]);
978+
979+ BatchRunnerProcess ._(this ._useJson);
980+
981+ factory BatchRunnerProcess .byIdentifier (String identifier, int maxProcesses) {
982+ // Start batch processes if needed.
983+ var runners = _batchProcesses.putIfAbsent (
984+ identifier,
985+ () => List <BatchRunnerProcess >.generate (
986+ maxProcesses, (_) => BatchRunnerProcess ._(identifier == "fasta" )));
987+
988+ for (var runner in runners) {
989+ if (! runner._currentlyRunning) return runner;
990+ }
991+ throw Exception ('Unable to find inactive batch runner.' );
992+ }
993+
994+ bool get _currentlyRunning => _command != null ;
995+
996+ bool isCompatibleRunner (ProcessCommand command) => const MapEquality ()
997+ .equals (_processEnvironmentOverrides, command.environmentOverrides);
996998
997- BatchRunnerProcess ({ bool useJson = true }) : _useJson = useJson ;
999+ bool get hasRunningProcess => _process != null ;
9981000
999- Future <CommandOutput > runCommand (String runnerType, ProcessCommand command,
1000- int timeout, List <String > arguments) {
1001- assert (_completer == null );
1001+ Future <CommandOutput > runCommand (ProcessCommand command, int timeout) async {
10021002 assert (! _currentlyRunning);
10031003
1004- _completer = Completer ();
1005- var sameRunnerType = _runnerType == runnerType &&
1006- _dictEquals (_processEnvironmentOverrides, command.environmentOverrides);
1007- _runnerType = runnerType;
1008- _currentlyRunning = true ;
1004+ if (! isCompatibleRunner (command)) {
1005+ await terminate ();
1006+ }
10091007 _command = command;
1010- _arguments = arguments;
1008+ _arguments = command. arguments;
10111009 _processEnvironmentOverrides = command.environmentOverrides;
1012-
1013- // TODO(jmesserly): this restarts `dartdevc --batch` to work around a
1014- // memory leak, see https://github.com/dart-lang/sdk/issues/30314.
1015- var clearMemoryLeak = command is CompilationCommand &&
1016- command.displayName == 'dartdevc' &&
1017- ++ _testCount % 100 == 0 ;
10181010 if (_process == null ) {
1019- // Start process if not yet started.
1020- _startProcess (() {
1021- doStartTest (command, timeout);
1022- });
1023- } else if (! sameRunnerType || clearMemoryLeak) {
1024- // Restart this runner with the right executable for this test if needed.
1025- _processExitHandler = (_) {
1026- _startProcess (() {
1027- doStartTest (command, timeout);
1028- });
1029- };
1030- _process! .kill ();
1031- _stdoutSubscription.cancel ();
1032- _stderrSubscription.cancel ();
1033- } else {
1034- doStartTest (command, timeout);
1011+ await _startProcess ();
10351012 }
1036- return _completer ! .future ;
1013+ return await _doStartTest (timeout) ;
10371014 }
10381015
1039- Future <bool > terminate () {
1040- if (_process == null ) return Future . value ( true ) ;
1041- var terminateCompleter = Completer <bool >();
1016+ Future <void > terminate () async {
1017+ if (_process == null ) return ;
1018+ var terminateCompleter = Completer <void >();
10421019 final sigkillTimer = Timer (const Duration (seconds: 5 ), () {
10431020 _process? .kill (io.ProcessSignal .sigkill);
10441021 });
10451022 _processExitHandler = (_) {
10461023 sigkillTimer.cancel ();
1047- terminateCompleter.complete (true );
1024+ _process = null ;
1025+ terminateCompleter.complete ();
10481026 };
10491027 _process! .kill ();
10501028 _stdoutSubscription.cancel ();
@@ -1053,27 +1031,30 @@ class BatchRunnerProcess {
10531031 return terminateCompleter.future;
10541032 }
10551033
1056- void doStartTest ( Command command, int timeout) {
1034+ Future < CommandOutput > _doStartTest ( int timeout) async {
10571035 if (_processJustStarted) {
10581036 // We just started the process, add some extra timeout to account for
10591037 // the startup cost of the batch compiler.
10601038 _processJustStarted = false ;
1061- timeout += extraStartupTimeout ;
1039+ timeout += _extraStartupTimeout ;
10621040 }
10631041 _startTime = DateTime .now ();
10641042 _testStdout = OutputLog ();
10651043 _testStderr = OutputLog ();
10661044 _status = null ;
10671045 _stdoutCompleter = Completer ();
10681046 _stderrCompleter = Completer ();
1047+ _stdoutSubscription.resume ();
1048+ _stderrSubscription.resume ();
10691049 _timer = Timer (Duration (seconds: timeout), _timeoutHandler);
10701050
10711051 var line = _createArgumentsLine (_arguments, timeout);
10721052 _process! .stdin.write (line);
1073- _stdoutSubscription.resume ();
1074- _stderrSubscription.resume ();
1075- Future .wait ([_stdoutCompleter.future, _stderrCompleter.future])
1076- .then ((_) => _reportResult ());
1053+ await (_stdoutCompleter.future, _stderrCompleter.future).wait;
1054+ if (_status == null ) {
1055+ await _process! .exitCode;
1056+ }
1057+ return _reportResult ();
10771058 }
10781059
10791060 String _createArgumentsLine (List <String > arguments, int timeout) {
@@ -1084,9 +1065,7 @@ class BatchRunnerProcess {
10841065 }
10851066 }
10861067
1087- void _reportResult () {
1088- if (! _currentlyRunning) return ;
1089-
1068+ CommandOutput _reportResult () {
10901069 var outcome = _status! .split (" " )[2 ];
10911070 var exitCode = 0 ;
10921071 if (outcome == "CRASH" ) exitCode = unhandledCompilerExceptionExitCode;
@@ -1100,47 +1079,42 @@ class BatchRunnerProcess {
11001079 exitCode = truncatedFakeExitCode;
11011080 }
11021081
1103- var output = _command.createOutput (
1082+ var output = _command! .createOutput (
11041083 exitCode,
11051084 outcome == "TIMEOUT" ,
11061085 _testStdout.bytes,
11071086 _testStderr.bytes,
11081087 DateTime .now ().difference (_startTime),
11091088 false );
1110- assert (_completer != null );
1111- _completer! .complete (output);
1112- _completer = null ;
1113- _currentlyRunning = false ;
1089+ _command = null ;
1090+ return output;
11141091 }
11151092
1116- void Function (int ) makeExitHandler (String status) {
1093+ void Function (int ) _makeExitHandler (String status) {
11171094 return (int exitCode) {
11181095 if (_currentlyRunning) {
1119- if ( _timer != null ) _timer ! .cancel ();
1096+ _timer? .cancel ();
11201097 _status = status;
11211098 _stdoutSubscription.cancel ();
11221099 _stderrSubscription.cancel ();
1123- _startProcess (_reportResult);
1124- } else {
1125- // No active test case running.
1126- _process = null ;
11271100 }
1101+ // No active test case running.
1102+ _process = null ;
11281103 };
11291104 }
11301105
11311106 void _timeoutHandler () {
1132- _processExitHandler = makeExitHandler (">>> TEST TIMEOUT" );
1107+ _processExitHandler = _makeExitHandler (">>> TEST TIMEOUT" );
11331108 _process! .kill ();
11341109 }
11351110
1136- void _startProcess (void Function () callback) async {
1137- var executable = _command.executable;
1138- var arguments = _command.batchArguments.toList ();
1139- arguments.add ('--batch' );
1140- var environment = Map <String , String >.from (io.Platform .environment);
1141- if (_processEnvironmentOverrides != null ) {
1142- environment.addAll (_processEnvironmentOverrides! );
1143- }
1111+ Future <void > _startProcess () async {
1112+ var executable = _command! .executable;
1113+ var arguments = [..._command! .batchArguments, '--batch' ];
1114+ var environment = {
1115+ ...io.Platform .environment,
1116+ ...? _processEnvironmentOverrides,
1117+ };
11441118 try {
11451119 _process = await io.Process .start (executable, arguments,
11461120 environment: environment);
@@ -1173,9 +1147,9 @@ class BatchRunnerProcess {
11731147 if (_status != null ) {
11741148 _stdoutSubscription.pause ();
11751149 _timer! .cancel ();
1176- _stdoutCompleter.complete (null );
1150+ _stdoutCompleter.complete ();
11771151 }
1178- });
1152+ }, onDone : () => _stdoutCompleter. complete () );
11791153 _stdoutSubscription.pause ();
11801154
11811155 var stderrStream = _process! .stderr
@@ -1184,35 +1158,22 @@ class BatchRunnerProcess {
11841158 _stderrSubscription = stderrStream.listen ((String line) {
11851159 if (line.startsWith ('>>> EOF STDERR' )) {
11861160 _stderrSubscription.pause ();
1187- _stderrCompleter.complete (null );
1161+ _stderrCompleter.complete ();
11881162 } else {
11891163 _testStderr.add (utf8.encode (line));
11901164 _testStderr.add ("\n " .codeUnits);
11911165 }
1192- });
1166+ }, onDone : () => _stderrCompleter. complete () );
11931167 _stderrSubscription.pause ();
11941168
1195- _processExitHandler = makeExitHandler (">>> TEST CRASH" );
1196- _process! .exitCode.then ((exitCode) {
1197- _processExitHandler (exitCode);
1198- });
1199-
12001169 _process! .stdin.done.catchError ((Object err) {
12011170 print ('Error on batch runner input stream stdin' );
12021171 print (' Previous test\' s status: $_status ' );
12031172 print (' Error: $err ' );
12041173 throw err;
12051174 });
1206- callback ();
1207- }
12081175
1209- bool _dictEquals (Map ? a, Map ? b) {
1210- if (a == null ) return b == null ;
1211- if (b == null ) return false ;
1212- if (a.length != b.length) return false ;
1213- for (var key in a.keys) {
1214- if (a[key] != b[key]) return false ;
1215- }
1216- return true ;
1176+ _processExitHandler = _makeExitHandler (">>> TEST CRASH" );
1177+ _process! .exitCode.then ((exitCode) => _processExitHandler (exitCode));
12171178 }
12181179}
0 commit comments