mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 08:24:39 +00:00
[vm] Flush thread store buffer block after processing finalizers
`MournFinalized` runs during marking and can add objects to the store buffer. These objects are stored in the threads' store buffer block. This block needs to be released to the central store buffer in order for the objects' addresses to be updated during compacting. TEST=runtime/vm/object_test.cc TEST=tools/test.py vm/cc/Finalizer_Regress_48843 Closes: https://github.com/dart-lang/sdk/issues/48843 Change-Id: Ib2424929c86fee730d3f09fbd2f9f6c97f31abfd Cq-Include-Trybots: luci.dart.try:vm-canary-linux-debug-try,vm-kernel-linux-debug-x64-try,vm-kernel-linux-debug-x64c-try,vm-kernel-mac-debug-x64-try,vm-kernel-win-debug-x64-try,vm-kernel-linux-release-x64-try,vm-kernel-linux-product-x64-try,vm-kernel-linux-debug-ia32-try,vm-kernel-linux-release-ia32-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243262 Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Daco Harkes <dacoharkes@google.com> Reviewed-by: Ryan Macnak <rmacnak@google.com>
This commit is contained in:
parent
531e6e175f
commit
78f218eb4c
|
@ -233,6 +233,18 @@ void MournFinalized(GCVisitorType* visitor) {
|
|||
finalizer_dart->untag()->exchange_entries_collected(current_entry);
|
||||
current_entry->untag()->set_next(previous_head);
|
||||
const bool first_entry = previous_head.IsRawNull();
|
||||
|
||||
// If we're in the marker, we need to ensure that we release the store
|
||||
// buffer afterwards.
|
||||
// If we're in the scavenger and have the finalizer in old space and
|
||||
// a new space entry, we don't need to release the store buffer.
|
||||
if (!first_entry && previous_head->IsNewObject() &&
|
||||
current_entry->IsOldObject()) {
|
||||
TRACE_FINALIZER("Entry %p (old) next is %p (new)",
|
||||
current_entry->untag(), previous_head->untag());
|
||||
// We must release the thread's store buffer block.
|
||||
}
|
||||
|
||||
// Schedule calling Dart finalizer.
|
||||
if (first_entry) {
|
||||
Isolate* isolate = finalizer->untag()->isolate_;
|
||||
|
|
|
@ -273,6 +273,15 @@ class MarkingVisitorBase : public ObjectPointerVisitor {
|
|||
work_list_.Finalize();
|
||||
deferred_work_list_.Finalize();
|
||||
MournFinalized(this);
|
||||
// MournFinalized inserts newly discovered dead entries into the
|
||||
// linked list attached to the Finalizer. This might create
|
||||
// cross-generational references which might be added to the store
|
||||
// buffer. Release the store buffer to satisfy the invariant that
|
||||
// thread local store buffer is empty after marking and all references
|
||||
// are processed.
|
||||
// TODO(http://dartbug.com/48957): `thread_` can differ from
|
||||
// `Thread::Current()`.
|
||||
Thread::Current()->ReleaseStoreBuffer();
|
||||
}
|
||||
|
||||
void MournWeakProperties() {
|
||||
|
|
|
@ -5043,6 +5043,87 @@ REPEAT_512(FINALIZER_CROSS_GEN_TEST_CASE)
|
|||
|
||||
#undef FINALIZER_CROSS_GEN_TEST_CASE
|
||||
|
||||
// Force the marker to add a FinalizerEntry to the store buffer during marking.
|
||||
//
|
||||
// This test requires two entries, one in new space, one in old space.
|
||||
// The scavenger should run first, adding the entry to collected_entries.
|
||||
// The marker runs right after, swapping the collected_entries with the entry
|
||||
// in old space, _and_ setting the next field to the entry in new space.
|
||||
// This forces the entry to be added to the store-buffer _during_ marking.
|
||||
//
|
||||
// Then, the compacter needs to be used. Which will move the entry in old
|
||||
// space.
|
||||
//
|
||||
// If the thread's store buffer block is not released after that, the compactor
|
||||
// will not update it, causing an outdated address to be released to the store
|
||||
// buffer later.
|
||||
//
|
||||
// This causes two types of errors to trigger with --verify-store-buffer:
|
||||
// 1. We see the address in the store buffer but the object is no entry there.
|
||||
// Also can cause segfaults on reading garbage or unallocated memory.
|
||||
// 2. We see the entry has a marked bit, but can't find it in the store buffer.
|
||||
ISOLATE_UNIT_TEST_CASE(Finalizer_Regress_48843) {
|
||||
#ifdef DEBUG
|
||||
SetFlagScope<bool> sfs(&FLAG_trace_finalizers, true);
|
||||
SetFlagScope<bool> sfs2(&FLAG_verify_store_buffer, true);
|
||||
#endif
|
||||
SetFlagScope<bool> sfs3(&FLAG_use_compactor, true);
|
||||
|
||||
const auto& finalizer = Finalizer::Handle(Finalizer::New(Heap::kOld));
|
||||
finalizer.set_isolate(thread->isolate());
|
||||
|
||||
const auto& detach1 =
|
||||
String::Handle(OneByteString::New("detach1", Heap::kNew));
|
||||
const auto& token1 = String::Handle(OneByteString::New("token1", Heap::kNew));
|
||||
const auto& detach2 =
|
||||
String::Handle(OneByteString::New("detach2", Heap::kOld));
|
||||
const auto& token2 = String::Handle(OneByteString::New("token2", Heap::kOld));
|
||||
|
||||
{
|
||||
HANDLESCOPE(thread);
|
||||
const auto& entry1 =
|
||||
FinalizerEntry::Handle(FinalizerEntry::New(finalizer, Heap::kNew));
|
||||
entry1.set_detach(detach1);
|
||||
entry1.set_token(token1);
|
||||
|
||||
const auto& entry2 =
|
||||
FinalizerEntry::Handle(FinalizerEntry::New(finalizer, Heap::kOld));
|
||||
entry2.set_detach(detach2);
|
||||
entry2.set_token(token2);
|
||||
|
||||
{
|
||||
HANDLESCOPE(thread);
|
||||
const auto& value1 =
|
||||
String::Handle(OneByteString::New("value1", Heap::kNew));
|
||||
entry1.set_value(value1);
|
||||
const auto& value2 =
|
||||
String::Handle(OneByteString::New("value2", Heap::kOld));
|
||||
entry2.set_value(value2);
|
||||
// Lose both values.
|
||||
}
|
||||
|
||||
// First collect new space.
|
||||
GCTestHelper::CollectNewSpace();
|
||||
// Then old space, this will make the old space entry point to the new
|
||||
// space entry.
|
||||
// Also, this must be a mark compact, not a mark sweep, to move the entry.
|
||||
GCTestHelper::CollectOldSpace();
|
||||
}
|
||||
|
||||
// Imagine callbacks running.
|
||||
// Entries themselves become unreachable.
|
||||
finalizer.set_entries_collected(
|
||||
FinalizerEntry::Handle(FinalizerEntry::null()));
|
||||
|
||||
// There should be a single entry in the store buffer.
|
||||
// And it should crash when seeing the address in the buffer.
|
||||
GCTestHelper::CollectNewSpace();
|
||||
|
||||
// We should no longer be processing the entries.
|
||||
GCTestHelper::CollectOldSpace();
|
||||
GCTestHelper::CollectNewSpace();
|
||||
}
|
||||
|
||||
void NativeFinalizer_TwoEntriesCrossGen_Finalizer(void* peer) {
|
||||
intptr_t* token = reinterpret_cast<intptr_t*>(peer);
|
||||
(*token)++;
|
||||
|
|
|
@ -380,8 +380,10 @@ void Thread::ExitIsolateGroupAsHelper(bool bypass_safepoint) {
|
|||
|
||||
void Thread::ReleaseStoreBuffer() {
|
||||
ASSERT(IsAtSafepoint());
|
||||
if (store_buffer_block_ == nullptr || store_buffer_block_->IsEmpty()) {
|
||||
return; // Nothing to release.
|
||||
}
|
||||
// Prevent scheduling another GC by ignoring the threshold.
|
||||
ASSERT(store_buffer_block_ != nullptr);
|
||||
StoreBufferRelease(StoreBuffer::kIgnoreThreshold);
|
||||
// Make sure to get an *empty* block; the isolate needs all entries
|
||||
// at GC time.
|
||||
|
|
Loading…
Reference in a new issue