[ VM / Service ] Add support for enabling/disabling breakpoints

This change adds a `setBreakpointState` RPC which allows for breakpoints
to be enabled or disabled without needing to remove and recreate
breakpoints.

Fixes https://github.com/dart-lang/sdk/issues/45336.

TEST=set_breakpoint_state_test.dart

Change-Id: I1a04e6028d4e4560fdb8d3d26420c9a05da06b4b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/193896
Commit-Queue: Ben Konyi <bkonyi@google.com>
Reviewed-by: Alexander Aprelev <aam@google.com>
diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json
index 4064188..b5ad7d0 100644
--- a/.dart_tool/package_config.json
+++ b/.dart_tool/package_config.json
@@ -11,7 +11,7 @@
     "constraint, update this by running tools/generate_package_config.dart."
   ],
   "configVersion": 2,
-  "generated": "2021-04-06T14:39:06.316531",
+  "generated": "2021-04-06T11:20:39.965073",
   "generator": "tools/generate_package_config.dart",
   "packages": [
     {
@@ -728,6 +728,12 @@
       "languageVersion": "2.12"
     },
     {
+      "name": "uuid",
+      "rootUri": "../third_party/pkg/uuid",
+      "packageUri": "lib/",
+      "languageVersion": "2.0"
+    },
+    {
       "name": "vector_math",
       "rootUri": "../third_party/pkg/vector_math",
       "packageUri": "lib/",
diff --git a/pkg/vm_service/CHANGELOG.md b/pkg/vm_service/CHANGELOG.md
index 2492753..5f0da4d 100644
--- a/pkg/vm_service/CHANGELOG.md
+++ b/pkg/vm_service/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Changelog
 
+## 6.3.0-dev
+- Add support for `setBreakpointState` RPC and updated `Breakpoint` class to include
+  `enabled` property.
+
 ## 6.2.0
 - Added support for `getHttpProfile` and `clearHttpProfile` `dart:io` service extensions.
 
diff --git a/pkg/vm_service/example/vm_service_assert.dart b/pkg/vm_service/example/vm_service_assert.dart
index 7122a12..c861f82 100644
--- a/pkg/vm_service/example/vm_service_assert.dart
+++ b/pkg/vm_service/example/vm_service_assert.dart
@@ -136,6 +136,7 @@
   if (obj == "BreakpointAdded") return obj;
   if (obj == "BreakpointRemoved") return obj;
   if (obj == "BreakpointResolved") return obj;
+  if (obj == "BreakpointUpdated") return obj;
   if (obj == "Extension") return obj;
   if (obj == "GC") return obj;
   if (obj == "Inspect") return obj;
@@ -293,6 +294,7 @@
   assertNotNull(obj);
   assertString(obj.id!);
   assertInt(obj.breakpointNumber!);
+  assertBool(obj.enabled!);
   assertBool(obj.resolved!);
   if (obj.location is vms.SourceLocation) {
     assertSourceLocation(obj.location!);
diff --git a/pkg/vm_service/java/.gitignore b/pkg/vm_service/java/.gitignore
index 7e45717..cdc39af 100644
--- a/pkg/vm_service/java/.gitignore
+++ b/pkg/vm_service/java/.gitignore
@@ -4,6 +4,7 @@
 src/org/dartlang/vm/service/consumer/AddBreakpointAtEntryConsumer.java
 src/org/dartlang/vm/service/consumer/AddBreakpointConsumer.java
 src/org/dartlang/vm/service/consumer/AddBreakpointWithScriptUriConsumer.java
+src/org/dartlang/vm/service/consumer/BreakpointConsumer.java
 src/org/dartlang/vm/service/consumer/ClearCpuSamplesConsumer.java
 src/org/dartlang/vm/service/consumer/CpuSamplesConsumer.java
 src/org/dartlang/vm/service/consumer/EvaluateConsumer.java
diff --git a/pkg/vm_service/java/version.properties b/pkg/vm_service/java/version.properties
index 452573e..d934516 100644
--- a/pkg/vm_service/java/version.properties
+++ b/pkg/vm_service/java/version.properties
@@ -1 +1 @@
-version=3.44
+version=3.45
diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart
index 8b869fb..50fe029 100644
--- a/pkg/vm_service/lib/src/vm_service.dart
+++ b/pkg/vm_service/lib/src/vm_service.dart
@@ -26,7 +26,7 @@
         HeapSnapshotObjectNoData,
         HeapSnapshotObjectNullData;
 
-const String vmServiceVersion = '3.44.0';
+const String vmServiceVersion = '3.45.0';
 
 /// @optional
 const String optional = 'optional';
@@ -229,6 +229,7 @@
   'removeBreakpoint': const ['Success'],
   'requestHeapSnapshot': const ['Success'],
   'resume': const ['Success'],
+  'setBreakpointState': const ['Breakpoint'],
   'setExceptionPauseMode': const ['Success'],
   'setFlag': const ['Success', 'Error'],
   'setLibraryDebuggable': const ['Success'],
@@ -1008,6 +1009,18 @@
   Future<Success> resume(String isolateId,
       {/*StepOption*/ String? step, int? frameIndex});
 
+  /// The `setBreakpointState` RPC allows for breakpoints to be enabled or
+  /// disabled, without requiring for the breakpoint to be completely removed.
+  ///
+  /// If `isolateId` refers to an isolate which has exited, then the `Collected`
+  /// [Sentinel] is returned.
+  ///
+  /// The returned [Breakpoint] is the updated breakpoint with its new values.
+  ///
+  /// See [Breakpoint].
+  Future<Breakpoint> setBreakpointState(
+      String isolateId, String breakpointId, bool enable);
+
   /// The `setExceptionPauseMode` RPC is used to control if an isolate pauses
   /// when an exception is thrown.
   ///
@@ -1136,7 +1149,7 @@
   /// IsolateReload, ServiceExtensionAdded
   /// Debug | PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted,
   /// PauseException, PausePostRequest, Resume, BreakpointAdded,
-  /// BreakpointResolved, BreakpointRemoved, Inspect, None
+  /// BreakpointResolved, BreakpointRemoved, BreakpointUpdated, Inspect, None
   /// GC | GC
   /// Extension | Extension
   /// Timeline | TimelineEvents, TimelineStreamsSubscriptionUpdate
@@ -1471,6 +1484,13 @@
             frameIndex: params['frameIndex'],
           );
           break;
+        case 'setBreakpointState':
+          response = await _serviceImplementation.setBreakpointState(
+            params!['isolateId'],
+            params['breakpointId'],
+            params['enable'],
+          );
+          break;
         case 'setExceptionPauseMode':
           response = await _serviceImplementation.setExceptionPauseMode(
             params!['isolateId'],
@@ -1667,7 +1687,7 @@
   // IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, IsolateReload, ServiceExtensionAdded
   Stream<Event> get onIsolateEvent => _getEventController('Isolate').stream;
 
-  // PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted, PauseException, PausePostRequest, Resume, BreakpointAdded, BreakpointResolved, BreakpointRemoved, Inspect, None
+  // PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted, PauseException, PausePostRequest, Resume, BreakpointAdded, BreakpointResolved, BreakpointRemoved, BreakpointUpdated, Inspect, None
   Stream<Event> get onDebugEvent => _getEventController('Debug').stream;
 
   // GC
@@ -1982,6 +2002,15 @@
       });
 
   @override
+  Future<Breakpoint> setBreakpointState(
+          String isolateId, String breakpointId, bool enable) =>
+      _call('setBreakpointState', {
+        'isolateId': isolateId,
+        'breakpointId': breakpointId,
+        'enable': enable
+      });
+
+  @override
   Future<Success> setExceptionPauseMode(
           String isolateId, /*ExceptionPauseMode*/ String mode) =>
       _call('setExceptionPauseMode', {'isolateId': isolateId, 'mode': mode});
@@ -2430,6 +2459,9 @@
   /// A breakpoint has been removed.
   static const String kBreakpointRemoved = 'BreakpointRemoved';
 
+  /// A breakpoint has been updated.
+  static const String kBreakpointUpdated = 'BreakpointUpdated';
+
   /// A garbage collection event.
   static const String kGC = 'GC';
 
@@ -2810,6 +2842,9 @@
   /// A number identifying this breakpoint to the user.
   int? breakpointNumber;
 
+  /// Is this breakpoint enabled?
+  bool? enabled;
+
   /// Has this breakpoint been assigned to a specific program location?
   bool? resolved;
 
@@ -2826,6 +2861,7 @@
 
   Breakpoint({
     required this.breakpointNumber,
+    required this.enabled,
     required this.resolved,
     required this.location,
     required String id,
@@ -2836,6 +2872,7 @@
 
   Breakpoint._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
     breakpointNumber = json['breakpointNumber'] ?? -1;
+    enabled = json['enabled'] ?? false;
     resolved = json['resolved'] ?? false;
     isSyntheticAsyncContinuation = json['isSyntheticAsyncContinuation'];
     location = createServiceObject(json['location']!,
@@ -2851,6 +2888,7 @@
     json['type'] = type;
     json.addAll({
       'breakpointNumber': breakpointNumber,
+      'enabled': enabled,
       'resolved': resolved,
       'location': location?.toJson(),
     });
@@ -2864,8 +2902,8 @@
   operator ==(other) => other is Breakpoint && id == other.id;
 
   String toString() => '[Breakpoint ' //
-      'id: ${id}, breakpointNumber: ${breakpointNumber}, resolved: ${resolved}, ' //
-      'location: ${location}]';
+      'id: ${id}, breakpointNumber: ${breakpointNumber}, enabled: ${enabled}, ' //
+      'resolved: ${resolved}, location: ${location}]';
 }
 
 /// `ClassRef` is a reference to a `Class`.
@@ -3685,6 +3723,7 @@
   ///  - BreakpointAdded
   ///  - BreakpointRemoved
   ///  - BreakpointResolved
+  ///  - BreakpointUpdated
   @optional
   Breakpoint? breakpoint;
 
diff --git a/pkg/vm_service/pubspec.yaml b/pkg/vm_service/pubspec.yaml
index 5ba220b..dfe2e30 100644
--- a/pkg/vm_service/pubspec.yaml
+++ b/pkg/vm_service/pubspec.yaml
@@ -3,7 +3,7 @@
   A library to communicate with a service implementing the Dart VM
   service protocol.
 
-version: 6.2.0
+version: 6.3.0-dev
 
 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/vm_service
 
diff --git a/pkg/vm_service/test/set_breakpoint_state_test.dart b/pkg/vm_service/test/set_breakpoint_state_test.dart
new file mode 100644
index 0000000..7bc96ab
--- /dev/null
+++ b/pkg/vm_service/test/set_breakpoint_state_test.dart
@@ -0,0 +1,71 @@
+// Copyright (c) 2021, 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=--verbose_debug
+
+import 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+
+import 'common/service_test_common.dart';
+import 'common/test_helper.dart';
+
+const int LINE_A = 17;
+const int LINE_B = LINE_A + 1;
+
+testMain() {
+  while (true) {
+    print('a'); // LINE_A
+    print('b'); // LINE_B
+  }
+}
+
+late Breakpoint bpt;
+
+var tests = <IsolateTest>[
+  hasPausedAtStart,
+  (VmService service, IsolateRef isolateRef) async {
+    bpt = await service.addBreakpointWithScriptUri(
+      isolateRef.id!,
+      'set_breakpoint_state_test.dart',
+      LINE_A,
+    );
+    expect(bpt.enabled, true);
+  },
+  setBreakpointAtLine(LINE_B),
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_A),
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_B),
+  (VmService service, IsolateRef isolateRef) async {
+    bpt = await service.setBreakpointState(
+      isolateRef.id!,
+      bpt.id!,
+      false,
+    );
+    expect(bpt.enabled, false);
+  },
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_B),
+  (VmService service, IsolateRef isolateRef) async {
+    bpt = await service.setBreakpointState(
+      isolateRef.id!,
+      bpt.id!,
+      true,
+    );
+    expect(bpt.enabled, true);
+  },
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_A),
+];
+
+main([args = const <String>[]]) => runIsolateTests(
+      args,
+      tests,
+      'set_breakpoint_state_test.dart',
+      pause_on_start: true,
+      testeeConcurrent: testMain,
+    );
diff --git a/runtime/observatory/lib/src/service/object.dart b/runtime/observatory/lib/src/service/object.dart
index 1516d15..e32c00f 100644
--- a/runtime/observatory/lib/src/service/object.dart
+++ b/runtime/observatory/lib/src/service/object.dart
@@ -2407,6 +2407,9 @@
   // Either SourceLocation or UnresolvedSourceLocation.
   Location? location;
 
