Unverified Commit 2c94f2b4 authored by Zachary Anderson's avatar Zachary Anderson Committed by GitHub

[flutter_tool] Kill a timing out process before trying to drain its stdout/err streams (#40159)

parent e6ae95c4
...@@ -275,13 +275,23 @@ Future<RunResult> runAsync( ...@@ -275,13 +275,23 @@ Future<RunResult> runAsync(
int exitCode; int exitCode;
exitCode = await process.exitCode.timeout(timeout, onTimeout: () { exitCode = await process.exitCode.timeout(timeout, onTimeout: () {
// The process timed out. Kill it.
processManager.killPid(process.pid);
return null; return null;
}); });
String stdoutString; String stdoutString;
String stderrString; String stderrString;
try { try {
await Future.wait<void>(<Future<void>>[stdoutFuture, stderrFuture]); Future<void> stdioFuture =
Future.wait<void>(<Future<void>>[stdoutFuture, stderrFuture]);
if (exitCode == null) {
// If we had to kill the process for a timeout, only wait a short time
// for the stdio streams to drain in case killing the process didn't
// work.
stdioFuture = stdioFuture.timeout(const Duration(seconds: 1));
}
await stdioFuture;
} catch (_) { } catch (_) {
// Ignore errors on the process' stdout and stderr streams. Just capture // Ignore errors on the process' stdout and stderr streams. Just capture
// whatever we got, and use the exit code // whatever we got, and use the exit code
...@@ -299,9 +309,6 @@ Future<RunResult> runAsync( ...@@ -299,9 +309,6 @@ Future<RunResult> runAsync(
return runResult; return runResult;
} }
// The process timed out. Kill it.
processManager.killPid(process.pid);
// If we are out of timeoutRetries, throw a ProcessException. // If we are out of timeoutRetries, throw a ProcessException.
if (timeoutRetries < 0) { if (timeoutRetries < 0) {
throw ProcessException(cmd[0], cmd.sublist(1), throw ProcessException(cmd[0], cmd.sublist(1),
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
...@@ -103,13 +105,13 @@ void main() { ...@@ -103,13 +105,13 @@ void main() {
// MockProcessManager has an implementation of start() that returns the // MockProcessManager has an implementation of start() that returns the
// result of processFactory. // result of processFactory.
flakyProcessManager = MockProcessManager(); flakyProcessManager = MockProcessManager();
});
testUsingContext('flaky process fails without retry', () async {
flakyProcessManager.processFactory = flakyProcessFactory( flakyProcessManager.processFactory = flakyProcessFactory(
flakes: 1, flakes: 1,
delay: delay, delay: delay,
); );
});
testUsingContext('flaky process fails without retry', () async {
final RunResult result = await runAsync( final RunResult result = await runAsync(
<String>['dummy'], <String>['dummy'],
timeout: delay + const Duration(seconds: 1), timeout: delay + const Duration(seconds: 1),
...@@ -120,6 +122,10 @@ void main() { ...@@ -120,6 +122,10 @@ void main() {
}); });
testUsingContext('flaky process succeeds with retry', () async { testUsingContext('flaky process succeeds with retry', () async {
flakyProcessManager.processFactory = flakyProcessFactory(
flakes: 1,
delay: delay,
);
final RunResult result = await runAsync( final RunResult result = await runAsync(
<String>['dummy'], <String>['dummy'],
timeout: delay - const Duration(milliseconds: 500), timeout: delay - const Duration(milliseconds: 500),
...@@ -131,6 +137,22 @@ void main() { ...@@ -131,6 +137,22 @@ void main() {
}); });
testUsingContext('flaky process generates ProcessException on timeout', () async { testUsingContext('flaky process generates ProcessException on timeout', () async {
final Completer<List<int>> flakyStderr = Completer<List<int>>();
final Completer<List<int>> flakyStdout = Completer<List<int>>();
flakyProcessManager.processFactory = flakyProcessFactory(
flakes: 1,
delay: delay,
stderr: () => Stream<List<int>>.fromFuture(flakyStderr.future),
stdout: () => Stream<List<int>>.fromFuture(flakyStdout.future),
);
when(flakyProcessManager.killPid(any)).thenAnswer((_) {
// Don't let the stderr stream stop until the process is killed. This
// ensures that runAsync() does not delay killing the process until
// stdout and stderr are drained (which won't happen).
flakyStderr.complete(<int>[]);
flakyStdout.complete(<int>[]);
return true;
});
expect(() async => await runAsync( expect(() async => await runAsync(
<String>['dummy'], <String>['dummy'],
timeout: delay - const Duration(milliseconds: 500), timeout: delay - const Duration(milliseconds: 500),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment