Skip to content

Commit 3dc9d8d

Browse files
derekxu16Commit Queue
authored andcommitted
[VM/Service] Add getQueuedMicrotasks RPC
TEST=pkg/vm_service/test/get_queued_microtasks_rpc_test CoreLibraryReviewExempt: This CL does not include any core library API changes, only VM Service implementation changes within sdk/lib/vmservice/. Change-Id: Ie5488f498e4d0f3d201e3f31423fd5029b74a726 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/425160 Commit-Queue: Derek Xu <[email protected]> Reviewed-by: Ben Konyi <[email protected]>
1 parent 0ead16d commit 3dc9d8d

File tree

20 files changed

+340
-11
lines changed

20 files changed

+340
-11
lines changed

pkg/dds/lib/src/rpc_error_codes.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ abstract class RpcErrorCodes {
4343
static const kExpressionCompilationError = 113;
4444

4545
// static const kInvalidTimelineRequest = 114;
46+
// static const kCannotGetQueuedMicrotasks = 115;
4647
static const kCustomStreamDoesNotExist = 130;
4748
static const kCoreStreamNotAllowed = 131;
4849

pkg/pkg.status

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ vm_service/test/get_allocation_traces_test: SkipByDesign # Debugger is disabled
145145
vm_service/test/get_instances_as_*: SkipByDesign # Debugger is disabled in AOT mode
146146
vm_service/test/get_instances_rpc_test: SkipByDesign # Debugger is disabled in AOT mode.
147147
vm_service/test/get_object_rpc_test: SkipByDesign # Debugger is disabled in AOT mode.
148+
vm_service/test/get_queued_microtasks_rpc_test: SkipByDesign # In AOT mode, the debugger is disabled, and there's no other way to pause the testee at a point when the microtask queue is non-empty.
148149
vm_service/test/get_source_report_const_coverage_test: SkipByDesign # Debugger is disabled in AOT mode.
149150
vm_service/test/get_source_report_test: SkipByDesign # Debugger is disabled in AOT mode.
150151
vm_service/test/get_source_report_with_mixin_test: SkipByDesign # Debugger is disabled in AOT mode.

pkg/vm_service/CHANGELOG.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
## 15.0.1
2-
- Update to version `4.17` of the spec.
1+
## 15.0.1-wip
2+
- Update to version `4.19` of the spec.
33
- Add `Timer` stream.
44
- Add `TimerSignificantlyOverdue` event kind.
55
- Add `details` property to `Event`.
6+
- Add `getQueuedMicrotasks` RPC.
7+
- Add `Microtask` and `QueuedMicrotasks` types.
8+
- Add `RPCErrorKind.kCannotGetQueuedMicrotasks`.
69

710
## 15.0.0
811
- Update type of `CodeRef.function` from `FuncRef` to `dynamic` to allow for `NativeFunction`

pkg/vm_service/java/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ src/org/dartlang/vm/service/consumer/PerfettoTimelineConsumer.java
3535
src/org/dartlang/vm/service/consumer/PortListConsumer.java
3636
src/org/dartlang/vm/service/consumer/ProcessMemoryUsageConsumer.java
3737
src/org/dartlang/vm/service/consumer/ProtocolListConsumer.java
38+
src/org/dartlang/vm/service/consumer/QueuedMicrotasksConsumer.java
3839
src/org/dartlang/vm/service/consumer/ReloadSourcesConsumer.java
3940
src/org/dartlang/vm/service/consumer/RemoveBreakpointConsumer.java
4041
src/org/dartlang/vm/service/consumer/RequestHeapSnapshotConsumer.java
@@ -105,6 +106,7 @@ src/org/dartlang/vm/service/element/LogRecord.java
105106
src/org/dartlang/vm/service/element/MapAssociation.java
106107
src/org/dartlang/vm/service/element/MemoryUsage.java
107108
src/org/dartlang/vm/service/element/Message.java
109+
src/org/dartlang/vm/service/element/Microtask.java
108110
src/org/dartlang/vm/service/element/NativeFunction.java
109111
src/org/dartlang/vm/service/element/Null.java
110112
src/org/dartlang/vm/service/element/NullRef.java
@@ -119,6 +121,7 @@ src/org/dartlang/vm/service/element/ProcessMemoryUsage.java
119121
src/org/dartlang/vm/service/element/ProfileFunction.java
120122
src/org/dartlang/vm/service/element/Protocol.java
121123
src/org/dartlang/vm/service/element/ProtocolList.java
124+
src/org/dartlang/vm/service/element/QueuedMicrotasks.java
122125
src/org/dartlang/vm/service/element/ReloadReport.java
123126
src/org/dartlang/vm/service/element/Response.java
124127
src/org/dartlang/vm/service/element/RetainingObject.java
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version=4.18
1+
version=4.19

pkg/vm_service/lib/src/vm_service.dart

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export 'snapshot_graph.dart'
2727
HeapSnapshotObjectNoData,
2828
HeapSnapshotObjectNullData;
2929

30-
const String vmServiceVersion = '4.18.0';
30+
const String vmServiceVersion = '4.19.0';
3131

3232
/// @optional
3333
const String optional = 'optional';
@@ -151,6 +151,7 @@ final _typeFactories = <String, Function>{
151151
'MapAssociation': MapAssociation.parse,
152152
'MemoryUsage': MemoryUsage.parse,
153153
'Message': Message.parse,
154+
'Microtask': Microtask.parse,
154155
'NativeFunction': NativeFunction.parse,
155156
'@Null': NullValRef.parse,
156157
'Null': NullVal.parse,
@@ -165,6 +166,7 @@ final _typeFactories = <String, Function>{
165166
'Protocol': Protocol.parse,
166167
'ProcessMemoryUsage': ProcessMemoryUsage.parse,
167168
'ProcessMemoryItem': ProcessMemoryItem.parse,
169+
'QueuedMicrotasks': QueuedMicrotasks.parse,
168170
'ReloadReport': ReloadReport.parse,
169171
'RetainingObject': RetainingObject.parse,
170172
'RetainingPath': RetainingPath.parse,
@@ -226,6 +228,7 @@ final _methodReturnTypes = <String, List<String>>{
226228
'getPorts': const ['PortList'],
227229
'getRetainingPath': const ['RetainingPath'],
228230
'getProcessMemoryUsage': const ['ProcessMemoryUsage'],
231+
'getQueuedMicrotasks': const ['QueuedMicrotasks'],
229232
'getStack': const ['Stack'],
230233
'getSupportedProtocols': const ['ProtocolList'],
231234
'getSourceReport': const ['SourceReport'],
@@ -1255,6 +1258,28 @@ class VmService {
12551258
Future<ProcessMemoryUsage> getProcessMemoryUsage() =>
12561259
_call('getProcessMemoryUsage');
12571260

1261+
/// The `getQueuedMicrotasks` RPC returns a snapshot containing information
1262+
/// about the microtasks that were queued in the specified isolate when the
1263+
/// snapshot was taken.
1264+
///
1265+
/// If the VM was not started with the flag `--profile-microtasks`, this RPC
1266+
/// will return [RPCError] 100 "Feature is disabled".
1267+
///
1268+
/// If an exception has gone unhandled in the specified isolate, this RPC will
1269+
/// return [RPCError] 115 "Cannot get queued microtasks".
1270+
///
1271+
/// If custom `dart:async` `Zone`s are used to redirect microtasks to be
1272+
/// queued elsewhere than the root `dart:async` `Zone`'s microtask queue,
1273+
/// information about those redirected microtasks will not be returned by this
1274+
/// function.
1275+
///
1276+
/// If `isolateId` refers to an isolate that has exited, then the `Collected`
1277+
/// [Sentinel] will be returned.
1278+
///
1279+
/// See [QueuedMicrotasks].
1280+
Future<QueuedMicrotasks> getQueuedMicrotasks(String isolateId) =>
1281+
_call('getQueuedMicrotasks', {'isolateId': isolateId});
1282+
12581283
/// The `getStack` RPC is used to retrieve the current execution stack and
12591284
/// message queue for an isolate. The isolate does not need to be paused.
12601285
///
@@ -2150,6 +2175,11 @@ enum RPCErrorKind {
21502175
message:
21512176
'Invalid timeline request for the current timeline configuration'),
21522177

2178+
/// Information about the microtasks queued in the specified isolate cannot be
2179+
/// retrieved.
2180+
kCannotGetQueuedMicrotasks(
2181+
code: 115, message: 'Cannot get queued microtasks'),
2182+
21532183
/// The custom stream does not exist.
21542184
kCustomStreamDoesNotExist(code: 130, message: 'Custom stream does not exist'),
21552185

@@ -6654,6 +6684,51 @@ class Message extends Response {
66546684
'size: $size]';
66556685
}
66566686

6687+
/// A `Microtask` represents a Dart microtask.
6688+
///
6689+
/// See [QueuedMicrotasks].
6690+
class Microtask extends Response {
6691+
static Microtask? parse(Map<String, dynamic>? json) =>
6692+
json == null ? null : Microtask._fromJson(json);
6693+
6694+
/// The numeric ID for this microtask.
6695+
///
6696+
/// This ID uniquely identifies a microtask within an isolate.
6697+
int? id;
6698+
6699+
/// A stack trace that was collected when this microtask was enqueued.
6700+
String? stackTrace;
6701+
6702+
Microtask({
6703+
this.id,
6704+
this.stackTrace,
6705+
});
6706+
6707+
Microtask._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
6708+
id = json['id'] ?? -1;
6709+
stackTrace = json['stackTrace'] ?? '';
6710+
}
6711+
6712+
@override
6713+
String get type => 'Microtask';
6714+
6715+
@override
6716+
Map<String, dynamic> toJson() => <String, Object?>{
6717+
'type': type,
6718+
'id': id ?? -1,
6719+
'stackTrace': stackTrace ?? '',
6720+
};
6721+
6722+
@override
6723+
int get hashCode => id.hashCode;
6724+
6725+
@override
6726+
bool operator ==(Object other) => other is Microtask && id == other.id;
6727+
6728+
@override
6729+
String toString() => '[Microtask id: $id, stackTrace: $stackTrace]';
6730+
}
6731+
66576732
/// A `NativeFunction` object is used to represent native functions in profiler
66586733
/// samples. See [CpuSamples];
66596734
class NativeFunction {
@@ -7302,6 +7377,51 @@ class ProcessMemoryItem {
73027377
'name: $name, description: $description, size: $size, children: $children]';
73037378
}
73047379

7380+
/// A `QueuedMicrotasks` object is a snapshot containing information about the
7381+
/// microtasks that were queued in a certain isolate at a certain time.
7382+
///
7383+
/// See [VmService.getQueuedMicrotasks] and [Microtask].
7384+
class QueuedMicrotasks extends Response {
7385+
static QueuedMicrotasks? parse(Map<String, dynamic>? json) =>
7386+
json == null ? null : QueuedMicrotasks._fromJson(json);
7387+
7388+
/// The time at which this snapshot of the microtask queue was taken,
7389+
/// represented as microseconds since the "Unix epoch".
7390+
int? timestamp;
7391+
7392+
/// The microtasks that were in the queue when this snapshot was taken. The
7393+
/// microtask at the front of the queue (i.e. the one that will run earliest)
7394+
/// is the one at index 0 of this list.
7395+
List<Microtask>? microtasks;
7396+
7397+
QueuedMicrotasks({
7398+
this.timestamp,
7399+
this.microtasks,
7400+
});
7401+
7402+
QueuedMicrotasks._fromJson(Map<String, dynamic> json)
7403+
: super._fromJson(json) {
7404+
timestamp = json['timestamp'] ?? -1;
7405+
microtasks = List<Microtask>.from(
7406+
createServiceObject(json['microtasks'], const ['Microtask']) as List? ??
7407+
[]);
7408+
}
7409+
7410+
@override
7411+
String get type => 'QueuedMicrotasks';
7412+
7413+
@override
7414+
Map<String, dynamic> toJson() => <String, Object?>{
7415+
'type': type,
7416+
'timestamp': timestamp ?? -1,
7417+
'microtasks': microtasks?.map((f) => f.toJson()).toList(),
7418+
};
7419+
7420+
@override
7421+
String toString() =>
7422+
'[QueuedMicrotasks timestamp: $timestamp, microtasks: $microtasks]';
7423+
}
7424+
73057425
class ReloadReport extends Response {
73067426
static ReloadReport? parse(Map<String, dynamic>? json) =>
73077427
json == null ? null : ReloadReport._fromJson(json);

pkg/vm_service/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: vm_service
2-
version: 15.0.0
2+
version: 15.0.1-wip
33
description: >-
44
A library to communicate with a service implementing the Dart VM
55
service protocol.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) 2025, 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+
import 'dart:async';
6+
import 'dart:developer';
7+
8+
import 'package:test/test.dart';
9+
import 'package:vm_service/vm_service.dart';
10+
11+
import 'common/service_test_common.dart';
12+
import 'common/test_helper.dart';
13+
14+
const String shortFile = 'get_queued_microtasks_rpc_test.dart';
15+
const int numberOfMicrotasksToSchedule = 5;
16+
17+
Future<void> testeeMain() async {
18+
for (int i = 0; i < numberOfMicrotasksToSchedule; i++) {
19+
scheduleMicrotask(() {});
20+
debugger();
21+
// Give the microtask that we just scheduled an opportunity to run.
22+
await Future.delayed(const Duration(milliseconds: 1));
23+
}
24+
}
25+
26+
final tests = <IsolateTest>[
27+
(VmService service, IsolateRef isolateRef) async {
28+
for (int i = 0; i < numberOfMicrotasksToSchedule; i++) {
29+
await hasStoppedAtBreakpoint(service, isolateRef);
30+
final result = await service.getQueuedMicrotasks(
31+
isolateRef.id!,
32+
);
33+
34+
expect(result.timestamp, isPositive);
35+
expect(result.microtasks!.length, 1);
36+
expect(result.microtasks!.first.id, i);
37+
expect(result.microtasks!.first.stackTrace, contains(shortFile));
38+
await service.resume(isolateRef.id!);
39+
}
40+
},
41+
];
42+
43+
Future<void> main([args = const <String>[]]) => runIsolateTests(
44+
args,
45+
tests,
46+
shortFile,
47+
testeeConcurrent: testeeMain,
48+
extraArgs: ['--profile-microtasks'],
49+
);

pkg/vm_service/tool/dart/generate_dart_client.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,10 @@ enum RPCErrorKind {
334334
kInvalidTimelineRequest(code: 114,
335335
message: 'Invalid timeline request for the current timeline configuration'),
336336
337+
/// Information about the microtasks queued in the specified isolate cannot be
338+
/// retrieved.
339+
kCannotGetQueuedMicrotasks(code: 115, message: 'Cannot get queued microtasks'),
340+
337341
/// The custom stream does not exist.
338342
kCustomStreamDoesNotExist(code: 130, message: 'Custom stream does not exist'),
339343

pkg/vm_service_interface/lib/src/vm_service_interface.dart

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import 'service_extension_registry.dart';
1717

1818
export 'service_extension_registry.dart' show ServiceExtensionRegistry;
1919

20-
const String vmServiceVersion = '4.18.0';
20+
const String vmServiceVersion = '4.19.0';
2121

2222
/// A class representation of the Dart VM Service Protocol.
2323
abstract interface class VmServiceInterface {
@@ -784,6 +784,27 @@ abstract interface class VmServiceInterface {
784784
/// addition of any bucket.
785785
Future<ProcessMemoryUsage> getProcessMemoryUsage();
786786

787+
/// The `getQueuedMicrotasks` RPC returns a snapshot containing information
788+
/// about the microtasks that were queued in the specified isolate when the
789+
/// snapshot was taken.
790+
///
791+
/// If the VM was not started with the flag `--profile-microtasks`, this RPC
792+
/// will return [RPCError] 100 "Feature is disabled".
793+
///
794+
/// If an exception has gone unhandled in the specified isolate, this RPC will
795+
/// return [RPCError] 115 "Cannot get queued microtasks".
796+
///
797+
/// If custom `dart:async` `Zone`s are used to redirect microtasks to be
798+
/// queued elsewhere than the root `dart:async` `Zone`'s microtask queue,
799+
/// information about those redirected microtasks will not be returned by this
800+
/// function.
801+
///
802+
/// If `isolateId` refers to an isolate that has exited, then the `Collected`
803+
/// [Sentinel] will be returned.
804+
///
805+
/// See [QueuedMicrotasks].
806+
Future<QueuedMicrotasks> getQueuedMicrotasks(String isolateId);
807+
787808
/// The `getStack` RPC is used to retrieve the current execution stack and
788809
/// message queue for an isolate. The isolate does not need to be paused.
789810
///
@@ -1606,6 +1627,11 @@ class VmServerConnection {
16061627
case 'getProcessMemoryUsage':
16071628
response = await _serviceImplementation.getProcessMemoryUsage();
16081629
break;
1630+
case 'getQueuedMicrotasks':
1631+
response = await _serviceImplementation.getQueuedMicrotasks(
1632+
params!['isolateId'],
1633+
);
1634+
break;
16091635
case 'getStack':
16101636
response = await _serviceImplementation.getStack(
16111637
params!['isolateId'],

0 commit comments

Comments
 (0)