[VM/Developer] Expose through the developer API a gc count for the isolate group that could potentially trigger execution of associated finalizers of objects that were collected.

TEST=reachability_test.dart

Change-Id: I27bdac49a4b7069ca428282d55d8ec572c9fc0e0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/266424
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Siva Annamalai <asiva@google.com>
This commit is contained in:
asiva 2022-11-09 19:15:04 +00:00 committed by Commit Queue
parent f915ad9e87
commit b464175a29
12 changed files with 120 additions and 5 deletions

View file

@ -9,6 +9,8 @@
#include "vm/debugger.h"
#include "vm/exceptions.h"
#include "vm/flags.h"
#include "vm/heap/heap.h"
#include "vm/isolate.h"
#include "vm/message.h"
#include "vm/native_entry.h"
#include "vm/object.h"
@ -164,4 +166,16 @@ DEFINE_NATIVE_ENTRY(Developer_getIsolateIDFromSendPort, 0, 1) {
#endif
}
DEFINE_NATIVE_ENTRY(Developer_reachability_barrier, 0, 0) {
#if defined(PRODUCT)
return Smi::New(0);
#else
IsolateGroup* isolate_group = thread->isolate_group();
ASSERT(isolate_group != nullptr);
Heap* heap = isolate_group->heap();
ASSERT(heap != nullptr);
return Integer::New(heap->ReachabilityBarrier());
#endif
}
} // namespace dart

View file

@ -0,0 +1,37 @@
// Copyright (c) 2022, 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=--new_gen_semi_max_size=1
import 'dart:_internal' show VMInternalsForTesting;
import 'dart:developer';
import 'package:expect/expect.dart';
var g_arr = List<List<int>?>.filled(1000, null);
void main() {
var count = reachabilityBarrier;
var l_arr = List<List<int>?>.filled(1000, null);
for (int i = 0; i < 1000; i++) {
l_arr[i] = List<int>.filled(100, 0);
}
for (int j = 0; j < 1000; j++) {
for (int i = 0; i < 1000; i++) {
g_arr[i] = l_arr[i];
}
for (int i = 0; i < 1000; i++) {
l_arr[i] = List<int>.filled(100, 0);
}
}
VMInternalsForTesting.collectAllGarbage();
for (int j = 0; j < 1000; j++) {
for (int i = 0; i < 1000; i++) {
g_arr[i] = l_arr[i];
}
for (int i = 0; i < 1000; i++) {
l_arr[i] = List<int>.filled(100, 0);
}
}
VMInternalsForTesting.collectAllGarbage();
Expect.isTrue(reachabilityBarrier > count);
}

View file