+  // Is the breakpoint enabled?
+  bool? enabled;
+
   // The breakpoint is in a file which is not yet loaded.
   bool? latent;
 
@@ -2425,6 +2428,7 @@
     // number never changes.
     assert((number == null) || (number == newNumber));
     number = newNumber;
+    enabled = map['enabled'];
     resolved = map['resolved'];
 
     var oldLocation = location;
@@ -2448,6 +2452,15 @@
     assert(resolved! || location is UnresolvedSourceLocation);
   }
 
+  Future<void> setState(bool enable) {
+    return location!.script.isolate!.invokeRpcNoUpgrade('setBreakpointState', {
+      'breakpointId': 'breakpoints/$number',
+      'enable': enable,
+    }).then((Map result) {
+      _update(result, false);
+    });
+  }
+
   void remove() {
     location!.script._removeBreakpoint(this);
   }
diff --git a/runtime/observatory/tests/service/get_version_rpc_test.dart b/runtime/observatory/tests/service/get_version_rpc_test.dart
index b63155d..1fe66c5 100644
--- a/runtime/observatory/tests/service/get_version_rpc_test.dart
+++ b/runtime/observatory/tests/service/get_version_rpc_test.dart
@@ -12,7 +12,7 @@
     final result = await vm.invokeRpcNoUpgrade('getVersion', {});
     expect(result['type'], 'Version');
     expect(result['major'], 3);
