Reland "[ Service / dart:isolate ] Added getPorts RPC and 'debugName' optional"

This reverts commit f78c40e32a5b52f6b013a61f1548e0da5a9089a8.

Change-Id: Id838b39afcb371d3b50f0009322ecf0fb2080894
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/169461
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5e40af4..2611829 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,8 +4,14 @@
 
 #### `dart:io`
 
-*   `HttpRequest` will now correctly follow HTTP 308 redirects
-    (`HttpStatus.permanentRedirect`).
+* `HttpRequest` will now correctly follow HTTP 308 redirects
+  (`HttpStatus.permanentRedirect`).
+
+#### `dart:isolate`
+
+* Added `debugName` positional parameter to `ReceivePort` and `RawReceivePort`
+  constructors, a name which can be associated with the port and displayed in
+  tooling.
 
 ### Dart VM
 
diff --git a/pkg/vm_service/CHANGELOG.md b/pkg/vm_service/CHANGELOG.md
index 9299ed5..bbb753e 100644
--- a/pkg/vm_service/CHANGELOG.md
+++ b/pkg/vm_service/CHANGELOG.md
@@ -1,4 +1,12 @@
 # Changelog
+
+## 5.4.0
+- Update to version `3.41.0` of the spec.
+- Added `PortList` class.
+- Added `getPorts` RPC.
+- Added optional properties `portId`, `allocationLocation`, and `debugName` to
+  `InstanceRef` and `Instance`.
+
 ## 5.3.1
 - Rename `State` class to `_State` to avoid class name conflicts with Flutter.
 
@@ -6,7 +14,6 @@
 - Added support for `dart:io` extensions version 1.5.
 - Added combination getter/setter `socketProfilingEnabled`.
 - Deprecated `startSocketProfiling` and `pauseSocketProfiling`.
-- Added support for `dart:io` extensions version 1.4.
 - Update to version `3.40.0` of the spec.
 - Added `IsolateFlag` class.
 - Added `isolateFlags` property to `Isolate`.
diff --git a/pkg/vm_service/example/vm_service_assert.dart b/pkg/vm_service/example/vm_service_assert.dart
index e2ae6d1..617bd7c 100644
--- a/pkg/vm_service/example/vm_service_assert.dart
+++ b/pkg/vm_service/example/vm_service_assert.dart
@@ -192,6 +192,7 @@
   if (obj == "MirrorReference") return obj;
   if (obj == "Null") return obj;
   if (obj == "PlainInstance") return obj;
+  if (obj == "ReceivePort") return obj;
   if (obj == "RegExp") return obj;
   if (obj == "StackTrace") return obj;
   if (obj == "String") return obj;
@@ -912,6 +913,13 @@
   return obj;
 }
 
+vms.PortList assertPortList(vms.PortList obj) {
+  assertNotNull(obj);
+  assertString(obj.type);
+  assertListOfInstanceRef(obj.ports);
+  return obj;
+}
+
 vms.ProfileFunction assertProfileFunction(vms.ProfileFunction obj) {
   assertNotNull(obj);
   assertString(obj.kind);
diff --git a/pkg/vm_service/java/.gitignore b/pkg/vm_service/java/.gitignore
index ae6691f..8803d45 100644
--- a/pkg/vm_service/java/.gitignore
+++ b/pkg/vm_service/java/.gitignore
@@ -25,6 +25,7 @@
 src/org/dartlang/vm/service/consumer/InvokeConsumer.java
 src/org/dartlang/vm/service/consumer/KillConsumer.java
 src/org/dartlang/vm/service/consumer/PauseConsumer.java
+src/org/dartlang/vm/service/consumer/PortListConsumer.java
 src/org/dartlang/vm/service/consumer/ProcessMemoryUsageConsumer.java
 src/org/dartlang/vm/service/consumer/ProtocolListConsumer.java
 src/org/dartlang/vm/service/consumer/ReloadSourcesConsumer.java
@@ -95,6 +96,7 @@
 src/org/dartlang/vm/service/element/NullRef.java
 src/org/dartlang/vm/service/element/Obj.java
 src/org/dartlang/vm/service/element/ObjRef.java
+src/org/dartlang/vm/service/element/PortList.java
 src/org/dartlang/vm/service/element/ProcessMemoryItem.java
 src/org/dartlang/vm/service/element/ProcessMemoryUsage.java
 src/org/dartlang/vm/service/element/ProfileFunction.java
diff --git a/pkg/vm_service/java/version.properties b/pkg/vm_service/java/version.properties
index d0564a6..ae41aeb 100644
--- a/pkg/vm_service/java/version.properties
+++ b/pkg/vm_service/java/version.properties
@@ -1 +1 @@
-version=3.40
+version=3.41
diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart
index fd20c5b..4dfc752 100644
--- a/pkg/vm_service/lib/src/vm_service.dart
+++ b/pkg/vm_service/lib/src/vm_service.dart
@@ -28,7 +28,7 @@
         HeapSnapshotObjectNoData,
         HeapSnapshotObjectNullData;
 
-const String vmServiceVersion = '3.40.0';
+const String vmServiceVersion = '3.41.0';
 
 /// @optional
 const String optional = 'optional';
@@ -157,6 +157,7 @@
   'Null': NullVal.parse,
   '@Object': ObjRef.parse,
   'Object': Obj.parse,
+  'PortList': PortList.parse,
   'ProfileFunction': ProfileFunction.parse,
   'ProtocolList': ProtocolList.parse,
   'Protocol': Protocol.parse,
@@ -209,6 +210,7 @@
   'getIsolateGroupMemoryUsage': const ['MemoryUsage'],
   'getScripts': const ['ScriptList'],
   'getObject': const ['Obj'],
+  'getPorts': const ['PortList'],
   'getRetainingPath': const ['RetainingPath'],
   'getProcessMemoryUsage': const ['ProcessMemoryUsage'],
   'getStack': const ['Stack'],
@@ -688,6 +690,12 @@
     int count,
   });
 
+  /// The `getPorts` RPC is used to retrieve the list of `ReceivePort` instances
+  /// for a given isolate.
+  ///
+  /// See [PortList].
+  Future<PortList> getPorts(String isolateId);
+
   /// The `getRetainingPath` RPC is used to lookup a path from an object
   /// specified by `targetId` to a GC root (i.e., the object which is preventing
   /// this object from being garbage collected).
@@ -1318,6 +1326,11 @@
             count: params['count'],
           );
           break;
