Recreate outputFileName completer, handle process launch errors. (#11980)

* Recreate outputFileName completer, handle process launch errors.

* Fix formatting

* Updated comment
diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart
index bf663f1..1052805 100644
--- a/packages/flutter_tools/lib/src/compile.dart
+++ b/packages/flutter_tools/lib/src/compile.dart
@@ -28,8 +28,12 @@
 }
 
 class _StdoutHandler {
+  _StdoutHandler() {
+    reset();
+  }
+
   String boundaryKey;
-  Completer<String> outputFilename = new Completer<String>();
+  Completer<String> outputFilename;
 
   void handler(String string) {
     const String kResultPrefix = 'result ';
@@ -43,6 +47,13 @@
     else
       printTrace('compile debug message: $string');
   }
+
+  // This is needed to get ready to process next compilation result output,
+  // with its own boundary key and new completer.
+  void reset() {
+    boundaryKey = null;
+    outputFilename = new Completer<String>();
+  }
 }
 
 Future<String> compile({String sdkRoot, String mainPath}) async {
@@ -59,9 +70,12 @@
     '--sdk-root',
     sdkRoot,
     mainPath
-  ]);
+  ]).catchError((dynamic error, StackTrace stack) {
+    printTrace('Failed to start frontend server $error, $stack');
+  });
 
   final _StdoutHandler stdoutHandler = new _StdoutHandler();
+
   server.stderr
     .transform(UTF8.decoder)
     .listen((String s) { printTrace('compile debug message: $s'); });
@@ -97,6 +111,8 @@
   /// Binary file name is returned if compilation was successful, otherwise
   /// `null` is returned.
   Future<String> recompile(String mainPath, List<String> invalidatedFiles) async {
+    stdoutHandler.reset();
+
     // First time recompile is called we actually have to compile the app from
     // scratch ignoring list of invalidated files.
     if (_server == null)
@@ -112,18 +128,16 @@
   }
 
   Future<String> _compile(String scriptFilename) async {
-    if (_server == null) {
-      final String frontendServer = artifacts.getArtifactPath(
-        Artifact.frontendServerSnapshotForEngineDartSdk
-      );
-      _server = await processManager.start(<String>[
-        _dartExecutable(),
-        frontendServer,
-        '--sdk-root',
-        _sdkRoot,
-        '--incremental'
-      ]);
-    }
+    final String frontendServer = artifacts.getArtifactPath(
+      Artifact.frontendServerSnapshotForEngineDartSdk
+    );
+    _server = await processManager.start(<String>[
+      _dartExecutable(),
+      frontendServer,
+      '--sdk-root',
+      _sdkRoot,
+      '--incremental'
+    ]);
     _server.stdout
       .transform(UTF8.decoder)
       .transform(const LineSplitter())
diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart
index 5a0e550..b95d1ae 100644
--- a/packages/flutter_tools/lib/src/devfs.dart
+++ b/packages/flutter_tools/lib/src/devfs.dart
@@ -429,20 +429,26 @@
           assetPathsToEvict.add(archivePath);
       }
     });
