[ DDS ] Update DDS launch sites to assume DDS process closes stderr

Removes risk of DDS connection information being split across two stream
events, causing JSON decoding to fail.

Also updates DDS to close stderr, even in the error case.

TEST=Existing service and dartdev tests

Change-Id: I5cceab899aac1fa63bd7578dd658b34096722bd3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/371000
Reviewed-by: Derek Xu <derekx@google.com>
This commit is contained in:
Ben Konyi 2024-06-11 18:41:01 +00:00
parent b640dffb0a
commit cf9623f3d9
3 changed files with 57 additions and 38 deletions

View file

@ -46,12 +46,17 @@ class DDSRunner {
], ],
mode: ProcessStartMode.detachedWithStdio, mode: ProcessStartMode.detachedWithStdio,
); );
final completer = Completer<void>();
// NOTE: update pkg/dartdev/lib/src/commands/run.dart if this message
// is changed to ensure consistency.
const devToolsMessagePrefix = const devToolsMessagePrefix =
'The Dart DevTools debugger and profiler is available at:'; 'The Dart DevTools debugger and profiler is available at:';
if (debugDds) { if (debugDds) {
late final StreamSubscription stdoutSub; late final StreamSubscription stdoutSub;
stdoutSub = process.stdout.transform(utf8.decoder).listen((event) { stdoutSub = process.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((event) {
if (event.startsWith(devToolsMessagePrefix)) { if (event.startsWith(devToolsMessagePrefix)) {
final ddsDebuggingUri = event.split(' ').last; final ddsDebuggingUri = event.split(' ').last;
print( print(
@ -61,21 +66,27 @@ class DDSRunner {
} }
}); });
} }
late final StreamSubscription stderrSub;
stderrSub = process.stderr.transform(utf8.decoder).listen((event) { // DDS will close stderr once it's finished launching.
final result = json.decode(event) as Map<String, dynamic>; final launchResult = await process.stderr.transform(utf8.decoder).join();
final state = result['state'];
if (state == 'started') { void printError(String details) => stderr.writeln(
if (result.containsKey('devToolsUri')) { 'Could not start the VM service:\n$details',
final devToolsUri = result['devToolsUri']; );
try {
final result = json.decode(launchResult);
if (result
case {
'state': 'started',
'ddsUri': final String ddsUriStr,
}) {
ddsUri = Uri.parse(ddsUriStr);
if (result case {'devToolsUri': String devToolsUri}) {
print('$devToolsMessagePrefix $devToolsUri'); print('$devToolsMessagePrefix $devToolsUri');
} }
ddsUri = Uri.parse(result['ddsUri']);
stderrSub.cancel();
completer.complete();
} else { } else {
stderrSub.cancel(); final error = result['error'] ?? result;
final error = result['error'] ?? event;
final stacktrace = result['stacktrace'] ?? ''; final stacktrace = result['stacktrace'] ?? '';
String message = 'Could not start the VM service: '; String message = 'Could not start the VM service: ';
if (error.contains('Failed to create server socket')) { if (error.contains('Failed to create server socket')) {
@ -83,15 +94,15 @@ class DDSRunner {
} else { } else {
message += '$error\n$stacktrace\n'; message += '$error\n$stacktrace\n';
} }
completer.completeError(message); printError(message);
return false;
} }
}); } catch (_) {
try { // Malformed JSON was likely encountered, so output the entirety of
await completer.future; // stderr in the error message.
return true; printError(launchResult);
} catch (e) {
stderr.write(e);
return false; return false;
} }
return true;
} }
} }

View file

@ -131,6 +131,10 @@ ${argParser.usage}
stderr.close(); stderr.close();
} catch (e, st) { } catch (e, st) {
writeErrorResponse(e, st); writeErrorResponse(e, st);
} finally {
// Always close stderr to notify tooling that DDS has finished writing
// launch details.
await stderr.close();
} }
} }

View file

@ -114,12 +114,20 @@ class _DebuggingSession {
], ],
mode: ProcessStartMode.detachedWithStdio, mode: ProcessStartMode.detachedWithStdio,
); );
final completer = Completer<void>();
late StreamSubscription<String> stderrSub; // DDS will close stderr once it's finished launching.
stderrSub = _process!.stderr.transform(utf8.decoder).listen((event) { final launchResult = await _process!.stderr.transform(utf8.decoder).join();
final result = json.decode(event) as Map<String, dynamic>;
final state = result['state']; void printError(String details) => stderr.writeln(
if (state == 'started') { 'Could not start the VM service:\n$details',
);
try {
final result = json.decode(launchResult);
if (result
case {
'state': 'started',
}) {
if (result case {'devToolsUri': String devToolsUri}) { if (result case {'devToolsUri': String devToolsUri}) {
// NOTE: update pkg/dartdev/lib/src/commands/run.dart if this message // NOTE: update pkg/dartdev/lib/src/commands/run.dart if this message
// is changed to ensure consistency. // is changed to ensure consistency.
@ -135,21 +143,17 @@ class _DebuggingSession {
} when _printDtd) { } when _printDtd) {
print('The Dart Tooling Daemon (DTD) is available at: $dtdUri'); print('The Dart Tooling Daemon (DTD) is available at: $dtdUri');
} }
stderrSub.cancel();
completer.complete();
} else { } else {
final error = result['error'] ?? event; printError(result['error'] ?? result);
stderrSub.cancel(); return false;
completer.completeError('Could not start the VM service:\n$error\n');
} }
}); } catch (_) {
try { // Malformed JSON was likely encountered, so output the entirety of
await completer.future; // stderr in the error message.
return true; printError(launchResult);
} catch (e) {
stderr.write(e);
return false; return false;
} }
return true;
} }
void shutdown() => _process!.kill(); void shutdown() => _process!.kill();