-    expect(result['minor'], 44);
+    expect(result['minor'], 45);
     expect(result['_privateMajor'], 0);
     expect(result['_privateMinor'], 0);
   },
diff --git a/runtime/observatory/tests/service/service_kernel.status b/runtime/observatory/tests/service/service_kernel.status
index 3ddf48d..291688c 100644
--- a/runtime/observatory/tests/service/service_kernel.status
+++ b/runtime/observatory/tests/service/service_kernel.status
@@ -175,6 +175,7 @@
 reload_sources_test: SkipByDesign #  Hot reload is disabled in AOT mode.
 rewind_optimized_out_test: SkipByDesign # Debugger is disabled in AOT mode.
 rewind_test: SkipByDesign # Debugger is disabled in AOT mode.
+set_breakpoint_state_test: SkipByDesign # Debugger is disabled in AOT mode.
 set_library_debuggable_test: SkipByDesign # Debugger is disabled in AOT mode.
 sigquit_starts_service_test: SkipByDesign # Spawns a secondary process using Platform.executable.
 simple_reload_test: SkipByDesign # Hot reload is disabled in AOT mode.
diff --git a/runtime/observatory/tests/service/set_breakpoint_state_test.dart b/runtime/observatory/tests/service/set_breakpoint_state_test.dart
new file mode 100644
index 0000000..fdcee6a
--- /dev/null
+++ b/runtime/observatory/tests/service/set_breakpoint_state_test.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2021, 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=--verbose_debug
+
+import 'package:observatory/service_io.dart';
+import 'package:test/test.dart';
+
+import 'service_test_common.dart';
+import 'test_helper.dart';
+
+const int LINE_A = 17;
+const int LINE_B = LINE_A + 1;
+
+testMain() {
+  while (true) {
+    print('a'); // LINE_A
+    print('b'); // LINE_B
+  }
+}
+
+late Breakpoint bpt;
+
+var tests = <IsolateTest>[
+  hasPausedAtStart,
+  (Isolate isolate) async {
+    bpt = await isolate.addBreakpointByScriptUri(
+      'set_breakpoint_state_test.dart',
+      LINE_A,
+    );
+    expect(bpt.enabled, true);
+  },
+  setBreakpointAtLine(LINE_B),
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_A),
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_B),
+  (Isolate isolate) async {
+    await bpt.setState(
+      false,
+    );
+    expect(bpt.enabled, false);
+  },
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_B),
+  (Isolate isolate) async {
+    await bpt.setState(
+      true,
+    );
+    expect(bpt.enabled, true);
+  },
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_A),
+];
+
+main([args = const <String>[]]) => runIsolateTests(
+      args,
+      tests,
+      pause_on_start: true,
+      testeeConcurrent: testMain,
+    );
diff --git a/runtime/observatory_2/lib/src/service/object.dart b/runtime/observatory_2/lib/src/service/object.dart
index 1c5c9bc..743ab24 100644
--- a/runtime/observatory_2/lib/src/service/object.dart
+++ b/runtime/observatory_2/lib/src/service/object.dart
@@ -2418,6 +2418,9 @@
   // Either SourceLocation or UnresolvedSourceLocation.
   Location location;
 