+    if (generator != null) {
+      // We run generator even if [dirtyEntries] was empty because we want
+      // to keep logic of accepting/rejecting generator's output simple:
+      // we must accept/reject generator's output after every [update] call.
+      // Incremental run with no changes is supposed to be fast (considering
+      // that it is initiated by user key press).
+      final List<String> invalidatedFiles = <String>[];
+      for (DevFSContent content in dirtyEntries.values)
+        if (content is DevFSFileContent)
+          invalidatedFiles.add(content.file.uri.toString());
+      printTrace('Compiling dart to kernel with ${invalidatedFiles.length} updated files');
+      final String compiledBinary = await generator.recompile(mainPath, invalidatedFiles);
+      if (compiledBinary != null && compiledBinary.isNotEmpty)
+        dirtyEntries.putIfAbsent(
+          Uri.parse(target + '.dill'),
+          () => new DevFSFileContent(fs.file(compiledBinary))
+        );
+    }
     if (dirtyEntries.isNotEmpty) {
       printTrace('Updating files');
-      if (generator != null) {
-        final List<String> invalidatedFiles = <String>[];
-        dirtyEntries.forEach((Uri deviceUri, DevFSContent content) {
-          if (content is DevFSFileContent)
-            invalidatedFiles.add(content.file.uri.toString());
-        });
-        final String compiledBinary = await generator.recompile(mainPath, invalidatedFiles);
-        if (compiledBinary != null && compiledBinary.isNotEmpty)
-          dirtyEntries.putIfAbsent(Uri.parse(target + '.dill'),
-                  () => new DevFSFileContent(fs.file(compiledBinary)));
-      }
-
       if (_httpWriter != null) {
         try {
           await _httpWriter.write(dirtyEntries);
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 464ded4..7215dd8 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -135,6 +135,10 @@
 
     await refreshViews();
     for (FlutterDevice device in flutterDevices) {
+      // VM must have accepted the kernel binary, there will be no reload
+      // report, so we let incremental compiler know that source code was accepted.
+      if (device.generator != null)
+        device.generator.accept();
       for (FlutterView view in device.views)
         printTrace('Connected to $view.');
     }
@@ -334,6 +338,10 @@
       return new OperationResult(1, 'DevFS synchronization failed');
     // Check if the isolate is paused and resume it.
     for (FlutterDevice device in flutterDevices) {
+      // VM must have accepted the kernel binary, there will be no reload
+      // report, so we let incremental compiler know that source code was accepted.
+      if (device.generator != null)
+        device.generator.accept();
       for (FlutterView view in device.views) {
         if (view.uiIsolate != null) {
           // Reload the isolate.
@@ -495,6 +503,8 @@
           device.updateReloadStatus(validateReloadReport(firstReport,
             printErrors: false));
           retrieveFirstReloadReport.complete(firstReport);
+        }, onError: (dynamic error, StackTrace stack) {
+          retrieveFirstReloadReport.completeError(error, stack);
         });
       }
 
diff --git a/packages/flutter_tools/test/compile_test.dart b/packages/flutter_tools/test/compile_test.dart
index 3047c22..d0550b3 100644
--- a/packages/flutter_tools/test/compile_test.dart
+++ b/packages/flutter_tools/test/compile_test.dart
@@ -121,32 +121,70 @@
     testUsingContext('compile and recompile', () async {
       final BufferLogger logger = context[Logger];
 
-      when(mockFrontendServer.stdout).thenReturn(new Stream<List<int>>.fromFuture(
-        new Future<List<int>>.value(UTF8.encode(
-          'result abc\nline1\nline2\nabc /path/to/main.dart.dill'
-        ))
-      ));
-
+      final StreamController<List<int>> streamController = new StreamController<List<int>>();
+      when(mockFrontendServer.stdout).thenReturn(streamController.stream);
+      streamController.add(UTF8.encode('result abc\nline0\nline1\nabc /path/to/main.dart.dill\n'));
       await generator.recompile('/path/to/main.dart', null /* invalidatedFiles */);
       verify(mockFrontendServerStdIn.writeln('compile /path/to/main.dart'));
 
-      final String output = await generator.recompile(
-        null /* mainPath */,
-        <String>['/path/to/main.dart']
-      );
-      final String recompileCommand = verify(mockFrontendServerStdIn.writeln(captureThat(startsWith('recompile ')))).captured[0];
-      final String token = recompileCommand.split(' ')[1];
-      verify(mockFrontendServerStdIn.writeln('/path/to/main.dart'));
-      verify(mockFrontendServerStdIn.writeln(token));
+      await _recompile(streamController, generator, mockFrontendServerStdIn,
+        'result abc\nline1\nline2\nabc /path/to/main.dart.dill\n');
+
       verifyNoMoreInteractions(mockFrontendServerStdIn);
-      expect(logger.traceText, equals('compile debug message: line1\ncompile debug message: line2\n'));
-      expect(output, equals('/path/to/main.dart.dill'));
+      expect(logger.traceText, equals(
+        'compile debug message: line0\ncompile debug message: line1\n'
+        'compile debug message: line1\ncompile debug message: line2\n'
+      ));
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext('compile and recompile twice', () async {
+      final BufferLogger logger = context[Logger];
+
+      final StreamController<List<int>> streamController = new StreamController<List<int>>();
+      when(mockFrontendServer.stdout).thenReturn(streamController.stream);
+      streamController.add(UTF8.encode(
+        'result abc\nline0\nline1\nabc /path/to/main.dart.dill\n'
+      ));
+      await generator.recompile('/path/to/main.dart', null /* invalidatedFiles */);
+      verify(mockFrontendServerStdIn.writeln('compile /path/to/main.dart'));
+
+      await _recompile(streamController, generator, mockFrontendServerStdIn,
+        'result abc\nline1\nline2\nabc /path/to/main.dart.dill\n');
+      await _recompile(streamController, generator, mockFrontendServerStdIn,
+        'result abc\nline2\nline3\nabc /path/to/main.dart.dill\n');
+
+      verifyNoMoreInteractions(mockFrontendServerStdIn);
+      expect(logger.traceText, equals(
+        'compile debug message: line0\ncompile debug message: line1\n'
+        'compile debug message: line1\ncompile debug message: line2\n'
+        'compile debug message: line2\ncompile debug message: line3\n'
+      ));
     }, overrides: <Type, Generator>{
       ProcessManager: () => mockProcessManager,
     });
   });
 }
 
+Future<Null> _recompile(StreamController<List<int>> streamController,
+  ResidentCompiler generator, MockStdIn mockFrontendServerStdIn,
+  String mockCompilerOutput) async {
+  // Put content into the output stream after generator.recompile gets
+  // going few lines below, resets completer.
+  new Future<List<int>>(() {
+    streamController.add(UTF8.encode(mockCompilerOutput));
+  });
+  final String output = await generator.recompile(null /* mainPath */, <String>['/path/to/main.dart']);
+  expect(output, equals('/path/to/main.dart.dill'));
+  final String recompileCommand = verify(
+    mockFrontendServerStdIn.writeln(captureThat(startsWith('recompile ')))
+  ).captured[0];
+  final String token1 = recompileCommand.split(' ')[1];
+  verify(mockFrontendServerStdIn.writeln('/path/to/main.dart'));
+  verify(mockFrontendServerStdIn.writeln(token1));
+}
+
 class MockProcessManager extends Mock implements ProcessManager {}
 class MockProcess extends Mock implements Process {}
 class MockStream extends Mock implements Stream<List<int>> {}