Skip to content

Commit cffe4ac

Browse files
committed
Merge branch 'master' of github.com:flutter/devtools into offline2
2 parents b7f0bb4 + b9ae32e commit cffe4ac

File tree

11 files changed

+144
-33
lines changed

11 files changed

+144
-33
lines changed

packages/devtools_app/integration_test/test/live_connection/service_connection_test.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:devtools_app/src/service/service_extensions.dart' as extensions;
1111
import 'package:devtools_app_shared/service.dart';
1212
import 'package:devtools_app_shared/ui.dart';
1313
import 'package:devtools_app_shared/utils.dart';
14+
import 'package:devtools_shared/devtools_shared.dart';
1415
import 'package:devtools_test/helpers.dart';
1516
import 'package:devtools_test/integration_test.dart';
1617
import 'package:flutter/widgets.dart';
@@ -398,8 +399,8 @@ Future<void> _serviceExtensionAvailable(String extensionName) async {
398399

399400
final completer = Completer<void>();
400401
void listener() {
401-
if (listenable.value && !completer.isCompleted) {
402-
completer.complete();
402+
if (listenable.value) {
403+
completer.safeComplete();
403404
}
404405
}
405406

packages/devtools_app/lib/src/extensions/embedded/_view_web.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:devtools_app_shared/ui.dart';
99
import 'package:devtools_app_shared/utils.dart';
1010
import 'package:devtools_extensions/api.dart';
1111
import 'package:devtools_extensions/utils.dart';
12+
import 'package:devtools_shared/devtools_shared.dart';
1213
import 'package:flutter/material.dart';
1314
import 'package:web/web.dart';
1415

@@ -268,9 +269,7 @@ class _ExtensionIFrameController extends DisposableController
268269

269270
switch (event.type) {
270271
case DevToolsExtensionEventType.pong:
271-
if (!_extensionHandlerReady.isCompleted) {
272-
_extensionHandlerReady.complete();
273-
}
272+
_extensionHandlerReady.safeComplete();
274273
break;
275274
case DevToolsExtensionEventType.vmServiceConnection:
276275
updateVmServiceConnection(

packages/devtools_app/lib/src/service/vm_service_wrapper.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:dap/dap.dart' as dap;
1313
import 'package:dds_service_extensions/dap.dart';
1414
import 'package:dds_service_extensions/dds_service_extensions.dart';
1515
import 'package:devtools_app_shared/service.dart';
16+
import 'package:devtools_shared/devtools_shared.dart';
1617
import 'package:flutter/foundation.dart';
1718
import 'package:logging/logging.dart';
1819
import 'package:vm_service/vm_service.dart';
@@ -380,8 +381,8 @@ class VmServiceWrapper extends VmService {
380381

381382
void futureComplete() {
382383
activeFutures.remove(trackedFuture);
383-
if (activeFutures.isEmpty && !_allFuturesCompleter.isCompleted) {
384-
_allFuturesCompleter.complete(true);
384+
if (activeFutures.isEmpty) {
385+
_allFuturesCompleter.safeComplete(true);
385386
}
386387
}
387388

packages/devtools_app/test/legacy_integration_tests/integration.dart

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'dart:async';
99
import 'dart:convert';
1010
import 'dart:io';
1111

12+
import 'package:devtools_shared/devtools_shared.dart';
1213
import 'package:devtools_shared/devtools_test_utils.dart';
1314
import 'package:flutter_test/flutter_test.dart';
1415
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
@@ -302,12 +303,12 @@ class WebBuildFixture {
302303

303304
// Serving `web` on http://localhost:8080
304305
if (line.contains('Serving `web`')) {
305-
if (!hasUrl.isCompleted) {
306-
final String url = line.substring(line.indexOf('http://'));
307-
hasUrl.complete(url);
308-
} else {
309-
print('Ignoring "Serving..." notification because already completed');
310-
}
306+
hasUrl.safeComplete(
307+
line.substring(line.indexOf('http://')),
308+
() => print(
309+
'Ignoring "Serving..." notification because already completed',
310+
),
311+
);
311312
}
312313
});
313314

packages/devtools_app_shared/lib/src/service/isolate_state.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'dart:async';
66
import 'dart:core';
77

8+
import 'package:devtools_shared/devtools_shared.dart';
89
import 'package:flutter/foundation.dart';
910
import 'package:meta/meta.dart';
1011
import 'package:vm_service/vm_service.dart' hide Error;
@@ -43,11 +44,10 @@ class IsolateState {
4344

4445
void dispose() {
4546
_isolateNow = null;
46-
if (!_isolateLoadCompleter.isCompleted) {
47-
_isolateLoadCompleter.complete(null);
48-
} else {
49-
_isolateLoadCompleter = Completer()..complete(null);
50-
}
47+
_isolateLoadCompleter.safeComplete(
48+
null,
49+
() => _isolateLoadCompleter = Completer()..complete(null),
50+
);
5151
}
5252

5353
void handleDebugEvent(String? kind) {

packages/devtools_app_shared/lib/src/service/service_extension_manager.dart

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'dart:async';
66
import 'dart:core';
77

8+
import 'package:devtools_shared/devtools_shared.dart';
89
import 'package:flutter/foundation.dart';
910
import 'package:logging/logging.dart';
1011
import 'package:vm_service/vm_service.dart' hide Error;
@@ -436,11 +437,7 @@ final class ServiceExtensionManager with DisposerMixin {
436437
// service extensions that might be registered.
437438
_performActionAndClearMap<Completer<bool>>(
438439
_maybeRegisteringServiceExtensions,
439-
action: (completer) {
440-
if (!completer.isCompleted) {
441-
completer.complete(false);
442-
}
443-
},
440+
action: (completer) => completer.safeComplete(false),
444441
);
445442

446443
_performActionAndClearMap<ValueNotifier<bool>>(

packages/devtools_shared/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# 9.1.0-wip
22
* Return valid extensions from the `apiServeAvailableExtensions` endpoint even when
33
an exception is thrown.
4+
* Add utility extension methods on `Completer`: `safeComplete` and `safeCompleteError`.
45

56
# 9.0.1
67
* Restructure `devtools_extensions.dart` and `devtools_extensions_io.dart` libraries.

packages/devtools_shared/lib/devtools_shared.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ export 'src/utils/compare.dart';
1515
export 'src/utils/file_utils.dart';
1616
export 'src/utils/retry.dart';
1717
export 'src/utils/semantic_version.dart';
18+
export 'src/utils/utils.dart';

packages/devtools_shared/lib/src/service/service.dart

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:vm_service/vm_service.dart';
1212
import 'package:web_socket_channel/web_socket_channel.dart';
1313

1414
import '../sse/sse_shim.dart';
15+
import '../utils/utils.dart';
1516

1617
Future<T> _connectWithSse<T extends VmService>({
1718
required Uri uri,
@@ -87,11 +88,7 @@ Future<T> connect<T extends VmService>({
8788
}) {
8889
final connectedCompleter = Completer<T>();
8990

90-
void onError(Object? error) {
91-
if (!connectedCompleter.isCompleted) {
92-
connectedCompleter.completeError(error!);
93-
}
94-
}
91+
void onError(Object? error) => connectedCompleter.safeCompleteError(error!);
9592

9693
// Connects to a VM Service but does not verify the connection was fully
9794
// successful.
@@ -118,11 +115,7 @@ Future<T> connect<T extends VmService>({
118115
}
119116

120117
connectHelper().then(
121-
(service) {
122-
if (!connectedCompleter.isCompleted) {
123-
connectedCompleter.complete(service);
124-
}
125-
},
118+
(service) => connectedCompleter.safeComplete(service),
126119
onError: onError,
127120
);
128121
finishedCompleter.future.then((_) {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2024 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
7+
extension CompleterExtension<T> on Completer<T> {
8+
/// Completes this completer with optional [value] only if it has not already
9+
/// been completed.
10+
///
11+
/// If the completer has already been completed, [orElse] will be called if it
12+
/// is not null,
13+
void safeComplete([T? value, void Function()? orElse]) {
14+
if (!isCompleted) {
15+
complete(value);
16+
} else {
17+
orElse?.call();
18+
}
19+
}
20+
21+
/// Completes this completer with [error] and an optional [stackTrace] only if
22+
/// it has not already been completed.
23+
void safeCompleteError(Object error, [StackTrace? stackTrace]) {
24+
if (!isCompleted) {
25+
completeError(error, stackTrace);
26+
}
27+
}
28+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2024 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
7+
import 'package:devtools_shared/devtools_shared.dart';
8+
import 'package:test/test.dart';
9+
10+
void main() {
11+
group('CompleterExtension', () {
12+
late Completer<int> completer;
13+
14+
setUp(() {
15+
completer = Completer<int>();
16+
});
17+
18+
group('safeComplete', () {
19+
int? value;
20+
setUp(() {
21+
value = null;
22+
unawaited(completer.future.then((v) => value = v));
23+
});
24+
25+
test('completes successfully', () async {
26+
expect(completer.isCompleted, false);
27+
expect(value, isNull);
28+
29+
completer.safeComplete(5);
30+
await completer.future;
31+
expect(completer.isCompleted, true);
32+
expect(value, 5);
33+
});
34+
35+
test('does not complete if already completed', () async {
36+
expect(completer.isCompleted, false);
37+
expect(value, isNull);
38+
completer.complete(1);
39+
await completer.future;
40+
41+
expect(completer.isCompleted, true);
42+
expect(value, 1);
43+
44+
completer.safeComplete(5);
45+
expect(completer.isCompleted, true);
46+
expect(value, 1);
47+
48+
String? elseValue;
49+
completer.safeComplete(3, () => elseValue = 'hit orElse');
50+
expect(completer.isCompleted, true);
51+
expect(value, 1);
52+
expect(elseValue, 'hit orElse');
53+
});
54+
});
55+
56+
group('safeCompleteError', () {
57+
late Future<int> errorFuture;
58+
({Object error, StackTrace? st})? error;
59+
60+
setUp(() {
61+
unawaited(
62+
errorFuture = completer.future.onError((e, st) {
63+
error = (error: e!, st: st);
64+
return -1;
65+
}),
66+
);
67+
});
68+
69+
test('completes successfully', () async {
70+
completer.safeCompleteError('This is an error');
71+
await errorFuture;
72+
expect(completer.isCompleted, true);
73+
expect(error?.error, 'This is an error');
74+
});
75+
76+
test('does not complete if already completed', () async {
77+
completer.safeCompleteError('This is an error');
78+
await errorFuture;
79+
expect(completer.isCompleted, true);
80+
expect(error?.error, 'This is an error');
81+
82+
completer.safeCompleteError('This is a different error');
83+
await errorFuture;
84+
expect(completer.isCompleted, true);
85+
expect(error?.error, 'This is an error');
86+
});
87+
});
88+
});
89+
}

0 commit comments

Comments
 (0)