+  // Is the breakpoint enabled?
+  bool enabled;
+
   // The breakpoint is in a file which is not yet loaded.
   bool latent;
 
@@ -2436,6 +2439,7 @@
     // number never changes.
     assert((number == null) || (number == newNumber));
     number = newNumber;
+    enabled = map['enabled'];
     resolved = map['resolved'];
 
     var oldLocation = location;
@@ -2459,6 +2463,15 @@
     assert(resolved || location is UnresolvedSourceLocation);
   }
 
+  Future<void> setState(bool enable) {
+    return location.script.isolate.invokeRpcNoUpgrade('setBreakpointState', {
+      'breakpointId': 'breakpoints/$number',
+      'enable': enable,
+    }).then((Map result) {
+      _update(result, false);
+    });
+  }
+
   void remove() {
     location.script._removeBreakpoint(this);
   }
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 e7a9528..bd9d9b1 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(44));
+    expect(result['minor'], equals(45));
     expect(result['_privateMajor'], equals(0));
     expect(result['_privateMinor'], equals(0));
   },
diff --git a/runtime/observatory_2/tests/service_2/service_2_kernel.status b/runtime/observatory_2/tests/service_2/service_2_kernel.status
index 099e686..717b43e 100644
--- a/runtime/observatory_2/tests/service_2/service_2_kernel.status
+++ b/runtime/observatory_2/tests/service_2/service_2_kernel.status
@@ -174,6 +174,7 @@
 reload_sources_test: SkipByDesign # Hot reload is disabled in AOT mode.
 rewind_optimized_out_test: SkipByDesign # Debugger is disabled in AOT mode.
 rewind_test: SkipByDesign # Debugger is disabled in AOT mode.