+        case 'getPorts':
+          response = await _serviceImplementation.getPorts(
+            params['isolateId'],
+          );
+          break;
         case 'getRetainingPath':
           response = await _serviceImplementation.getRetainingPath(
             params['isolateId'],
@@ -1771,6 +1784,10 @@
       });
 
   @override
+  Future<PortList> getPorts(String isolateId) =>
+      _call('getPorts', {'isolateId': isolateId});
+
+  @override
   Future<RetainingPath> getRetainingPath(
           String isolateId, String targetId, int limit) =>
       _call('getRetainingPath',
@@ -2432,6 +2449,9 @@
 
   /// An instance of the Dart class BoundedType.
   static const String kBoundedType = 'BoundedType';
+
+  /// An instance of the Dart class ReceivePort.
+  static const String kReceivePort = 'ReceivePort';
 }
 
 /// A `SentinelKind` is used to distinguish different kinds of `Sentinel`
@@ -4239,6 +4259,27 @@
   @optional
   ContextRef closureContext;
 
+  /// The port ID for a ReceivePort.
+  ///
+  /// Provided for instance kinds:
+  ///  - ReceivePort
+  @optional
+  int portId;
+
+  /// The stack trace associated with the allocation of a ReceivePort.
+  ///
+  /// Provided for instance kinds:
+  ///  - ReceivePort
+  @optional
+  InstanceRef allocationLocation;
+
+  /// A name associated with a ReceivePort used for debugging purposes.
+  ///
+  /// Provided for instance kinds:
+  ///  - ReceivePort
+  @optional
+  String debugName;
+
   InstanceRef({
     @required this.kind,
     @required this.classRef,
@@ -4252,6 +4293,9 @@
     this.pattern,
     this.closureFunction,
     this.closureContext,
+    this.portId,
+    this.allocationLocation,
+    this.debugName,
   }) : super(id: id);
 
   InstanceRef._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -4269,6 +4313,10 @@
         createServiceObject(json['closureFunction'], const ['FuncRef']);
     closureContext =
         createServiceObject(json['closureContext'], const ['ContextRef']);
+    portId = json['portId'];
+    allocationLocation =
+        createServiceObject(json['allocationLocation'], const ['InstanceRef']);
+    debugName = json['debugName'];
   }
 
   @override
@@ -4288,6 +4336,9 @@
     _setIfNotNull(json, 'pattern', pattern?.toJson());
     _setIfNotNull(json, 'closureFunction', closureFunction?.toJson());
     _setIfNotNull(json, 'closureContext', closureContext?.toJson());
+    _setIfNotNull(json, 'portId', portId);
+    _setIfNotNull(json, 'allocationLocation', allocationLocation?.toJson());
+    _setIfNotNull(json, 'debugName', debugName);
     return json;
   }
 
@@ -4318,6 +4369,7 @@
   ///  - Double (suitable for passing to Double.parse())
   ///  - Int (suitable for passing to int.parse())
   ///  - String (value may be truncated)
+  ///  - StackTrace
   @optional
   String valueAsString;
 
@@ -4554,6 +4606,27 @@
   @optional
   InstanceRef bound;
 
+  /// The port ID for a ReceivePort.
+  ///
+  /// Provided for instance kinds:
+  ///  - ReceivePort
+  @optional
+  int portId;
+
+  /// The stack trace associated with the allocation of a ReceivePort.
+  ///
+  /// Provided for instance kinds:
+  ///  - ReceivePort
+  @optional
+  InstanceRef allocationLocation;
+
+  /// A name associated with a ReceivePort used for debugging purposes.
+  ///
+  /// Provided for instance kinds:
+  ///  - ReceivePort
+  @optional
+  String debugName;
+
   Instance({
     @required this.kind,
     @required this.classRef,
@@ -4582,6 +4655,9 @@
     this.parameterIndex,
     this.targetType,
     this.bound,
+    this.portId,
+    this.allocationLocation,
+    this.debugName,
   }) : super(id: id);
 
   Instance._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -4627,6 +4703,10 @@
     parameterIndex = json['parameterIndex'];
     targetType = createServiceObject(json['targetType'], const ['InstanceRef']);
     bound = createServiceObject(json['bound'], const ['InstanceRef']);
+    portId = json['portId'];
+    allocationLocation =
+        createServiceObject(json['allocationLocation'], const ['InstanceRef']);
+    debugName = json['debugName'];
   }
 
   @override
@@ -4663,6 +4743,9 @@
     _setIfNotNull(json, 'parameterIndex', parameterIndex);
     _setIfNotNull(json, 'targetType', targetType?.toJson());
     _setIfNotNull(json, 'bound', bound?.toJson());
+    _setIfNotNull(json, 'portId', portId);
+    _setIfNotNull(json, 'allocationLocation', allocationLocation?.toJson());
+    _setIfNotNull(json, 'debugName', debugName);
     return json;
   }
 
@@ -5743,6 +5826,37 @@
   String toString() => '[Obj type: ${type}, id: ${id}]';
 }
 
+/// A `PortList` contains a list of ports associated with some isolate.
+///
+/// See [getPort].
+class PortList extends Response {
+  static PortList parse(Map<String, dynamic> json) =>
+      json == null ? null : PortList._fromJson(json);
+
+  List<InstanceRef> ports;
+
+  PortList({
+    @required this.ports,
+  });
+
+  PortList._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
+    ports = List<InstanceRef>.from(
+        createServiceObject(json['ports'], const ['InstanceRef']) ?? []);
+  }
+
+  @override
+  Map<String, dynamic> toJson() {
+    var json = <String, dynamic>{};
+    json['type'] = 'PortList';
+    json.addAll({
+      'ports': ports.map((f) => f.toJson()).toList(),
+    });
+    return json;
+  }
+
+  String toString() => '[PortList type: ${type}, ports: ${ports}]';
+}
+
 /// A `ProfileFunction` contains profiling information about a Dart or native
 /// function.
 ///
diff --git a/pkg/vm_service/pubspec.yaml b/pkg/vm_service/pubspec.yaml
index 7d0d07a..0c77a68 100644
--- a/pkg/vm_service/pubspec.yaml
+++ b/pkg/vm_service/pubspec.yaml
@@ -2,7 +2,7 @@
 description: >-
   A library to communicate with a service implementing the Dart VM
   service protocol.
-version: 5.3.1
+version: 5.4.0
 
 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/vm_service
 
diff --git a/runtime/lib/isolate.cc b/runtime/lib/isolate.cc
index 4b381cb..905cb26 100644
--- a/runtime/lib/isolate.cc
+++ b/runtime/lib/isolate.cc
@@ -51,11 +51,12 @@
   return Smi::New(hash);
 }
 