@ -80,6 +80,7 @@ namespace dart {
V(Developer_log, 8) \
V(Developer_postEvent, 2) \
V(Developer_webServerControl, 3) \
V(Developer_reachability_barrier, 0) \
V(Double_hashCode, 1) \
V(Double_getIsNegative, 1) \
V(Double_getIsInfinite, 1) \

View file

@ -58,6 +58,8 @@ Heap::Heap(IsolateGroup* isolate_group,
old_weak_tables_[sel] = new WeakTable();
}
stats_.num_ = 0;
stats_.state_ = kInitial;
stats_.reachability_barrier_ = 0;
}
Heap::~Heap() {

View file

@ -51,6 +51,20 @@ class Heap {
kNumWeakSelectors
};
// States for a state machine that represents the worst-case set of GCs
// that an unreachable object could survive before begin collected:
// a new-space object that is involved with a cycle with an old-space object
// is copied to survivor space, then promoted during concurrent marking,
// and finally proven unreachable in the next round of old-gen marking.
// We ignore the case of unreachable-but-not-yet-collected objects being
// made reachable again by allInstances.
enum LeakCountState {
kInitial = 0,
kFirstScavenge,
kSecondScavenge,
kMarkingStart,
};
// Pattern for unused new space and swept old space.
static const uint8_t kZapByte = 0xf3;
@ -273,6 +287,8 @@ class Heap {
JSONStream* stream) {
old_space_.PrintHeapMapToJSONStream(isolate_group, stream);
}
intptr_t ReachabilityBarrier() { return stats_.reachability_barrier_; }
#endif // PRODUCT
IsolateGroup* isolate_group() const { return isolate_group_; }
@ -296,6 +312,8 @@ class Heap {
intptr_t num_;
GCType type_;
GCReason reason_;
LeakCountState state_; // State to track finalization of GCed object.
intptr_t reachability_barrier_; // Tracks reachability of GCed objects.
class Data : public ValueObject {
public:

View file

@ -479,9 +479,17 @@ class MarkingWeakVisitor : public HandleVisitor {
void GCMarker::Prologue() {
isolate_group_->ReleaseStoreBuffers();
if (heap_->stats_.state_ == Heap::kSecondScavenge) {
heap_->stats_.state_ = Heap::kMarkingStart;
}
}
void GCMarker::Epilogue() {}
void GCMarker::Epilogue() {
if (heap_->stats_.state_ == Heap::kMarkingStart) {
heap_->stats_.state_ = Heap::kInitial;
heap_->stats_.reachability_barrier_ += 1;
}
}
enum RootSlices {
kIsolate = 0,

View file

@ -1696,10 +1696,17 @@ void Scavenger::Scavenge(Thread* thread, GCType type, GCReason reason) {
if (abort_) {
ReverseScavenge(&from);
bytes_promoted = 0;
} else if ((CapacityInWords() - UsedInWords()) < KBInWords) {
// Don't scavenge again until the next old-space GC has occurred. Prevents
// performing one scavenge per allocation as the heap limit is approached.
heap_->assume_scavenge_will_fail_ = true;
} else {
if (heap_->stats_.state_ == Heap::kInitial) {
heap_->stats_.state_ = Heap::kFirstScavenge;
} else if (heap_->stats_.state_ == Heap::kFirstScavenge) {
heap_->stats_.state_ = Heap::kSecondScavenge;
}
if ((CapacityInWords() - UsedInWords()) < KBInWords) {
// Don't scavenge again until the next old-space GC has occurred. Prevents
// performing one scavenge per allocation as the heap limit is approached.
heap_->assume_scavenge_will_fail_ = true;
}
}
ASSERT(promotion_stack_.IsEmpty());
MournWeakHandles();

View file

@ -60,6 +60,9 @@ void log(String message,
JS('', 'console.debug("dart.developer.log", #)', items);
}
@patch
int get reachabilityBarrier => 0;
final _extensions = <String, ServiceExtensionHandler>{};
@patch

View file

@ -35,6 +35,9 @@ void log(String message,
// TODO.
}
@patch
int get reachabilityBarrier => 0;
final _extensions = <String, ServiceExtensionHandler>{};
@patch

View file

@ -50,6 +50,10 @@ void log(String message,
error, stackTrace);
}
@patch
@pragma("vm:external-name", "Developer_reachability_barrier")
external int get reachabilityBarrier;
int _nextSequenceNumber = 0;
@pragma("vm:external-name", "Developer_log")

View file

@ -26,6 +26,9 @@ void log(String message,
Object? error,
StackTrace? stackTrace}) {}
@patch
int get reachabilityBarrier => 0;
@patch
bool get extensionStreamHasListener => false;

View file

@ -67,3 +67,18 @@ external void log(
Object? error,
StackTrace? stackTrace,
});
/// Current reachability barrier state.
///
/// A reachability barrier state that provides a way to synchronize on
/// reachability. At value 'x', any object that became unreachable during
/// 'value' < 'x' has been collected and any associated finalizers have been
/// scheduled for execution, i.e. the non-execution of a finalizer reliably
/// indicates the object is still reachable in the previous barrier state.
///
/// Objects that became unreachable in the current barrier state may have not
/// yet been collected or finalized.
///
/// NOTE: There are no guarantees of forward progress. An implementation may
/// return the same value forever for this barrier state.
external int get reachabilityBarrier;