[vm] Bypass malloc for large Zone allocations to avoid jemalloc leaks.
Revert 9a07ad88f494ca81030172c07b47d54a0f46f822.
Bug: https://github.com/flutter/flutter/issues/29007
Change-Id: I6a5f51f0c3a54d354ec5f8495677d46f94d8a1d3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/100568
Commit-Queue: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Zach Anderson <zra@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
diff --git a/runtime/vm/zone.cc b/runtime/vm/zone.cc
index 8227e48..2d95479 100644
--- a/runtime/vm/zone.cc
+++ b/runtime/vm/zone.cc
@@ -11,6 +11,7 @@
#include "vm/handles_impl.h"
#include "vm/heap/heap.h"
#include "vm/os.h"
+#include "vm/virtual_memory.h"
namespace dart {
@@ -21,25 +22,28 @@
public:
Segment* next() const { return next_; }
intptr_t size() const { return size_; }
+ VirtualMemory* memory() const { return memory_; }
uword start() { return address(sizeof(Segment)); }
uword end() { return address(size_); }
// Allocate or delete individual segments.
static Segment* New(intptr_t size, Segment* next);
+ static Segment* NewLarge(intptr_t size, Segment* next);
static void DeleteSegmentList(Segment* segment);
+ static void DeleteLargeSegmentList(Segment* segment);
static void IncrementMemoryCapacity(uintptr_t size);
static void DecrementMemoryCapacity(uintptr_t size);
private:
Segment* next_;
intptr_t size_;
+ VirtualMemory* memory_;
+ void* alignment_;
// Computes the address of the nth byte in this segment.
uword address(int n) { return reinterpret_cast<uword>(this) + n; }
- static void Delete(Segment* segment) { free(segment); }
-
DISALLOW_IMPLICIT_CONSTRUCTORS(Segment);
};
@@ -49,13 +53,36 @@
if (result == NULL) {
OUT_OF_MEMORY();
}
- ASSERT(Utils::IsAligned(result->start(), Zone::kAlignment));
#ifdef DEBUG
// Zap the entire allocated segment (including the header).
memset(result, kZapUninitializedByte, size);
#endif
result->next_ = next;
result->size_ = size;
+ result->memory_ = nullptr;
+ result->alignment_ = nullptr; // Avoid unused variable warnings.
+ IncrementMemoryCapacity(size);
+ return result;
+}
+
+Zone::Segment* Zone::Segment::NewLarge(intptr_t size, Zone::Segment* next) {
+ size = Utils::RoundUp(size, VirtualMemory::PageSize());
+ VirtualMemory* memory = VirtualMemory::Allocate(size, false, "dart-zone");
+ if (memory == NULL) {
+ OUT_OF_MEMORY();
+ }
+ Segment* result = reinterpret_cast<Segment*>(memory->start());
+#ifdef DEBUG
+ // Zap the entire allocated segment (including the header).
+ memset(result, kZapUninitializedByte, size);
+#endif
+ result->next_ = next;
+ result->size_ = size;
+ result->memory_ = memory;
+ result->alignment_ = nullptr; // Avoid unused variable warnings.
+
+ LSAN_REGISTER_ROOT_REGION(result, sizeof(*result));
+
IncrementMemoryCapacity(size);
return result;
}
@@ -69,7 +96,23 @@
// Zap the entire current segment (including the header).
memset(current, kZapDeletedByte, current->size());
#endif
- Segment::Delete(current);
+ free(current);
+ current = next;
+ }
+}
+
+void Zone::Segment::DeleteLargeSegmentList(Segment* head) {
+ Segment* current = head;
+ while (current != NULL) {
+ DecrementMemoryCapacity(current->size());
+ Segment* next = current->next();
+ VirtualMemory* memory = current->memory();
+#ifdef DEBUG
+ // Zap the entire current segment (including the header).
+ memset(current, kZapDeletedByte, current->size());
+#endif
+ LSAN_UNREGISTER_ROOT_REGION(current, sizeof(*current));
+ delete memory;
current = next;
}
}
@@ -129,7 +172,7 @@
Segment::DeleteSegmentList(head_);
}
if (large_segments_ != NULL) {
- Segment::DeleteSegmentList(large_segments_);
+ Segment::DeleteLargeSegmentList(large_segments_);
}
// Reset zone state.
#ifdef DEBUG
@@ -214,9 +257,9 @@
ASSERT(free_size < size);
// Create a new large segment and chain it up.
- ASSERT(Utils::IsAligned(sizeof(Segment), kAlignment));
- size += sizeof(Segment); // Account for book keeping fields in size.
- large_segments_ = Segment::New(size, large_segments_);
+ // Account for book keeping fields in size.
+ size += Utils::RoundUp(sizeof(Segment), kAlignment);
+ large_segments_ = Segment::NewLarge(size, large_segments_);
uword result = Utils::RoundUp(large_segments_->start(), kAlignment);
return result;
diff --git a/runtime/vm/zone_test.cc b/runtime/vm/zone_test.cc
index 5f8f783..a12b5ff 100644
--- a/runtime/vm/zone_test.cc
+++ b/runtime/vm/zone_test.cc
@@ -176,4 +176,66 @@
EXPECT_EQ(0UL, ApiNativeScope::current_memory_usage());
}
+#if !defined(PRODUCT)
+// Allow for pooling in the malloc implementation.
+static const int64_t kRssSlack = 20 * MB;
+#endif // !defined(PRODUCT)
+
+// clang-format off
+static const size_t kSizes[] = {
+ 64 * KB,
+ 64 * KB + 2 * kWordSize,
+ 64 * KB - 2 * kWordSize,
+ 128 * KB,
+ 128 * KB + 2 * kWordSize,
+ 128 * KB - 2 * kWordSize,
+ 256 * KB,
+ 256 * KB + 2 * kWordSize,
+ 256 * KB - 2 * kWordSize,
+ 512 * KB,
+ 512 * KB + 2 * kWordSize,
+ 512 * KB - 2 * kWordSize,
+};
+// clang-format on
+
+TEST_CASE(StressMallocDirectly) {
+#if !defined(PRODUCT)
+ int64_t start_rss = Service::CurrentRSS();
+#endif // !defined(PRODUCT)
+
+ void* allocations[ARRAY_SIZE(kSizes)];
+ for (size_t i = 0; i < ((3u * GB) / (512u * KB)); i++) {
+ for (size_t j = 0; j < ARRAY_SIZE(kSizes); j++) {
+ allocations[j] = malloc(kSizes[j]);
+ }
+ for (size_t j = 0; j < ARRAY_SIZE(kSizes); j++) {
+ free(allocations[j]);
+ }
+ }
+
+#if !defined(PRODUCT)
+ int64_t stop_rss = Service::CurrentRSS();
+ EXPECT_LT(stop_rss, start_rss + kRssSlack);
+#endif // !defined(PRODUCT)
+}
+
+TEST_CASE(StressMallocThroughZones) {
+#if !defined(PRODUCT)
+ int64_t start_rss = Service::CurrentRSS();
+#endif // !defined(PRODUCT)
+
+ for (size_t i = 0; i < ((3u * GB) / (512u * KB)); i++) {
+ StackZone stack_zone(Thread::Current());
+ Zone* zone = stack_zone.GetZone();
+ for (size_t j = 0; j < ARRAY_SIZE(kSizes); j++) {
+ zone->Alloc<uint8_t>(kSizes[j]);
+ }
+ }
+
+#if !defined(PRODUCT)
+ int64_t stop_rss = Service::CurrentRSS();
+ EXPECT_LT(stop_rss, start_rss + kRssSlack);
+#endif // !defined(PRODUCT)
+}
+
} // namespace dart
diff --git a/tests/corelib_2/regexp/jemalloc_leak_backtracking_stack_test.dart b/tests/corelib_2/regexp/jemalloc_leak_backtracking_stack_test.dart
new file mode 100644
index 0000000..18c4621
--- /dev/null
+++ b/tests/corelib_2/regexp/jemalloc_leak_backtracking_stack_test.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2019, 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.
+
+// Regression test for https://github.com/flutter/flutter/issues/29007
+
+String escape(String string) {
+ var regex = new RegExp("(\\?|\\\$|\\*|\\(|\\)|\\[)|\\+|\\.|\\\\");
+ return string.replaceAllMapped(
+ regex, (Match m) => "\\" + string.substring(m.start, m.end));
+}
+
+main() {
+ var text = """
+Yet but three? Come one more.
+Two of both kinds make up four.
+""";
+ var accumulate = 0;
+ for (var i = 0; i < 65536; i++) {
+ accumulate += escape(text).length;
+ }
+
+ print(accumulate);
+}