-DEFINE_NATIVE_ENTRY(RawReceivePortImpl_factory, 0, 1) {
+DEFINE_NATIVE_ENTRY(RawReceivePortImpl_factory, 0, 2) {
   ASSERT(
       TypeArguments::CheckedHandle(zone, arguments->NativeArgAt(0)).IsNull());
+  GET_NON_NULL_NATIVE_ARGUMENT(String, debug_name, arguments->NativeArgAt(1));
   Dart_Port port_id = PortMap::CreatePort(isolate->message_handler());
-  return ReceivePort::New(port_id, false /* not control port */);
+  return ReceivePort::New(port_id, debug_name, false /* not control port */);
 }
 
 DEFINE_NATIVE_ENTRY(RawReceivePortImpl_get_id, 0, 1) {
diff --git a/runtime/lib/stacktrace.cc b/runtime/lib/stacktrace.cc
index 43383d4..3eabaf4 100644
--- a/runtime/lib/stacktrace.cc
+++ b/runtime/lib/stacktrace.cc
@@ -214,4 +214,12 @@
   return stacktrace;
 }
 
+bool HasStack() {
+  Thread* thread = Thread::Current();
+  StackFrameIterator frames(ValidationPolicy::kDontValidateFrames, thread,
+                            StackFrameIterator::kNoCrossThreadIteration);
+  StackFrame* frame = frames.NextFrame();
+  return frame != nullptr;
+}
+
 }  // namespace dart
diff --git a/runtime/lib/stacktrace.h b/runtime/lib/stacktrace.h
index 9c8abae..a804d62 100644
--- a/runtime/lib/stacktrace.h
+++ b/runtime/lib/stacktrace.h
@@ -21,6 +21,9 @@
 // Creates a StackTrace object to be attached to an exception.
 StackTracePtr GetStackTraceForException();
 
+// Returns false if there is no Dart stack available.
+bool HasStack();
+
 }  // namespace dart
 
 #endif  // RUNTIME_LIB_STACKTRACE_H_
diff --git a/runtime/observatory/lib/src/models/objects/instance.dart b/runtime/observatory/lib/src/models/objects/instance.dart
index ab8d6d6..a0400c5 100644
--- a/runtime/observatory/lib/src/models/objects/instance.dart
+++ b/runtime/observatory/lib/src/models/objects/instance.dart
@@ -120,6 +120,9 @@
 
   /// An instance of the Dart class TypeRef.
   typeRef,
+
+  /// An instance of the Dart class RawReceivePort
+  receivePort,
 }
 
 bool isTypedData(InstanceKind? kind) {
diff --git a/runtime/observatory/lib/src/service/object.dart b/runtime/observatory/lib/src/service/object.dart
index 4f9ead0..55c9d13 100644
--- a/runtime/observatory/lib/src/service/object.dart
+++ b/runtime/observatory/lib/src/service/object.dart
@@ -2781,6 +2781,8 @@
       return M.InstanceKind.typeParameter;
     case 'TypeRef':
       return M.InstanceKind.typeRef;
+    case 'ReceivePort':
+      return M.InstanceKind.receivePort;
   }
   var message = 'Unrecognized instance kind: $s';
   Logger.root.severe(message);
diff --git a/runtime/observatory/tests/service/get_ports_public_rpc_test.dart b/runtime/observatory/tests/service/get_ports_public_rpc_test.dart
new file mode 100644
index 0000000..cc511f7
--- /dev/null
+++ b/runtime/observatory/tests/service/get_ports_public_rpc_test.dart
@@ -0,0 +1,50 @@
+// Copyright (c) 2020, 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.
+
+import 'dart:isolate' hide Isolate;
+import 'package:observatory/service_io.dart';
+import 'package:test/test.dart';
+
+import 'test_helper.dart';
+
+var port1;
+var port2;
+var port3;
+
+void warmup() {
+  port1 = RawReceivePort(null, 'port1');
+  port2 = RawReceivePort((_) {});
+  port3 = RawReceivePort((_) {}, 'port3');
+  port3.close();
+  RawReceivePort((_) {}, 'port4');
+}
+
+int countNameMatches(ports, name) {
+  var matches = 0;
+  for (var port in ports) {
+    if (port['debugName'] == name) {
+      matches++;
+    }
+  }
+  return matches;
+}
+
+final tests = <IsolateTest>[
+  (Isolate isolate) async {
+    dynamic result = await isolate.invokeRpcNoUpgrade('getPorts', {});
+    expect(result['type'], 'PortList');
+    expect(result['ports'], isList);
+    final ports = result['ports'];
+    // There are at least three ports: the three created in warm up that
+    // weren't closed. Some OSes will have other ports open but we do not try
+    // and test for these.
+    expect(ports.length, greaterThanOrEqualTo(3));
+    expect(countNameMatches(ports, 'port1'), 1);
+    expect(countNameMatches(ports, 'port3'), 0);
+    expect(countNameMatches(ports, 'port4'), 1);
+    expect(countNameMatches(ports, ''), greaterThanOrEqualTo(1));
+  },
+];
+
+main(args) async => runIsolateTests(args, tests, testeeBefore: warmup);
diff --git a/runtime/observatory/tests/service/get_version_rpc_test.dart b/runtime/observatory/tests/service/get_version_rpc_test.dart
index 8b56393..c8b0244 100644
--- a/runtime/observatory/tests/service/get_version_rpc_test.dart
+++ b/runtime/observatory/tests/service/get_version_rpc_test.dart
@@ -9,12 +9,12 @@
 
 var tests = <VMTest>[
   (VM vm) async {
-    var result = await vm.invokeRpcNoUpgrade('getVersion', {});
-    expect(result['type'], equals('Version'));
-    expect(result['major'], equals(3));
-    expect(result['minor'], equals(40));
-    expect(result['_privateMajor'], equals(0));
-    expect(result['_privateMinor'], equals(0));
+    final result = await vm.invokeRpcNoUpgrade('getVersion', {});
+    expect(result['type'], 'Version');
+    expect(result['major'], 3);
+    expect(result['minor'], 41);
+    expect(result['_privateMajor'], 0);
+    expect(result['_privateMinor'], 0);
   },
 ];
 
diff --git a/runtime/observatory_2/lib/src/models/objects/instance.dart b/runtime/observatory_2/lib/src/models/objects/instance.dart
index 66965e9..e52cdd7 100644
--- a/runtime/observatory_2/lib/src/models/objects/instance.dart
+++ b/runtime/observatory_2/lib/src/models/objects/instance.dart
@@ -120,6 +120,9 @@
 
   /// An instance of the Dart class TypeRef.
   typeRef,
