0e535e710b
The resumer in `resume_shutdown_race_test.dart` was running in a tight `do-while` loop, hammering the VM server with `getVM` and `getIsolate` requests to check if all isolates are paused at exit. During VM shutdown, this rapid sequence of requests could cause socket exhaustion, especially on Windows, or trigger transient connection errors. This is fixed by adding a 10ms delay using `Future.delayed` at the end of each iteration, and wrapping the loop's HTTP request block in a `try-catch` block to gracefully log and ignore transient connection or request errors during VM shutdown. Change-Id: I1bfcad7505254a1feb59dfb654421547efc02d46 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/508720 Commit-Queue: Ben Konyi <bkonyi@google.com> Reviewed-by: Mark Zhou <markzipan@google.com> Auto-Submit: Ben Konyi <bkonyi@google.com> Commit-Queue: Mark Zhou <markzipan@google.com>
94 lines
2.8 KiB
Dart
94 lines
2.8 KiB
Dart
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
// VMOptions=--pause-isolates-on-exit --enable-vm-service=0 --disable-service-auth-codes
|
|
|
|
// See b/271314180.
|
|
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:developer';
|
|
import 'dart:io';
|
|
import 'dart:isolate';
|
|
|
|
const childCount = 4;
|
|
|
|
void child(i) {
|
|
print('Child $i');
|
|
// Paused-at-exit.
|
|
}
|
|
|
|
void main() {
|
|
for (int i = 0; i < childCount; i++) {
|
|
Isolate.spawn(child, i);
|
|
}
|
|
Isolate.spawn(resumer, null);
|
|
print('Parent');
|
|
// Paused-at-exit.
|
|
}
|
|
|
|
Future<Map<String, dynamic>> get(
|
|
String method,
|
|
Map<String, dynamic> arguments,
|
|
) async {
|
|
final info = await Service.getInfo();
|
|
final uri = info.serverUri!.replace(path: method, queryParameters: arguments);
|
|
final client = HttpClient();
|
|
try {
|
|
final request = await client.getUrl(uri);
|
|
final response = await request.close();
|
|
final string = await response.transform(utf8.decoder).join();
|
|
return jsonDecode(string);
|
|
} finally {
|
|
client.close();
|
|
}
|
|
}
|
|
|
|
Future<Never> resumer(_) async {
|
|
try {
|
|
// Wait for the main isolate and children to all be paused at exit.
|
|
final paused = <String>[];
|
|
do {
|
|
try {
|
|
paused.clear();
|
|
final vmResult = await get('getVM', {});
|
|
final vm = vmResult['result'];
|
|
if (vm != null) {
|
|
for (Map<String, dynamic> isolate in vm['isolates']) {
|
|
final id = isolate['id'];
|
|
final isolateResult = await get('getIsolate', {'isolateId': id});
|
|
isolate = isolateResult['result'] ?? {};
|
|
if ((isolate['pauseEvent'] != null) &&
|
|
(isolate['pauseEvent']['kind'] == 'PauseExit')) {
|
|
paused.add(id);
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
print('Transient error in resumer: $e');
|
|
}
|
|
await Future.delayed(const Duration(milliseconds: 10));
|
|
} while (paused.length != childCount + 1);
|
|
|
|
// Resume the main isolate and children. When the main isolate resumes, it
|
|
// will exit and trigger VM shutdown. The VM shutdown will send the OOB kill
|
|
// message to children and so race with the resume message. No matter how
|
|
// the race resolves, the children should exit and the VM shutdown should
|
|
// not hang with
|
|
// Attempt:138 waiting for isolate child to check in
|
|
// ...
|
|
for (final id in paused) {
|
|
await get('resume', {'isolateId': id}).then((v) => print(v));
|
|
}
|
|
} catch (e, st) {
|
|
print(e);
|
|
print(st);
|
|
rethrow;
|
|
}
|
|
|
|
// This isolate itself will be paused-at-exit with no resume message coming,
|
|
// but should exit because of the VM shutdown.
|
|
throw StateError('Unreachable');
|
|
}
|