mirror of
https://github.com/flutter/flutter
synced 2024-09-19 16:21:58 +00:00
[flutter_tools] Fix race condition with completer in devfs_web (#109059)
This commit is contained in:
parent
d823c88349
commit
017dd3e7d6
|
@ -625,6 +625,12 @@ class ConnectionResult {
|
|||
final vm_service.VmService vmService;
|
||||
}
|
||||
|
||||
typedef VmServiceFactory = Future<vm_service.VmService> Function(
|
||||
Uri, {
|
||||
CompressionOptions compression,
|
||||
required Logger logger,
|
||||
});
|
||||
|
||||
/// The web specific DevFS implementation.
|
||||
class WebDevFS implements DevFS {
|
||||
/// Create a new [WebDevFS] instance.
|
||||
|
@ -686,9 +692,17 @@ class WebDevFS implements DevFS {
|
|||
/// Connect and retrieve the [DebugConnection] for the current application.
|
||||
///
|
||||
/// Only calls [AppConnection.runMain] on the subsequent connections.
|
||||
Future<ConnectionResult?> connect(bool useDebugExtension) {
|
||||
Future<ConnectionResult?> connect(
|
||||
bool useDebugExtension, {
|
||||
@visibleForTesting
|
||||
VmServiceFactory vmServiceFactory = createVmServiceDelegate,
|
||||
}) {
|
||||
final Completer<ConnectionResult> firstConnection =
|
||||
Completer<ConnectionResult>();
|
||||
// Note there is an asynchronous gap between this being set to true and
|
||||
// [firstConnection] completing; thus test the boolean to determine if
|
||||
// the current connection is the first.
|
||||
bool foundFirstConnection = false;
|
||||
_connectedApps =
|
||||
dwds.connectedApps.listen((AppConnection appConnection) async {
|
||||
try {
|
||||
|
@ -696,10 +710,11 @@ class WebDevFS implements DevFS {
|
|||
? await (_cachedExtensionFuture ??=
|
||||
dwds.extensionDebugConnections.stream.first)
|
||||
: await dwds.debugConnection(appConnection);
|
||||
if (firstConnection.isCompleted) {
|
||||
if (foundFirstConnection) {
|
||||
appConnection.runMain();
|
||||
} else {
|
||||
final vm_service.VmService vmService = await createVmServiceDelegate(
|
||||
foundFirstConnection = true;
|
||||
final vm_service.VmService vmService = await vmServiceFactory(
|
||||
Uri.parse(debugConnection.uri),
|
||||
logger: globals.logger,
|
||||
);
|
||||
|
@ -713,7 +728,8 @@ class WebDevFS implements DevFS {
|
|||
}
|
||||
}, onError: (Object error, StackTrace stackTrace) {
|
||||
globals.printError(
|
||||
'Unknown error while waiting for debug connection:$error\n$stackTrace');
|
||||
'Unknown error while waiting for debug connection:$error\n$stackTrace',
|
||||
);
|
||||
if (!firstConnection.isCompleted) {
|
||||
firstConnection.completeError(error, stackTrace);
|
||||
}
|
||||
|
@ -756,6 +772,7 @@ class WebDevFS implements DevFS {
|
|||
nullSafetyMode,
|
||||
testMode: testMode,
|
||||
);
|
||||
|
||||
final int selectedPort = webAssetServer.selectedPort;
|
||||
if (buildInfo.dartDefines.contains('FLUTTER_WEB_AUTO_DETECT=true')) {
|
||||
webAssetServer.webRenderer = WebRendererMode.autoDetect;
|
||||
|
|
|
@ -1352,7 +1352,7 @@ class FakeWebDevFS extends Fake implements WebDevFS {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<ConnectionResult> connect(bool useDebugExtension) async {
|
||||
Future<ConnectionResult> connect(bool useDebugExtension, {VmServiceFactory vmServiceFactory = createVmServiceDelegate}) async {
|
||||
if (exception != null) {
|
||||
assert(exception is Exception || exception is Error);
|
||||
// ignore: only_throw_errors, exception is either Error or Exception here.
|
||||
|
|
|
@ -4,8 +4,11 @@
|
|||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io' hide Directory, File;
|
||||
|
||||
import 'package:dwds/dwds.dart';
|
||||
import 'package:fake_async/fake_async.dart';
|
||||
import 'package:flutter_tools/src/artifacts.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
|
@ -24,6 +27,7 @@ import 'package:meta/meta.dart';
|
|||
import 'package:package_config/package_config.dart';
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:test/fake.dart';
|
||||
import 'package:vm_service/vm_service.dart' as vm_service;
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/testbed.dart';
|
||||
|
@ -870,6 +874,71 @@ void main() {
|
|||
Artifacts: () => Artifacts.test(),
|
||||
}));
|
||||
|
||||
test('.connect() will never call vmServiceFactory twice', () => testbed.run(() async {
|
||||
await FakeAsync().run<Future<void>>((FakeAsync time) {
|
||||
final File outputFile = globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
|
||||
..createSync(recursive: true);
|
||||
outputFile.parent.childFile('a.sources').writeAsStringSync('');
|
||||
outputFile.parent.childFile('a.json').writeAsStringSync('{}');
|
||||
outputFile.parent.childFile('a.map').writeAsStringSync('{}');
|
||||
outputFile.parent.childFile('a.metadata').writeAsStringSync('{}');
|
||||
|
||||
final WebDevFS webDevFS = WebDevFS(
|
||||
// if this is any other value, we will do a real ip lookup
|
||||
hostname: 'any',
|
||||
port: 0,
|
||||
packagesFilePath: '.packages',
|
||||
urlTunneller: null,
|
||||
useSseForDebugProxy: true,
|
||||
useSseForDebugBackend: true,
|
||||
useSseForInjectedClient: true,
|
||||
nullAssertions: true,
|
||||
nativeNullAssertions: true,
|
||||
buildInfo: const BuildInfo(
|
||||
BuildMode.debug,
|
||||
'',
|
||||
treeShakeIcons: false,
|
||||
),
|
||||
enableDwds: true,
|
||||
enableDds: false,
|
||||
entrypoint: Uri.base,
|
||||
testMode: true,
|
||||
expressionCompiler: null,
|
||||
chromiumLauncher: null,
|
||||
nullSafetyMode: NullSafetyMode.sound,
|
||||
);
|
||||
webDevFS.requireJS.createSync(recursive: true);
|
||||
webDevFS.stackTraceMapper.createSync(recursive: true);
|
||||
final FakeAppConnection firstConnection = FakeAppConnection();
|
||||
final FakeAppConnection secondConnection = FakeAppConnection();
|
||||
|
||||
final Future<void> done = webDevFS.create().then<void>((Uri _) {
|
||||
// In non-test mode, webDevFS.create() would have initialized DWDS
|
||||
webDevFS.webAssetServer.dwds = FakeDwds(<AppConnection>[firstConnection, secondConnection]);
|
||||
|
||||
int vmServiceFactoryInvocationCount = 0;
|
||||
Future<vm_service.VmService> vmServiceFactory(Uri uri, {CompressionOptions compression, @required Logger logger}) {
|
||||
if (vmServiceFactoryInvocationCount > 0) {
|
||||
fail('Called vmServiceFactory twice!');
|
||||
}
|
||||
vmServiceFactoryInvocationCount += 1;
|
||||
return Future<vm_service.VmService>.delayed(
|
||||
const Duration(seconds: 2),
|
||||
() => FakeVmService(),
|
||||
);
|
||||
}
|
||||
return webDevFS.connect(false, vmServiceFactory: vmServiceFactory).then<void>((ConnectionResult firstConnectionResult) {
|
||||
return webDevFS.destroy();
|
||||
});
|
||||
});
|
||||
time.elapse(const Duration(seconds: 1));
|
||||
time.elapse(const Duration(seconds: 2));
|
||||
return done;
|
||||
});
|
||||
}, overrides: <Type, Generator>{
|
||||
Artifacts: () => Artifacts.test(),
|
||||
}));
|
||||
|
||||
test('Can start web server with hostname any', () => testbed.run(() async {
|
||||
final File outputFile = globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
|
||||
..createSync(recursive: true);
|
||||
|
@ -1140,3 +1209,32 @@ class FakeShaderCompiler implements DevelopmentShaderCompiler {
|
|||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
class FakeDwds extends Fake implements Dwds {
|
||||
FakeDwds(this.connectedAppsIterable) :
|
||||
connectedApps = Stream<AppConnection>.fromIterable(connectedAppsIterable);
|
||||
|
||||
final Iterable<AppConnection> connectedAppsIterable;
|
||||
|
||||
@override
|
||||
final Stream<AppConnection> connectedApps;
|
||||
|
||||
@override
|
||||
Future<DebugConnection> debugConnection(AppConnection appConnection) => Future<DebugConnection>.value(FakeDebugConnection());
|
||||
}
|
||||
|
||||
class FakeAppConnection extends Fake implements AppConnection {
|
||||
@override
|
||||
void runMain() {}
|
||||
}
|
||||
|
||||
class FakeDebugConnection extends Fake implements DebugConnection {
|
||||
FakeDebugConnection({
|
||||
this.uri = 'http://foo',
|
||||
});
|
||||
|
||||
@override
|
||||
final String uri;
|
||||
}
|
||||
|
||||
class FakeVmService extends Fake implements vm_service.VmService {}
|
||||
|
|
Loading…
Reference in a new issue