+
+  /// An instance of the Dart class RawReceivePort
+  receivePort,
 }
 
 bool isTypedData(InstanceKind kind) {
diff --git a/runtime/observatory_2/lib/src/service/object.dart b/runtime/observatory_2/lib/src/service/object.dart
index 1bd33fb..fe4027b 100644
--- a/runtime/observatory_2/lib/src/service/object.dart
+++ b/runtime/observatory_2/lib/src/service/object.dart
@@ -2790,6 +2790,8 @@
       return M.InstanceKind.typeParameter;
     case 'TypeRef':
       return M.InstanceKind.typeRef;
+    case 'ReceivePort':
+      return M.InstanceKind.receivePort;
   }
   var message = 'Unrecognized instance kind: $s';
   Logger.root.severe(message);
diff --git a/runtime/observatory_2/tests/service_2/get_ports_public_rpc_test.dart b/runtime/observatory_2/tests/service_2/get_ports_public_rpc_test.dart
new file mode 100644
index 0000000..179823f
--- /dev/null
+++ b/runtime/observatory_2/tests/service_2/get_ports_public_rpc_test.dart
@@ -0,0 +1,50 @@
+// Copyright (c) 2020, 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.
+
+import 'dart:isolate' hide Isolate;
+import 'package:observatory_2/service_io.dart';
+import 'package:test/test.dart';
+
+import 'test_helper.dart';
+
+var port1;
+var port2;
+var port3;
+
+void warmup() {
+  port1 = RawReceivePort(null, 'port1');
+  port2 = RawReceivePort((_) {});
+  port3 = RawReceivePort((_) {}, 'port3');
+  port3.close();
+  RawReceivePort((_) {}, 'port4');
+}
+
+int countNameMatches(ports, name) {
+  var matches = 0;
+  for (var port in ports) {
+    if (port['debugName'] == name) {
+      matches++;
+    }
+  }
+  return matches;
+}
+
+final tests = <IsolateTest>[
+  (Isolate isolate) async {
+    dynamic result = await isolate.invokeRpcNoUpgrade('getPorts', {});
+    expect(result['type'], 'PortList');
+    expect(result['ports'], isList);
+    final ports = result['ports'];
+    // There are at least three ports: the three created in warm up that
+    // weren't closed. Some OSes will have other ports open but we do not try
+    // and test for these.
+    expect(ports.length, greaterThanOrEqualTo(3));
+    expect(countNameMatches(ports, 'port1'), 1);
+    expect(countNameMatches(ports, 'port3'), 0);
+    expect(countNameMatches(ports, 'port4'), 1);
+    expect(countNameMatches(ports, ''), greaterThanOrEqualTo(1));
+  },
+];
+
+main(args) async => runIsolateTests(args, tests, testeeBefore: warmup);
diff --git a/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart b/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart
index d5450e5..df37cb9 100644
--- a/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart
+++ b/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart
@@ -12,7 +12,7 @@
     var result = await vm.invokeRpcNoUpgrade('getVersion', {});
     expect(result['type'], equals('Version'));
     expect(result['major'], equals(3));
-    expect(result['minor'], equals(40));
+    expect(result['minor'], equals(41));
     expect(result['_privateMajor'], equals(0));
     expect(result['_privateMinor'], equals(0));
   },
diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h
index b7ef75aa..8b15f7b 100644
--- a/runtime/vm/bootstrap_natives.h
+++ b/runtime/vm/bootstrap_natives.h
@@ -54,7 +54,7 @@
   V(CapabilityImpl_factory, 1)                                                 \
   V(CapabilityImpl_equals, 2)                                                  \
   V(CapabilityImpl_get_hashcode, 1)                                            \
-  V(RawReceivePortImpl_factory, 1)                                             \
+  V(RawReceivePortImpl_factory, 2)                                             \
   V(RawReceivePortImpl_get_id, 1)                                              \
   V(RawReceivePortImpl_get_sendport, 1)                                        \
   V(RawReceivePortImpl_closeInternal, 1)                                       \
diff --git a/runtime/vm/compiler/runtime_offsets_extracted.h b/runtime/vm/compiler/runtime_offsets_extracted.h
index 6650cd6..52fcd87 100644
--- a/runtime/vm/compiler/runtime_offsets_extracted.h
+++ b/runtime/vm/compiler/runtime_offsets_extracted.h
@@ -497,7 +497,7 @@
 static constexpr dart::compiler::target::word PatchClass_InstanceSize = 24;
 static constexpr dart::compiler::target::word PcDescriptors_HeaderSize = 8;
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 12;
-static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 12;
+static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 20;
 static constexpr dart::compiler::target::word RedirectionData_InstanceSize = 16;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 60;
 static constexpr dart::compiler::target::word Script_InstanceSize = 56;
@@ -1017,7 +1017,7 @@
 static constexpr dart::compiler::target::word PatchClass_InstanceSize = 48;
 static constexpr dart::compiler::target::word PcDescriptors_HeaderSize = 16;
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 24;
-static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 24;
+static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 40;
 static constexpr dart::compiler::target::word RedirectionData_InstanceSize = 32;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 120;
 static constexpr dart::compiler::target::word Script_InstanceSize = 96;
@@ -1528,7 +1528,7 @@
 static constexpr dart::compiler::target::word PatchClass_InstanceSize = 24;
 static constexpr dart::compiler::target::word PcDescriptors_HeaderSize = 8;
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 12;
-static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 12;
+static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 20;
 static constexpr dart::compiler::target::word RedirectionData_InstanceSize = 16;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 60;
 static constexpr dart::compiler::target::word Script_InstanceSize = 56;
@@ -2049,7 +2049,7 @@
 static constexpr dart::compiler::target::word PatchClass_InstanceSize = 48;
 static constexpr dart::compiler::target::word PcDescriptors_HeaderSize = 16;
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 24;
-static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 24;
+static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 40;
 static constexpr dart::compiler::target::word RedirectionData_InstanceSize = 32;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 120;
 static constexpr dart::compiler::target::word Script_InstanceSize = 96;
@@ -2559,7 +2559,7 @@
 static constexpr dart::compiler::target::word PatchClass_InstanceSize = 24;
 static constexpr dart::compiler::target::word PcDescriptors_HeaderSize = 8;
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 12;
-static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 12;
+static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 20;
 static constexpr dart::compiler::target::word RedirectionData_InstanceSize = 16;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 60;
 static constexpr dart::compiler::target::word Script_InstanceSize = 56;