+set_breakpoint_state_test: SkipByDesign # Debugger is disabled in AOT mode.
 set_library_debuggable_test: SkipByDesign # Debugger is disabled in AOT mode.
 sigquit_starts_service_test: SkipByDesign # Spawns a secondary process using Platform.executable.
 simple_reload_test: SkipByDesign # Hot reload is disabled in AOT mode.
diff --git a/runtime/observatory_2/tests/service_2/set_breakpoint_state_test.dart b/runtime/observatory_2/tests/service_2/set_breakpoint_state_test.dart
new file mode 100644
index 0000000..78e0fcb
--- /dev/null
+++ b/runtime/observatory_2/tests/service_2/set_breakpoint_state_test.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2021, 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=--verbose_debug
+
+import 'package:observatory_2/service_io.dart';
+import 'package:test/test.dart';
+
+import 'service_test_common.dart';
+import 'test_helper.dart';
+
+const int LINE_A = 17;
+const int LINE_B = LINE_A + 1;
+
+testMain() {
+  while (true) {
+    print('a'); // LINE_A
+    print('b'); // LINE_B
+  }
+}
+
+Breakpoint bpt;
+
+var tests = <IsolateTest>[
+  hasPausedAtStart,
+  (Isolate isolate) async {
+    bpt = await isolate.addBreakpointByScriptUri(
+      'set_breakpoint_state_test.dart',
+      LINE_A,
+    );
+    expect(bpt.enabled, true);
+  },
+  setBreakpointAtLine(LINE_B),
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_A),
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_B),
+  (Isolate isolate) async {
+    await bpt.setState(
+      false,
+    );
+    expect(bpt.enabled, false);
+  },
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_B),
+  (Isolate isolate) async {
+    await bpt.setState(
+      true,
+    );
+    expect(bpt.enabled, true);
+  },
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_A),
+];
+
+main([args = const <String>[]]) => runIsolateTests(
+      args,
+      tests,
+      pause_on_start: true,
+      testeeConcurrent: testMain,
+    );
diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc
index 592a7e5..cfa5e5e 100644
--- a/runtime/vm/debugger.cc
+++ b/runtime/vm/debugger.cc
@@ -109,7 +109,14 @@
 }
 
 bool BreakpointLocation::AnyEnabled() const {
-  return breakpoints() != NULL;
+  Breakpoint* bpt = breakpoints();
+  while (bpt != nullptr) {
+    if (bpt->is_enabled()) {
+      return true;
+    }
+    bpt = bpt->next();
+  }
+  return false;
 }
 
 void BreakpointLocation::SetResolved(const Function& func,
@@ -175,6 +182,7 @@
   jsobj.AddProperty("type", "Breakpoint");
 
   jsobj.AddFixedServiceId("breakpoints/%" Pd "", id());
+  jsobj.AddProperty("enabled", enabled_);
   jsobj.AddProperty("breakpointNumber", id());
   if (is_synthetic_async()) {
     jsobj.AddProperty("isSyntheticAsyncContinuation", is_synthetic_async());
@@ -358,7 +366,7 @@
 void BreakpointLocation::AddBreakpoint(Breakpoint* bpt, Debugger* dbg) {
   bpt->set_next(breakpoints());
   set_breakpoints(bpt);
-
+  bpt->Enable();
   dbg->group_debugger()->SyncBreakpointLocation(this);
   dbg->SendBreakpointEvent(ServiceEvent::kBreakpointAdded, bpt);
 }
@@ -2945,7 +2953,6 @@
 // associated with the breakpoint location loc.
 void GroupDebugger::SyncBreakpointLocation(BreakpointLocation* loc) {
   bool any_enabled = loc->AnyEnabled();
-
   SafepointWriteRwLocker sl(Thread::Current(), code_breakpoints_lock());
   CodeBreakpoint* cbpt = code_breakpoints_;
   while (cbpt != NULL) {
@@ -4283,6 +4290,21 @@
   return Code::null();
 }
 
+bool Debugger::SetBreakpointState(Breakpoint* bpt, bool enable) {
+  SafepointWriteRwLocker sl(Thread::Current(),
+                            group_debugger()->breakpoint_locations_lock());
+  if (bpt->is_enabled() != enable) {
+    if (FLAG_verbose_debug) {
+      OS::PrintErr("Setting breakpoint %" Pd " to state: %s\n", bpt->id(),
+                   enable ? "enabled" : "disabled");
+    }
+    enable ? bpt->Enable() : bpt->Disable();
+    group_debugger()->SyncBreakpointLocation(bpt->bpt_location());
+    return true;
+  }
+  return false;
+}
+
 // Remove and delete the source breakpoint bpt and its associated
 // code breakpoints.
 void Debugger::RemoveBreakpoint(intptr_t bp_id) {
diff --git a/runtime/vm/debugger.h b/runtime/vm/debugger.h
index bd2d0de..b9d99de 100644
--- a/runtime/vm/debugger.h
+++ b/runtime/vm/debugger.h
@@ -81,6 +81,18 @@
     closure_ = closure.ptr();
   }
 
+  void Enable() {
+    ASSERT(!enabled_);
+    enabled_ = true;
+  }
+
+  void Disable() {
+    ASSERT(enabled_);
+    enabled_ = false;
+  }
+
+  bool is_enabled() const { return enabled_; }
+
   // Mark that this breakpoint is a result of a step OverAwait request.
   void set_is_synthetic_async(bool is_synthetic_async) {
     is_synthetic_async_ = is_synthetic_async;
@@ -105,6 +117,7 @@
   InstancePtr closure_;
   BreakpointLocation* bpt_location_;
   bool is_synthetic_async_;
+  bool enabled_ = false;
 
   friend class BreakpointLocation;
   DISALLOW_COPY_AND_ASSIGN(Breakpoint);
@@ -713,6 +726,9 @@
                                                   intptr_t line_number,
                                                   intptr_t column_number);
 
+  // Returns true if the breakpoint's state changed.
+  bool SetBreakpointState(Breakpoint* bpt, bool enable);
+
   void RemoveBreakpoint(intptr_t bp_id);
   Breakpoint* GetBreakpointById(intptr_t id);
 
diff --git a/runtime/vm/debugger_api_impl_test.cc b/runtime/vm/debugger_api_impl_test.cc
index 965764b..51ba8bd 100644
--- a/runtime/vm/debugger_api_impl_test.cc
+++ b/runtime/vm/debugger_api_impl_test.cc
@@ -149,7 +149,7 @@
     UNWRAP_AND_CHECK_PARAM(String, script_url, script_url_in);
 
     Debugger* debugger = I->debugger();
-    bpt = debugger->SetBreakpointAtLine(script_url, line_number);
+    bpt = debugger->SetBreakpointAtLineCol(script_url, line_number, -1);
     if (bpt == NULL) {
       return Api::NewError("%s: could not set breakpoint at line %" Pd
                            " in '%s'",
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index ee7a7ec..4c5578f 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -4805,6 +4805,36 @@
   return true;
 }
 
+static const MethodParameter* set_breakpoint_state_params[] = {
+    ISOLATE_PARAMETER,
+    new IdParameter("breakpointId", true),
+    new BoolParameter("enable", true),
+    nullptr,
+};
+
+static bool SetBreakpointState(Thread* thread, JSONStream* js) {
+  Isolate* isolate = thread->isolate();
+  const char* bpt_id = js->LookupParam("breakpointId");
+  bool enable = BoolParameter::Parse(js->LookupParam("enable"), true);
+  ObjectIdRing::LookupResult lookup_result;
+  Breakpoint* bpt = LookupBreakpoint(isolate, bpt_id, &lookup_result);
+  // TODO(bkonyi): Should we return a different error for bpts which
+  // have been already removed?
+  if (bpt == nullptr) {
+    PrintInvalidParamError(js, "breakpointId");
+    return true;
+  }
+  if (isolate->debugger()->SetBreakpointState(bpt, enable)) {
+    if (Service::debug_stream.enabled()) {
+      ServiceEvent event(isolate, ServiceEvent::kBreakpointUpdated);
+      event.set_breakpoint(bpt);
+      Service::HandleEvent(&event);
+    }
+  }
+  bpt->PrintJSON(js);
+  return true;
+}
+
 static const MethodParameter* get_flag_list_params[] = {
     NO_ISOLATE_PARAMETER,
     NULL,
@@ -5188,6 +5218,8 @@
     request_heap_snapshot_params },
   { "_evaluateCompiledExpression", EvaluateCompiledExpression,
     evaluate_compiled_expression_params },
+  { "setBreakpointState", SetBreakpointState,
+    set_breakpoint_state_params },
   { "setExceptionPauseMode", SetExceptionPauseMode,
     set_exception_pause_mode_params },
   { "setFlag", SetFlag,
diff --git a/runtime/vm/service.h b/runtime/vm/service.h
index 032fafd..0b94d3d 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 44
+#define SERVICE_PROTOCOL_MINOR_VERSION 45
 
 class Array;
 class EmbedderServiceHandler;
diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md
index e78af3e..99bdab7 100644
--- a/runtime/vm/service/service.md
+++ b/runtime/vm/service/service.md
@@ -1,8 +1,8 @@
-# Dart VM Service Protocol 3.44
+# Dart VM Service Protocol 3.45
 
 > Please post feedback to the [observatory-discuss group][discuss-list]
 
-This document describes of _version 3.44_ of the Dart VM Service Protocol. This
+This document describes of _version 3.45_ 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.
@@ -67,6 +67,7 @@
   - [reloadSources](#reloadsources)
   - [removeBreakpoint](#removebreakpoint)
   - [resume](#resume)
+  - [setBreakpointState](#setbreakpointstate)
   - [setExceptionPauseMode](#setexceptionpausemode)
   - [setFlag](#setflag)
   - [setLibraryDebuggable](#setlibrarydebuggable)
@@ -1305,6 +1306,24 @@
 
 See [Success](#success), [StepOption](#StepOption).
 
+### setBreakpointState
+
+```
+Breakpoint setBreakpointState(string isolateId,
+                              string breakpointId,
+                              bool enable)
+```
+
+The _setBreakpointState_ RPC allows for breakpoints to be enabled or disabled,
+without requiring for the breakpoint to be completely removed.
+
+If _isolateId_ refers to an isolate which has exited, then the
+_Collected_ [Sentinel](#sentinel) is returned.
+
+The returned [Breakpoint](#breakpoint) is the updated breakpoint with its new
+values.
+
+See [Breakpoint](#breakpoint).
 ### setExceptionPauseMode
 
 ```
@@ -1457,7 +1476,7 @@
 -------- | -----------
 VM | VMUpdate, VMFlagUpdate
 Isolate | IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, IsolateReload, ServiceExtensionAdded
-Debug | PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted, PauseException, PausePostRequest, Resume, BreakpointAdded, BreakpointResolved, BreakpointRemoved, Inspect, None
+Debug | PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted, PauseException, PausePostRequest, Resume, BreakpointAdded, BreakpointResolved, BreakpointRemoved, BreakpointUpdated, Inspect, None
 GC | GC
 Extension | Extension
 Timeline | TimelineEvents, TimelineStreamsSubscriptionUpdate
@@ -1639,6 +1658,9 @@
   // A number identifying this breakpoint to the user.
   int breakpointNumber;
 
+  // Is this breakpoint enabled?
+  bool enabled;
+
   // Has this breakpoint been assigned to a specific program location?
   bool resolved;
 
@@ -2002,6 +2024,7 @@
   //   BreakpointAdded
   //   BreakpointRemoved
   //   BreakpointResolved
+  //   BreakpointUpdated
   Breakpoint breakpoint [optional];
 
   // The list of breakpoints at which we are currently paused
@@ -2199,6 +2222,9 @@
   // A breakpoint has been removed.
   BreakpointRemoved,
 
+  // A breakpoint has been updated.
+  BreakpointUpdated,
+
   // A garbage collection event.
   GC,
 
@@ -4011,5 +4037,6 @@
 3.42 | Added `limit` optional parameter to `getStack` RPC.
 3.43 | Updated heap snapshot format to include identity hash codes. Added `getAllocationTraces` and `setTraceClassAllocation` RPCs, updated `CpuSample` to include `identityHashCode` and `classId` properties, updated `Class` to include `traceAllocations` property.
 3.44 | Added `identityHashCode` property to `@Instance` and `Instance`.
+3.45 | Added `setBreakpointState` RPC and `BreakpointUpdated` event kind.
 
 [discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss
diff --git a/runtime/vm/service_event.cc b/runtime/vm/service_event.cc
index aed7093..6b97b6d 100644
--- a/runtime/vm/service_event.cc
+++ b/runtime/vm/service_event.cc
@@ -117,6 +117,8 @@
       return "BreakpointResolved";
     case kBreakpointRemoved:
       return "BreakpointRemoved";
+    case kBreakpointUpdated:
+      return "BreakpointUpdated";
     case kGC:
       return "GC";  // TODO(koda): Change to GarbageCollected.
     case kInspect:
@@ -166,6 +168,7 @@
     case kBreakpointAdded:
     case kBreakpointResolved:
     case kBreakpointRemoved:
+    case kBreakpointUpdated:
     case kInspect:
     case kDebuggerSettingsUpdate:
       return &Service::debug_stream;
diff --git a/runtime/vm/service_event.h b/runtime/vm/service_event.h
index 08979ac..80aaa8a 100644
--- a/runtime/vm/service_event.h
+++ b/runtime/vm/service_event.h
@@ -44,6 +44,7 @@
     kBreakpointAdded,
     kBreakpointResolved,
     kBreakpointRemoved,
+    kBreakpointUpdated,
     kInspect,
     kDebuggerSettingsUpdate,
 
@@ -128,7 +129,8 @@
   Breakpoint* breakpoint() const { return breakpoint_; }
   void set_breakpoint(Breakpoint* bpt) {
     ASSERT(kind() == kPauseBreakpoint || kind() == kBreakpointAdded ||
-           kind() == kBreakpointResolved || kind() == kBreakpointRemoved);
+           kind() == kBreakpointResolved || kind() == kBreakpointRemoved ||
+           kind() == kBreakpointUpdated);
     breakpoint_ = bpt;
   }