Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit ce9a42f

Browse files
aamCommit Queue
authored andcommitted
[vm/vmservice] Ensure only one vmservice http server is launched.
When many concurrent requests come in, it was possible to leak http servers, leading to hanging dart vms. Fixes dart-lang/sdk#50389 TEST=developer_server_launch_test Change-Id: Icc59987a1a60af5ec72e0cb1ca7b43dea7f0c5e3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/268181 Reviewed-by: Ben Konyi <bkonyi@google.com> Commit-Queue: Alexander Aprelev <aam@google.com>
1 parent 51ab46f commit ce9a42f

File tree

3 files changed

+75
-1
lines changed

3 files changed

+75
-1
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
//
5+
// This test verifies that concurrent requests to start service server still
6+
// result only in one server being brought up.
7+
8+
import 'dart:async';
9+
import 'dart:developer';
10+
import 'package:expect/expect.dart';
11+
import 'package:observatory/service_io.dart' as S;
12+
import 'test_helper.dart';
13+
14+
main() async {
15+
for (int i = 0; i < 32; i++) {
16+
Service.controlWebServer(enable: true, silenceOutput: true);
17+
}
18+
// Give some time for control messages to arrive to vmservice isolate.
19+
await Future.delayed(Duration(seconds: 2));
20+
// If the program doesn hang on shutdown, the test passes.
21+
// Program hanging means that more than one http server was launched,
22+
// but only one gets closed.
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
//
5+
// This test verifies that concurrent requests to start service server still
6+
// result only in one server being brought up.
7+
8+
import 'dart:async';
9+
import 'dart:developer';
10+
import 'package:expect/expect.dart';
11+
import 'package:observatory/service_io.dart' as S;
12+
import 'test_helper.dart';
13+
14+
main() async {
15+
for (int i = 0; i < 32; i++) {
16+
Service.controlWebServer(enable: true, silenceOutput: true);
17+
}
18+
// Give some time for control messages to arrive to vmservice isolate.
19+
await Future.delayed(Duration(seconds: 2));
20+
// If the program doesn hang on shutdown, the test passes.
21+
// Program hanging means that more than one http server was launched,
22+
// but only one gets closed.
23+
}

sdk/lib/_internal/vm/bin/vmservice_server.dart

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ class Server {
150150
bool get running => _server != null;
151151
bool acceptNewWebSocketConnections = true;
152152
int _port = -1;
153+
// Ensures only one server is started even if many requests to launch
154+
// the server come in concurrently.
155+
Completer<bool>? _startingCompleter;
153156

154157
/// Returns the server address including the auth token.
155158
Uri? get serverAddress {
@@ -401,6 +404,19 @@ class Server {
401404
// Already running.
402405
return this;
403406
}
407+
408+
{
409+
final startingCompleter = _startingCompleter;
410+
if (startingCompleter != null) {
411+
if (!startingCompleter.isCompleted) {
412+
await startingCompleter.future;
413+
}
414+
return this;
415+
}
416+
}
417+
418+
final startingCompleter = Completer<bool>();
419+
_startingCompleter = startingCompleter;
404420
// Startup HTTP server.
405421
Future<bool> startServer() async {
406422
try {
@@ -431,11 +447,13 @@ class Server {
431447
}
432448

433449
if (!(await startServer())) {
450+
startingCompleter.complete(true);
434451
return this;
435452
}
436453
if (_service.isExiting) {
437454
serverPrint('Dart VM service HTTP server exiting before listening as '
438455
'vm service has received exit request\n');
456+
startingCompleter.complete(true);
439457
await shutdown(true);
440458
return this;
441459
}
@@ -447,6 +465,7 @@ class Server {
447465
// Server is up and running.
448466
_notifyServerState(serverAddress.toString());
449467
onServerAddressChange('$serverAddress');
468+
startingCompleter.complete(true);
450469
return this;
451470
}
452471

@@ -481,7 +500,14 @@ class Server {
481500
return serverLocal.close(force: force);
482501
}
483502

484-
Future<Server> shutdown(bool forced) {
503+
Future<Server> shutdown(bool forced) async {
504+
// If start is pending, wait for it to complete.
505+
if (_startingCompleter != null) {
506+
if (!_startingCompleter!.isCompleted) {
507+
await _startingCompleter!.future;
508+
}
509+
}
510+
485511
if (_server == null) {
486512
// Not started.
487513
return Future.value(this);
@@ -492,11 +518,13 @@ class Server {
492518
return cleanup(forced).then((_) {
493519
serverPrint('Dart VM service no longer listening on $oldServerAddress');
494520
_server = null;
521+
_startingCompleter = null;
495522
_notifyServerState('');
496523
onServerAddressChange(null);
497524
return this;
498525
}).catchError((e, st) {
499526
_server = null;
527+
_startingCompleter = null;
500528
serverPrint('Could not shutdown Dart VM service HTTP server:\n$e\n$st\n');
501529
_notifyServerState('');
502530
onServerAddressChange(null);

0 commit comments

Comments
 (0)