@@ -3073,7 +3073,7 @@
 static constexpr dart::compiler::target::word PatchClass_InstanceSize = 48;
 static constexpr dart::compiler::target::word PcDescriptors_HeaderSize = 16;
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 24;
-static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 24;
+static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 40;
 static constexpr dart::compiler::target::word RedirectionData_InstanceSize = 32;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 120;
 static constexpr dart::compiler::target::word Script_InstanceSize = 96;
@@ -3578,7 +3578,7 @@
 static constexpr dart::compiler::target::word PatchClass_InstanceSize = 24;
 static constexpr dart::compiler::target::word PcDescriptors_HeaderSize = 8;
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 12;
-static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 12;
+static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 20;
 static constexpr dart::compiler::target::word RedirectionData_InstanceSize = 16;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 60;
 static constexpr dart::compiler::target::word Script_InstanceSize = 56;
@@ -4093,7 +4093,7 @@
 static constexpr dart::compiler::target::word PatchClass_InstanceSize = 48;
 static constexpr dart::compiler::target::word PcDescriptors_HeaderSize = 16;
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 24;
-static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 24;
+static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 40;
 static constexpr dart::compiler::target::word RedirectionData_InstanceSize = 32;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 120;
 static constexpr dart::compiler::target::word Script_InstanceSize = 96;
@@ -4659,7 +4659,7 @@
 static constexpr dart::compiler::target::word AOT_PatchClass_InstanceSize = 20;
 static constexpr dart::compiler::target::word AOT_PcDescriptors_HeaderSize = 8;
 static constexpr dart::compiler::target::word AOT_Pointer_InstanceSize = 12;
-static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 12;
+static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 20;
 static constexpr dart::compiler::target::word AOT_RedirectionData_InstanceSize =
     16;
 static constexpr dart::compiler::target::word AOT_RegExp_InstanceSize = 60;
@@ -5233,7 +5233,7 @@
 static constexpr dart::compiler::target::word AOT_PatchClass_InstanceSize = 40;
 static constexpr dart::compiler::target::word AOT_PcDescriptors_HeaderSize = 16;
 static constexpr dart::compiler::target::word AOT_Pointer_InstanceSize = 24;
-static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 24;
+static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 40;
 static constexpr dart::compiler::target::word AOT_RedirectionData_InstanceSize =
     32;
 static constexpr dart::compiler::target::word AOT_RegExp_InstanceSize = 120;
@@ -5811,7 +5811,7 @@
 static constexpr dart::compiler::target::word AOT_PatchClass_InstanceSize = 40;
 static constexpr dart::compiler::target::word AOT_PcDescriptors_HeaderSize = 16;
 static constexpr dart::compiler::target::word AOT_Pointer_InstanceSize = 24;
-static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 24;
+static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 40;
 static constexpr dart::compiler::target::word AOT_RedirectionData_InstanceSize =
     32;
 static constexpr dart::compiler::target::word AOT_RegExp_InstanceSize = 120;
@@ -6377,7 +6377,7 @@
 static constexpr dart::compiler::target::word AOT_PatchClass_InstanceSize = 20;
 static constexpr dart::compiler::target::word AOT_PcDescriptors_HeaderSize = 8;
 static constexpr dart::compiler::target::word AOT_Pointer_InstanceSize = 12;
-static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 12;
+static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 20;
 static constexpr dart::compiler::target::word AOT_RedirectionData_InstanceSize =
     16;
 static constexpr dart::compiler::target::word AOT_RegExp_InstanceSize = 60;
@@ -6944,7 +6944,7 @@
 static constexpr dart::compiler::target::word AOT_PatchClass_InstanceSize = 40;
 static constexpr dart::compiler::target::word AOT_PcDescriptors_HeaderSize = 16;
 static constexpr dart::compiler::target::word AOT_Pointer_InstanceSize = 24;
-static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 24;
+static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 40;
 static constexpr dart::compiler::target::word AOT_RedirectionData_InstanceSize =
     32;
 static constexpr dart::compiler::target::word AOT_RegExp_InstanceSize = 120;
@@ -7515,7 +7515,7 @@
 static constexpr dart::compiler::target::word AOT_PatchClass_InstanceSize = 40;
 static constexpr dart::compiler::target::word AOT_PcDescriptors_HeaderSize = 16;
 static constexpr dart::compiler::target::word AOT_Pointer_InstanceSize = 24;
-static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 24;
+static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 40;
 static constexpr dart::compiler::target::word AOT_RedirectionData_InstanceSize =
     32;
 static constexpr dart::compiler::target::word AOT_RegExp_InstanceSize = 120;
diff --git a/runtime/vm/dart_entry.cc b/runtime/vm/dart_entry.cc
index a17966f..63db37e 100644
--- a/runtime/vm/dart_entry.cc
+++ b/runtime/vm/dart_entry.cc
@@ -725,6 +725,31 @@
   return result.raw();
 }
 
