Unverified Commit 2828a459 authored by Alexander Aprelev's avatar Alexander Aprelev Committed by GitHub

Serialize expression compilation requests (#18618)

* Serialize compilation requests.
parent 39f0076c
......@@ -186,6 +186,51 @@ class KernelCompiler {
}
}
/// Class that allows to serialize compilation requests to the compiler.
abstract class _CompilationRequest {
Completer<CompilerOutput> completer;
_CompilationRequest(this.completer);
Future<CompilerOutput> _run(ResidentCompiler compiler);
Future<void> run(ResidentCompiler compiler) async {
completer.complete(await _run(compiler));
}
}
class _RecompileRequest extends _CompilationRequest {
_RecompileRequest(Completer<CompilerOutput> completer, this.mainPath,
this.invalidatedFiles, this.outputPath, this.packagesFilePath) :
super(completer);
String mainPath;
List<String> invalidatedFiles;
String outputPath;
String packagesFilePath;
@override
Future<CompilerOutput> _run(ResidentCompiler compiler) async =>
compiler._recompile(this);
}
class _CompileExpressionRequest extends _CompilationRequest {
_CompileExpressionRequest(Completer<CompilerOutput> completer, this.expression, this.definitions,
this.typeDefinitions, this.libraryUri, this.klass, this.isStatic) :
super(completer);
String expression;
List<String> definitions;
List<String> typeDefinitions;
String libraryUri;
String klass;
bool isStatic;
@override
Future<CompilerOutput> _run(ResidentCompiler compiler) async =>
compiler._compileExpression(this);
}
/// Wrapper around incremental frontend server compiler, that communicates with
/// server via stdin/stdout.
///
......@@ -200,7 +245,8 @@ class ResidentCompiler {
_packagesPath = packagesPath,
_fileSystemRoots = fileSystemRoots,
_fileSystemScheme = fileSystemScheme,
_stdoutHandler = new _StdoutHandler(consumer: compilerMessageConsumer) {
_stdoutHandler = new _StdoutHandler(consumer: compilerMessageConsumer),
_controller = new StreamController<_CompilationRequest>() {
// This is a URI, not a file path, so the forward slash is correct even on Windows.
if (!_sdkRoot.endsWith('/'))
_sdkRoot = '$_sdkRoot/';
......@@ -214,6 +260,8 @@ class ResidentCompiler {
Process _server;
final _StdoutHandler _stdoutHandler;
final StreamController<_CompilationRequest> _controller;
/// If invoked for the first time, it compiles Dart script identified by
/// [mainPath], [invalidatedFiles] list is ignored.
/// On successive runs [invalidatedFiles] indicates which files need to be
......@@ -223,16 +271,30 @@ class ResidentCompiler {
/// null is returned.
Future<CompilerOutput> recompile(String mainPath, List<String> invalidatedFiles,
{String outputPath, String packagesFilePath}) async {
if (!_controller.hasListener) {
_controller.stream.listen(_handleCompilationRequest);
}
final Completer<CompilerOutput> completer = new Completer<CompilerOutput>();
_controller.add(
new _RecompileRequest(completer, mainPath, invalidatedFiles, outputPath, packagesFilePath)
);
return completer.future;
}
Future<CompilerOutput> _recompile(_RecompileRequest request) 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)
return _compile(_mapFilename(mainPath), outputPath, _mapFilename(packagesFilePath));
if (_server == null) {
return _compile(_mapFilename(request.mainPath),
request.outputPath, _mapFilename(request.packagesFilePath));
}
final String inputKey = new Uuid().generateV4();
_server.stdin.writeln('recompile ${mainPath != null ? _mapFilename(mainPath) + " ": ""}$inputKey');
for (String fileUri in invalidatedFiles) {
_server.stdin.writeln('recompile ${request.mainPath != null ? _mapFilename(request.mainPath) + " ": ""}$inputKey');
for (String fileUri in request.invalidatedFiles) {
_server.stdin.writeln(_mapFileUri(fileUri));
}
_server.stdin.writeln(inputKey);
......@@ -240,6 +302,23 @@ class ResidentCompiler {
return _stdoutHandler.compilerOutput.future;
}
final List<_CompilationRequest> compilationQueue = <_CompilationRequest>[];
void _handleCompilationRequest(_CompilationRequest request) async {
final bool isEmpty = compilationQueue.isEmpty;
compilationQueue.add(request);
// Only trigger processing if queue was empty - i.e. no other requests
// are currently being processed. This effectively enforces "one
// compilation request at a time".
if (isEmpty) {
while (compilationQueue.isNotEmpty) {
final _CompilationRequest request = compilationQueue.first;
await request.run(this);
compilationQueue.removeAt(0);
}
}
}
Future<CompilerOutput> _compile(String scriptFilename, String outputPath,
String packagesFilePath) async {
final String frontendServer = artifacts.getArtifactPath(
......@@ -301,6 +380,20 @@ class ResidentCompiler {
Future<CompilerOutput> compileExpression(String expression, List<String> definitions,
List<String> typeDefinitions, String libraryUri, String klass, bool isStatic) {
if (!_controller.hasListener) {
_controller.stream.listen(_handleCompilationRequest);
}
final Completer<CompilerOutput> completer = new Completer<CompilerOutput>();
_controller.add(
new _CompileExpressionRequest(
completer, expression, definitions, typeDefinitions, libraryUri, klass, isStatic)
);
return completer.future;
}
Future<CompilerOutput> _compileExpression(
_CompileExpressionRequest request) async {
_stdoutHandler.reset();
// 'compile-expression' should be invoked after compiler has been started,
......@@ -310,14 +403,14 @@ class ResidentCompiler {
final String inputKey = new Uuid().generateV4();
_server.stdin.writeln('compile-expression $inputKey');
_server.stdin.writeln(expression);
definitions?.forEach(_server.stdin.writeln);
_server.stdin.writeln(request.expression);
request.definitions?.forEach(_server.stdin.writeln);
_server.stdin.writeln(inputKey);
typeDefinitions?.forEach(_server.stdin.writeln);
request.typeDefinitions?.forEach(_server.stdin.writeln);
_server.stdin.writeln(inputKey);
_server.stdin.writeln(libraryUri ?? '');
_server.stdin.writeln(klass ?? '');
_server.stdin.writeln(isStatic ?? false);
_server.stdin.writeln(request.libraryUri ?? '');
_server.stdin.writeln(request.klass ?? '');
_server.stdin.writeln(request.isStatic ?? false);
return _stdoutHandler.compilerOutput.future;
}
......
......@@ -276,9 +276,9 @@ void main() {
'result abc\nline1\nline2\nabc /path/to/main.dart.dill 0\n'
)));
final CompilerOutput output = await generator.recompile(
generator.recompile(
'/path/to/main.dart', null /* invalidatedFiles */
);
).then((CompilerOutput output) {
expect(mockFrontendServerStdIn.getAndClear(),
'compile /path/to/main.dart\n');
verifyNoMoreInteractions(mockFrontendServerStdIn);
......@@ -286,15 +286,78 @@ void main() {
equals('compiler message: line1\ncompiler message: line2\n'));
expect(output.outputFilename, equals('/path/to/main.dart.dill'));
compileExpressionResponseCompleter.complete(new Future<List<int>>.value(utf8.encode(
compileExpressionResponseCompleter.complete(
new Future<List<int>>.value(utf8.encode(
'result def\nline1\nline2\ndef /path/to/main.dart.dill.incremental 0\n'
)));
final CompilerOutput outputExpression = await generator.compileExpression(
'2+2', null, null, null, null, false);
generator.compileExpression(
'2+2', null, null, null, null, false).then(
(CompilerOutput outputExpression) {
expect(outputExpression, isNotNull);
expect(outputExpression.outputFilename, equals('/path/to/main.dart.dill.incremental'));
expect(outputExpression.errorCount, 0);
}
);
});
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
});
testUsingContext('compile expressions without awaiting', () async {
final BufferLogger logger = context[Logger];
final Completer<List<int>> compileResponseCompleter = new Completer<List<int>>();
final Completer<List<int>> compileExpressionResponseCompleter1 = new Completer<List<int>>();
final Completer<List<int>> compileExpressionResponseCompleter2 = new Completer<List<int>>();
when(mockFrontendServer.stdout)
.thenAnswer((Invocation invocation) =>
new Stream<List<int>>.fromFutures(
<Future<List<int>>>[
compileResponseCompleter.future,
compileExpressionResponseCompleter1.future,
compileExpressionResponseCompleter2.future,
]));
generator.recompile(
'/path/to/main.dart', null /* invalidatedFiles */
).then((CompilerOutput outputCompile) {
expect(logger.errorText,
equals('compiler message: line1\ncompiler message: line2\n'));
expect(outputCompile.outputFilename, equals('/path/to/main.dart.dill'));
compileExpressionResponseCompleter1.complete(new Future<List<int>>.value(utf8.encode(
'result def\nline1\nline2\ndef /path/to/main.dart.dill.incremental 0\n'
)));
});
final Completer<bool> lastExpressionCompleted = new Completer<bool>();
generator.compileExpression('0+1', null, null, null, null, false).then(
(CompilerOutput outputExpression) {
expect(outputExpression, isNotNull);
expect(outputExpression.outputFilename,
equals('/path/to/main.dart.dill.incremental'));
expect(outputExpression.errorCount, 0);
compileExpressionResponseCompleter2.complete(new Future<List<int>>.value(utf8.encode(
'result def\nline1\nline2\ndef /path/to/main.dart.dill.incremental 0\n'
)));
});
generator.compileExpression('1+1', null, null, null, null, false).then(
(CompilerOutput outputExpression) {
expect(outputExpression, isNotNull);
expect(outputExpression.outputFilename,
equals('/path/to/main.dart.dill.incremental'));
expect(outputExpression.errorCount, 0);
lastExpressionCompleted.complete(true);
});
compileResponseCompleter.complete(new Future<List<int>>.value(utf8.encode(
'result abc\nline1\nline2\nabc /path/to/main.dart.dill 0\n'
)));
expect(await lastExpressionCompleted.future, isTrue);
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
});
......
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