+ObjectPtr DartLibraryCalls::LookupOpenPorts() {
+  Thread* thread = Thread::Current();
+  Zone* zone = thread->zone();
+  Function& function = Function::Handle(
+      zone, thread->isolate()->object_store()->lookup_open_ports());
+  const int kTypeArgsLen = 0;
+  const int kNumArguments = 0;
+  if (function.IsNull()) {
+    Library& isolate_lib = Library::Handle(zone, Library::IsolateLibrary());
+    ASSERT(!isolate_lib.IsNull());
+    const String& class_name = String::Handle(
+        zone, isolate_lib.PrivateName(Symbols::_RawReceivePortImpl()));
+    const String& function_name = String::Handle(
+        zone, isolate_lib.PrivateName(Symbols::_lookupOpenPorts()));
+    function = Resolver::ResolveStatic(isolate_lib, class_name, function_name,
+                                       kTypeArgsLen, kNumArguments,
+                                       Object::empty_array());
+    ASSERT(!function.IsNull());
+    thread->isolate()->object_store()->set_lookup_open_ports(function);
+  }
+  const Object& result = Object::Handle(
+      zone, DartEntry::InvokeFunction(function, Object::empty_array()));
+  return result.raw();
+}
+
 ObjectPtr DartLibraryCalls::HandleMessage(const Object& handler,
                                           const Instance& message) {
   Thread* thread = Thread::Current();
diff --git a/runtime/vm/dart_entry.h b/runtime/vm/dart_entry.h
index 2ebdbe8..c85f683 100644
--- a/runtime/vm/dart_entry.h
+++ b/runtime/vm/dart_entry.h
@@ -282,6 +282,9 @@
   // Returns the handler if one has been registered for this port id.
   static ObjectPtr LookupHandler(Dart_Port port_id);
 
+  // Returns a list of open ReceivePorts.
+  static ObjectPtr LookupOpenPorts();
+
   // Returns null on success, a RawError on failure.
   static ObjectPtr HandleMessage(const Object& handler,
                                  const Instance& dart_message);
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index 8b8bbc0..7a1c69e 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -2385,8 +2385,9 @@
     args.SetAt(2, Instance::Handle(state->BuildArgs(thread)));
     args.SetAt(3, Instance::Handle(state->BuildMessage(thread)));
     args.SetAt(4, is_spawn_uri ? Bool::True() : Bool::False());
-    args.SetAt(5, ReceivePort::Handle(ReceivePort::New(
-                      isolate->main_port(), true /* control port */)));
+    args.SetAt(5, ReceivePort::Handle(
+                      ReceivePort::New(isolate->main_port(), Symbols::Empty(),
+                                       true /* control port */)));
     args.SetAt(6, capabilities);
 
     const Library& lib = Library::Handle(Library::IsolateLibrary());
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 330635d..bc6666b 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "include/dart_api.h"
+#include "lib/stacktrace.h"
 #include "platform/assert.h"
 #include "platform/text_buffer.h"
 #include "platform/unaligned.h"
@@ -23805,6 +23806,7 @@
 }
 
 ReceivePortPtr ReceivePort::New(Dart_Port id,
+                                const String& debug_name,
                                 bool is_control_port,
                                 Heap::Space space) {
   ASSERT(id != ILLEGAL_PORT);
@@ -23812,6 +23814,8 @@
   Zone* zone = thread->zone();
   const SendPort& send_port =
       SendPort::Handle(zone, SendPort::New(id, thread->isolate()->origin_id()));
+  const StackTrace& allocation_location_ =
+      HasStack() ? GetCurrentStackTrace(0) : StackTrace::Handle();
 
   ReceivePort& result = ReceivePort::Handle(zone);
   {
@@ -23820,6 +23824,9 @@
     NoSafepointScope no_safepoint;
     result ^= raw;
     result.StorePointer(&result.raw_ptr()->send_port_, send_port.raw());
+    result.StorePointer(&result.raw_ptr()->debug_name_, debug_name.raw());
+    result.StorePointer(&result.raw_ptr()->allocation_location_,
+                        allocation_location_.raw());
   }
   if (is_control_port) {
     PortMap::SetPortState(id, PortMap::kControlPort);
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 1a3cea0..1bd2be8 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -10567,10 +10567,17 @@
   InstancePtr handler() const { return raw_ptr()->handler_; }
   void set_handler(const Instance& value) const;
 
+  StackTracePtr allocation_location() const {
+    return raw_ptr()->allocation_location_;
+  }
+
+  StringPtr debug_name() const { return raw_ptr()->debug_name_; }
+
   static intptr_t InstanceSize() {
     return RoundedAllocationSize(sizeof(ReceivePortLayout));
   }
   static ReceivePortPtr New(Dart_Port id,
+                            const String& debug_name,
                             bool is_control_port,
                             Heap::Space space = Heap::kNew);
 
diff --git a/runtime/vm/object_service.cc b/runtime/vm/object_service.cc
index 0d6553e..acdaca6 100644
--- a/runtime/vm/object_service.cc
+++ b/runtime/vm/object_service.cc
@@ -1452,7 +1452,15 @@
 }
 
 void ReceivePort::PrintJSONImpl(JSONStream* stream, bool ref) const {
-  Instance::PrintJSONImpl(stream, ref);
+  JSONObject obj(stream);
+  Instance::PrintSharedInstanceJSON(&obj, ref);
+  const StackTrace& allocation_location_ =
+      StackTrace::Handle(allocation_location());
+  const String& debug_name_ = String::Handle(debug_name());
+  obj.AddProperty("kind", "ReceivePort");
+  obj.AddProperty64("portId", Id());
+  obj.AddProperty("debugName", debug_name_.ToCString());
+  obj.AddProperty("allocationLocation", allocation_location_);
 }
 
 void SendPort::PrintJSONImpl(JSONStream* stream, bool ref) const {
diff --git a/runtime/vm/object_store.h b/runtime/vm/object_store.h
index dc51e07..829ae7e 100644
--- a/runtime/vm/object_store.h
+++ b/runtime/vm/object_store.h
@@ -160,6 +160,7 @@
   RW(Instance, stack_overflow)                                                 \
   RW(Instance, out_of_memory)                                                  \
   RW(Function, lookup_port_handler)                                            \
+  RW(Function, lookup_open_ports)                                              \
   RW(Function, handle_message_function)                                        \
   RW(Function, growable_list_factory)                                          \
   RW(Function, simple_instance_of_function)                                    \
diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h
index 4a713b6..a124ad5 100644
--- a/runtime/vm/raw_object.h
+++ b/runtime/vm/raw_object.h
@@ -2726,7 +2726,9 @@
   VISIT_FROM(ObjectPtr, send_port_)
   SendPortPtr send_port_;
   InstancePtr handler_;
-  VISIT_TO(ObjectPtr, handler_)
+  StringPtr debug_name_;
+  StackTracePtr allocation_location_;
+  VISIT_TO(ObjectPtr, allocation_location_)
 };
 
 class TransferableTypedDataLayout : public InstanceLayout {
diff --git a/runtime/vm/raw_object_fields.cc b/runtime/vm/raw_object_fields.cc
index 7276a25..f5089d9 100644
--- a/runtime/vm/raw_object_fields.cc
+++ b/runtime/vm/raw_object_fields.cc
@@ -171,6 +171,8 @@
   F(ExternalTypedData, length_)                                                \
   F(ReceivePort, send_port_)                                                   \
   F(ReceivePort, handler_)                                                     \
+  F(ReceivePort, debug_name_)                                                  \
+  F(ReceivePort, allocation_location_)                                         \
   F(StackTrace, async_link_)                                                   \
   F(StackTrace, code_array_)                                                   \
   F(StackTrace, pc_offset_array_)                                              \
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index 23dfdde..d12772e 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -3142,6 +3142,30 @@
   return true;
 }
 
+static const MethodParameter* get_ports_params[] = {
+    RUNNABLE_ISOLATE_PARAMETER,
+    NULL,
+};
+
+static bool GetPorts(Thread* thread, JSONStream* js) {
+  // Ensure the array and handles created below are promptly destroyed.
+  StackZone zone(thread);
+  HANDLESCOPE(thread);
+  const GrowableObjectArray& ports = GrowableObjectArray::Handle(
+      GrowableObjectArray::RawCast(DartLibraryCalls::LookupOpenPorts()));
+  JSONObject jsobj(js);
+  jsobj.AddProperty("type", "PortList");
+  {
+    Instance& port = Instance::Handle(zone.GetZone());
+    JSONArray arr(&jsobj, "ports");
+    for (int i = 0; i < ports.Length(); ++i) {
+      port ^= ports.At(i);
+      arr.AddValue(port);
+    }
+  }
+  return true;
+}
+
 #if !defined(DART_PRECOMPILED_RUNTIME)
 static const char* const report_enum_names[] = {
     SourceReport::kCallSitesStr,
@@ -4389,12 +4413,12 @@
   return true;
 }
 
-static const MethodParameter* get_ports_params[] = {
+static const MethodParameter* get_ports_private_params[] = {
     RUNNABLE_ISOLATE_PARAMETER,
     NULL,
 };
 
-static bool GetPorts(Thread* thread, JSONStream* js) {
+static bool GetPortsPrivate(Thread* thread, JSONStream* js) {
   MessageHandler* message_handler = thread->isolate()->message_handler();
   PortMap::PrintPortsForMessageHandler(message_handler, js);
   return true;
@@ -5054,6 +5078,8 @@
     get_inbound_references_params },
   { "getInstances", GetInstances,
     get_instances_params },
+  { "getPorts", GetPorts,
+    get_ports_params },
   { "getIsolate", GetIsolate,
     get_isolate_params },
   { "_getIsolateObjectStore", GetIsolateObjectStore,
@@ -5074,8 +5100,8 @@
     get_object_store_params },
   { "_getPersistentHandles", GetPersistentHandles,
       get_persistent_handles_params, },
-  { "_getPorts", GetPorts,
-    get_ports_params },
+  { "_getPorts", GetPortsPrivate,
+    get_ports_private_params },
   { "getProcessMemoryUsage", GetProcessMemoryUsage,
     get_process_memory_usage_params },
   { "_getReachableSize", GetReachableSize,
diff --git a/runtime/vm/service.h b/runtime/vm/service.h
index bf1d99b..9d09af1 100644
--- a/runtime/vm/service.h
+++ b/runtime/vm/service.h
@@ -15,7 +15,7 @@
 namespace dart {
 
 #define SERVICE_PROTOCOL_MAJOR_VERSION 3
-#define SERVICE_PROTOCOL_MINOR_VERSION 40
+#define SERVICE_PROTOCOL_MINOR_VERSION 41
 
 class Array;
 class EmbedderServiceHandler;
diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md
index b8d0906..6fe2fb8 100644
--- a/runtime/vm/service/service.md
+++ b/runtime/vm/service/service.md
@@ -1,8 +1,8 @@
-# Dart VM Service Protocol 3.40
+# Dart VM Service Protocol 3.41
 
 > Please post feedback to the [observatory-discuss group][discuss-list]
 
-This document describes of _version 3.40_ of the Dart VM Service Protocol. This
+This document describes of _version 3.41_ of the Dart VM Service Protocol. This
 protocol is used to communicate with a running Dart Virtual Machine.
 
 To use the Service Protocol, start the VM with the *--observe* flag.
@@ -47,6 +47,7 @@
   - [getIsolateGroup](#getisolategroup)
   - [getMemoryUsage](#getmemoryusage)
   - [getObject](#getobject)
+  - [getPorts](#getports)
   - [getProcessMemoryUsage](#getprocessmemoryusage)
   - [getRetainingPath](#getretainingpath)
   - [getScripts](#getscripts)
@@ -113,6 +114,7 @@
   - [NativeFunction](#nativefunction)
   - [Null](#null)
   - [Object](#object)
+  - [PortList](#portlist)
   - [ReloadReport](#reloadreport)
   - [Response](#response)
   - [RetainingObject](#retainingobject)
@@ -936,6 +938,17 @@
 Float32x4List, and Float64x2List.  These parameters are otherwise
 ignored.
 
+### getPorts
+
+```
+PortList getPorts(string isolateId)
+```
+
+The _getPorts_ RPC is used to retrieve the list of `ReceivePort` instances for a
+given isolate.
+
+See [PortList](#portlist).
+
 ### getRetainingPath
 
 ```
@@ -2426,6 +2439,24 @@
   // Provided for instance kinds:
   //   Closure
   @Context closureContext [optional];
+
+  // The port ID for a ReceivePort.
+  //
+  // Provided for instance kinds:
+  //   ReceivePort
+  int portId [optional];
+
+  // The stack trace associated with the allocation of a ReceivePort.
+  //
+  // Provided for instance kinds:
+  //   ReceivePort
+  @Instance allocationLocation [optional];
+
+  // A name associated with a ReceivePort used for debugging purposes.
+  //
+  // Provided for instance kinds:
+  //   ReceivePort
+  string debugName [optional];
 }
 ```
 
@@ -2446,6 +2477,7 @@
   //   Double (suitable for passing to Double.parse())
   //   Int (suitable for passing to int.parse())
   //   String (value may be truncated)
+  //   StackTrace
   string valueAsString [optional];
 
   // The valueAsString for String references may be truncated. If so,
@@ -2658,6 +2690,24 @@
   //   BoundedType
   //   TypeParameter
   @Instance bound [optional];
+
+  // The port ID for a ReceivePort.
+  //
+  // Provided for instance kinds:
+  //   ReceivePort
+  int portId [optional];
+
+  // The stack trace associated with the allocation of a ReceivePort.
+  //
+  // Provided for instance kinds:
+  //   ReceivePort
+  @Instance allocationLocation [optional];
+
+  // A name associated with a ReceivePort used for debugging purposes.
+  //
+  // Provided for instance kinds:
+  //   ReceivePort
+  string debugName [optional];
 }
 ```
 
@@ -2742,6 +2792,9 @@
 
   // An instance of the Dart class BoundedType.
   BoundedType,
+
+  // An instance of the Dart class ReceivePort.
+  ReceivePort,
 }
 ```
 
@@ -3184,6 +3237,18 @@
 
 An _Object_ is a  persistent object that is owned by some isolate.
 
+### PortList
+
+```
+class PortList extends Response {
+  @Instance[] ports;
+}
+```
+
+A _PortList_ contains a list of ports associated with some isolate.
+
+See [getPort](#getPort).
+
 ### ProfileFunction
 
 ```
@@ -3855,5 +3920,6 @@
 3.38 | Added `isSystemIsolate` property to `@Isolate` and `Isolate`, `isSystemIsolateGroup` property to `@IsolateGroup` and `IsolateGroup`, and properties `systemIsolates` and `systemIsolateGroups` to `VM`.
 3.39 | Removed the following deprecated RPCs and objects: `getClientName`, `getWebSocketTarget`, `setClientName`, `requireResumeApproval`, `ClientName`, and `WebSocketTarget`.
 3.40 | Added `IsolateFlag` object and `isolateFlags` property to `Isolate`.
+3.41 | Added `PortList` object, `ReceivePort` `InstanceKind`, and `getPorts` RPC.
 
 [discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss
diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h
index 40b953c..d8f11c2 100644
--- a/runtime/vm/symbols.h
+++ b/runtime/vm/symbols.h
@@ -447,6 +447,7 @@
   V(_handleMessage, "_handleMessage")                                          \
   V(_instanceOf, "_instanceOf")                                                \
   V(_lookupHandler, "_lookupHandler")                                          \
+  V(_lookupOpenPorts, "_lookupOpenPorts")                                      \
   V(_name, "_name")                                                            \
   V(_onData, "_onData")                                                        \
   V(_rehashObjects, "_rehashObjects")                                          \
diff --git a/sdk/lib/_internal/vm/lib/isolate_patch.dart b/sdk/lib/_internal/vm/lib/isolate_patch.dart
index 91a567d..97e90ce 100644
--- a/sdk/lib/_internal/vm/lib/isolate_patch.dart
+++ b/sdk/lib/_internal/vm/lib/isolate_patch.dart
@@ -22,7 +22,8 @@
 @patch
 class ReceivePort {
   @patch
-  factory ReceivePort() => new _ReceivePortImpl();
+  factory ReceivePort([String debugName = '']) =>
+      new _ReceivePortImpl(debugName);
 
   @patch
   factory ReceivePort.fromRawReceivePort(RawReceivePort rawPort) {
@@ -62,15 +63,16 @@
    * event is received.
    */
   @patch
-  factory RawReceivePort([Function? handler]) {
-    _RawReceivePortImpl result = new _RawReceivePortImpl();
+  factory RawReceivePort([Function? handler, String debugName = '']) {
+    _RawReceivePortImpl result = new _RawReceivePortImpl(debugName);
     result.handler = handler;
     return result;
   }
 }
 
 class _ReceivePortImpl extends Stream implements ReceivePort {
-  _ReceivePortImpl() : this.fromRawReceivePort(new RawReceivePort());
+  _ReceivePortImpl([String debugName = ''])
+      : this.fromRawReceivePort(new RawReceivePort(null, debugName));
 
   _ReceivePortImpl.fromRawReceivePort(this._rawPort)
       : _controller = new StreamController(sync: true) {
@@ -128,11 +130,20 @@
 
 @pragma("vm:entry-point")
 class _RawReceivePortImpl implements RawReceivePort {
-  factory _RawReceivePortImpl() native "RawReceivePortImpl_factory";
+  factory _RawReceivePortImpl(String debugName) {
+    final port = _RawReceivePortImpl._(debugName);
+    _portMap[port._get_id()] = <String, dynamic>{
+      'port': port,
+    };
+    return port;
+  }
+
+  factory _RawReceivePortImpl._(String debugName)
+      native "RawReceivePortImpl_factory";
 
   close() {
     // Close the port and remove it from the handler map.
-    _handlerMap.remove(this._closeInternal());
+    _portMap.remove(this._closeInternal());
   }
 
   SendPort get sendPort {
@@ -155,10 +166,15 @@
   // Called from the VM to retrieve the handler for a message.
   @pragma("vm:entry-point", "call")
   static _lookupHandler(int id) {
-    var result = _handlerMap[id];
+    var result = _portMap[id]?['handler'];
     return result;
   }
 
+  @pragma("vm:entry-point", "call")
+  static _lookupOpenPorts() {
+    return _portMap.values.map((e) => e['port']).toList();
+  }
+
   // Called from the VM to dispatch to the handler.
   @pragma("vm:entry-point", "call")
   static void _handleMessage(Function handler, var message) {
@@ -173,22 +189,16 @@
   _closeInternal() native "RawReceivePortImpl_closeInternal";
 
   void set handler(Function? value) {
-    _handlerMap[this._get_id()] = value;
+    final id = this._get_id();
+    if (!_portMap.containsKey(id)) {
+      _portMap[id] = <String, dynamic>{
+        'port': this,
+      };
+    }
+    _portMap[id]!['handler'] = value;
   }
 
-  // TODO(iposva): Ideally keep this map in the VM.
-  // id to handler mapping.
-  static _initHandlerMap() {
-    // TODO(18511): Workaround bad CheckSmi hoisting.
-    var tempMap = new HashMap();
-    // Collect feedback that not all keys are Smis.
-    tempMap["."] = 1;
-    tempMap["."] = 2;
-
-    return new HashMap();
-  }
-
-  static final Map _handlerMap = _initHandlerMap();
+  static final _portMap = <dynamic, Map<String, dynamic>>{};
 }
 
 @pragma("vm:entry-point")
diff --git a/sdk/lib/isolate/isolate.dart b/sdk/lib/isolate/isolate.dart
index 0c5747e..b7650dd 100644
--- a/sdk/lib/isolate/isolate.dart
+++ b/sdk/lib/isolate/isolate.dart
@@ -671,9 +671,12 @@
    * receive messages. See [Stream.asBroadcastStream] for transforming the port
    * to a broadcast stream.
    *
+   * The optional `debugName` parameter can be set to associate a name with
+   * this port that can be displayed in tooling.
+   *
    * A receive port is closed by canceling its subscription.
    */
-  external factory ReceivePort();
+  external factory ReceivePort([String debugName = '']);
 
   /**
    * Creates a [ReceivePort] from a [RawReceivePort].
@@ -718,8 +721,12 @@
    * A [RawReceivePort] is low level and does not work with [Zone]s. It
    * can not be paused. The data-handler must be set before the first
    * event is received.
+   *
+   * The optional `debugName` parameter can be set to associate a name with
+   * this port that can be displayed in tooling.
+   *
    */
-  external factory RawReceivePort([Function? handler]);
+  external factory RawReceivePort([Function? handler, String debugName = '']);
 
   /**
    * Sets the handler that is invoked for every incoming message.