Commit 408d8904 authored by Michael Lippautz's avatar Michael Lippautz Committed by Commit Bot

Reland "Add external backing store JS test"

Allow mocking the limits for ArrayBuffer allocation to simulate operating
system OOM.

Fixes:
- Ensure OS limit > hard limit for external memory. This is necessary as
  any processing below the hard limit is opportunistic. E.g. a running
  sweeper may stall the current marking (GC) round.
- Immediately process AB allocations when under memory pressure. Otherwise,
  the allocations may be stuck in a stalled task. Freeing them upon
  adding them to the collector still enables parallelism if possible.

This reverts commit f3ad6cdb.

Bug: chromium:845409
Change-Id: Ic3e458f2af231bae3d53afcfd6002a0347d3f12b
Reviewed-on: https://chromium-review.googlesource.com/1206872
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#55656}
parent d48bd16c
...@@ -135,6 +135,7 @@ class ShellArrayBufferAllocator : public ArrayBufferAllocatorBase { ...@@ -135,6 +135,7 @@ class ShellArrayBufferAllocator : public ArrayBufferAllocatorBase {
// ArrayBuffer allocator that never allocates over 10MB. // ArrayBuffer allocator that never allocates over 10MB.
class MockArrayBufferAllocator : public ArrayBufferAllocatorBase { class MockArrayBufferAllocator : public ArrayBufferAllocatorBase {
protected:
void* Allocate(size_t length) override { void* Allocate(size_t length) override {
return ArrayBufferAllocatorBase::Allocate(Adjust(length)); return ArrayBufferAllocatorBase::Allocate(Adjust(length));
} }
...@@ -154,6 +155,39 @@ class MockArrayBufferAllocator : public ArrayBufferAllocatorBase { ...@@ -154,6 +155,39 @@ class MockArrayBufferAllocator : public ArrayBufferAllocatorBase {
} }
}; };
// ArrayBuffer allocator that can be equipped with a limit to simulate system
// OOM.
class MockArrayBufferAllocatiorWithLimit : public MockArrayBufferAllocator {
public:
explicit MockArrayBufferAllocatiorWithLimit(size_t allocation_limit)
: space_left_(allocation_limit) {}
protected:
void* Allocate(size_t length) override {
if (length > space_left_) {
return nullptr;
}
space_left_ -= length;
return MockArrayBufferAllocator::Allocate(length);
}
void* AllocateUninitialized(size_t length) override {
if (length > space_left_) {
return nullptr;
}
space_left_ -= length;
return MockArrayBufferAllocator::AllocateUninitialized(length);
}
void Free(void* data, size_t length) override {
space_left_ += length;
return MockArrayBufferAllocator::Free(data, length);
}
private:
std::atomic<size_t> space_left_;
};
// Predictable v8::Platform implementation. Worker threads are disabled, idle // Predictable v8::Platform implementation. Worker threads are disabled, idle
// tasks are disallowed, and the time reported by {MonotonicallyIncreasingTime} // tasks are disallowed, and the time reported by {MonotonicallyIncreasingTime}
// is deterministic. // is deterministic.
...@@ -2904,6 +2938,8 @@ bool Shell::SetOptions(int argc, char* argv[]) { ...@@ -2904,6 +2938,8 @@ bool Shell::SetOptions(int argc, char* argv[]) {
v8::V8::SetFlagsFromCommandLine(&argc, argv, true); v8::V8::SetFlagsFromCommandLine(&argc, argv, true);
options.mock_arraybuffer_allocator = i::FLAG_mock_arraybuffer_allocator; options.mock_arraybuffer_allocator = i::FLAG_mock_arraybuffer_allocator;
options.mock_arraybuffer_allocator_limit =
i::FLAG_mock_arraybuffer_allocator_limit;
// Set up isolated source groups. // Set up isolated source groups.
options.isolate_sources = new SourceGroup[options.num_isolates]; options.isolate_sources = new SourceGroup[options.num_isolates];
...@@ -3366,8 +3402,18 @@ int Shell::Main(int argc, char* argv[]) { ...@@ -3366,8 +3402,18 @@ int Shell::Main(int argc, char* argv[]) {
Isolate::CreateParams create_params; Isolate::CreateParams create_params;
ShellArrayBufferAllocator shell_array_buffer_allocator; ShellArrayBufferAllocator shell_array_buffer_allocator;
MockArrayBufferAllocator mock_arraybuffer_allocator; MockArrayBufferAllocator mock_arraybuffer_allocator;
const size_t memory_limit =
options.mock_arraybuffer_allocator_limit * options.num_isolates;
MockArrayBufferAllocatiorWithLimit mock_arraybuffer_allocator_with_limit(
memory_limit >= options.mock_arraybuffer_allocator_limit
? memory_limit
: std::numeric_limits<size_t>::max());
if (options.mock_arraybuffer_allocator) { if (options.mock_arraybuffer_allocator) {
Shell::array_buffer_allocator = &mock_arraybuffer_allocator; if (memory_limit) {
Shell::array_buffer_allocator = &mock_arraybuffer_allocator_with_limit;
} else {
Shell::array_buffer_allocator = &mock_arraybuffer_allocator;
}
} else { } else {
Shell::array_buffer_allocator = &shell_array_buffer_allocator; Shell::array_buffer_allocator = &shell_array_buffer_allocator;
} }
......
...@@ -369,6 +369,7 @@ class ShellOptions { ...@@ -369,6 +369,7 @@ class ShellOptions {
bool test_shell; bool test_shell;
bool expected_to_throw; bool expected_to_throw;
bool mock_arraybuffer_allocator; bool mock_arraybuffer_allocator;
size_t mock_arraybuffer_allocator_limit = 0;
bool enable_inspector; bool enable_inspector;
int num_isolates; int num_isolates;
v8::ScriptCompiler::CompileOptions compile_options; v8::ScriptCompiler::CompileOptions compile_options;
......
...@@ -1152,6 +1152,9 @@ DEFINE_ARGS(js_arguments, ...@@ -1152,6 +1152,9 @@ DEFINE_ARGS(js_arguments,
"Pass all remaining arguments to the script. Alias for \"--\".") "Pass all remaining arguments to the script. Alias for \"--\".")
DEFINE_BOOL(mock_arraybuffer_allocator, false, DEFINE_BOOL(mock_arraybuffer_allocator, false,
"Use a mock ArrayBuffer allocator for testing.") "Use a mock ArrayBuffer allocator for testing.")
DEFINE_SIZE_T(mock_arraybuffer_allocator_limit, 0,
"Memory limit for mock ArrayBuffer allocator used to simulate "
"OOM for testing.")
// //
// GDB JIT integration flags. // GDB JIT integration flags.
......
...@@ -12,19 +12,32 @@ ...@@ -12,19 +12,32 @@
namespace v8 { namespace v8 {
namespace internal { namespace internal {
void ArrayBufferCollector::AddGarbageAllocations( namespace {
void FreeAllocationsHelper(
Heap* heap, const std::vector<JSArrayBuffer::Allocation>& allocations) {
for (JSArrayBuffer::Allocation alloc : allocations) {
JSArrayBuffer::FreeBackingStore(heap->isolate(), alloc);
}
}
} // namespace
void ArrayBufferCollector::QueueOrFreeGarbageAllocations(
std::vector<JSArrayBuffer::Allocation> allocations) { std::vector<JSArrayBuffer::Allocation> allocations) {
base::LockGuard<base::Mutex> guard(&allocations_mutex_); if (heap_->ShouldReduceMemory()) {
allocations_.push_back(std::move(allocations)); FreeAllocationsHelper(heap_, allocations);
} else {
base::LockGuard<base::Mutex> guard(&allocations_mutex_);
allocations_.push_back(std::move(allocations));
}
} }
void ArrayBufferCollector::FreeAllocations() { void ArrayBufferCollector::PerformFreeAllocations() {
base::LockGuard<base::Mutex> guard(&allocations_mutex_); base::LockGuard<base::Mutex> guard(&allocations_mutex_);
for (const std::vector<JSArrayBuffer::Allocation>& allocations : for (const std::vector<JSArrayBuffer::Allocation>& allocations :
allocations_) { allocations_) {
for (JSArrayBuffer::Allocation alloc : allocations) { FreeAllocationsHelper(heap_, allocations);
JSArrayBuffer::FreeBackingStore(heap_->isolate(), alloc);
}
} }
allocations_.clear(); allocations_.clear();
} }
...@@ -41,21 +54,25 @@ class ArrayBufferCollector::FreeingTask final : public CancelableTask { ...@@ -41,21 +54,25 @@ class ArrayBufferCollector::FreeingTask final : public CancelableTask {
TRACE_BACKGROUND_GC( TRACE_BACKGROUND_GC(
heap_->tracer(), heap_->tracer(),
GCTracer::BackgroundScope::BACKGROUND_ARRAY_BUFFER_FREE); GCTracer::BackgroundScope::BACKGROUND_ARRAY_BUFFER_FREE);
heap_->array_buffer_collector()->FreeAllocations(); heap_->array_buffer_collector()->PerformFreeAllocations();
} }
Heap* heap_; Heap* heap_;
}; };
void ArrayBufferCollector::FreeAllocationsOnBackgroundThread() { void ArrayBufferCollector::FreeAllocations() {
// TODO(wez): Remove backing-store from external memory accounting. // TODO(wez): Remove backing-store from external memory accounting.
heap_->account_external_memory_concurrently_freed(); heap_->account_external_memory_concurrently_freed();
if (!heap_->IsTearingDown() && FLAG_concurrent_array_buffer_freeing) { if (!heap_->IsTearingDown() && !heap_->ShouldReduceMemory() &&
FLAG_concurrent_array_buffer_freeing) {
V8::GetCurrentPlatform()->CallOnWorkerThread( V8::GetCurrentPlatform()->CallOnWorkerThread(
base::make_unique<FreeingTask>(heap_)); base::make_unique<FreeingTask>(heap_));
} else { } else {
// Fallback for when concurrency is disabled/restricted. // Fallback for when concurrency is disabled/restricted. This is e.g. the
FreeAllocations(); // case when the GC should reduce memory. For such GCs the
// QueueOrFreeGarbageAllocations() call would immediately free the
// allocations and this call would free already queued ones.
PerformFreeAllocations();
} }
} }
......
...@@ -23,24 +23,27 @@ class ArrayBufferCollector { ...@@ -23,24 +23,27 @@ class ArrayBufferCollector {
public: public:
explicit ArrayBufferCollector(Heap* heap) : heap_(heap) {} explicit ArrayBufferCollector(Heap* heap) : heap_(heap) {}
~ArrayBufferCollector() { FreeAllocations(); } ~ArrayBufferCollector() { PerformFreeAllocations(); }
// These allocations will begin to be freed once FreeAllocations() is called, // These allocations will be either
// or on TearDown. // - freed immediately when under memory pressure, or
void AddGarbageAllocations( // - queued for freeing in FreeAllocations() or during tear down.
//
// FreeAllocations() potentially triggers a background task for processing.
void QueueOrFreeGarbageAllocations(
std::vector<JSArrayBuffer::Allocation> allocations); std::vector<JSArrayBuffer::Allocation> allocations);
// Calls FreeAllocations() on a background thread. // Calls FreeAllocations() on a background thread.
void FreeAllocationsOnBackgroundThread(); void FreeAllocations();
private: private:
class FreeingTask; class FreeingTask;
// Begin freeing the allocations added through AddGarbageAllocations. Also // Begin freeing the allocations added through QueueOrFreeGarbageAllocations.
// called by TearDown. // Also called by TearDown.
void FreeAllocations(); void PerformFreeAllocations();
Heap* heap_; Heap* const heap_;
base::Mutex allocations_mutex_; base::Mutex allocations_mutex_;
std::vector<std::vector<JSArrayBuffer::Allocation>> allocations_; std::vector<std::vector<JSArrayBuffer::Allocation>> allocations_;
}; };
......
...@@ -76,9 +76,9 @@ void LocalArrayBufferTracker::Process(Callback callback) { ...@@ -76,9 +76,9 @@ void LocalArrayBufferTracker::Process(Callback callback) {
array_buffers_.swap(kept_array_buffers); array_buffers_.swap(kept_array_buffers);
// Pass the backing stores that need to be freed to the main thread for later // Pass the backing stores that need to be freed to the main thread for
// distribution. // potential later distribution.
page_->heap()->array_buffer_collector()->AddGarbageAllocations( page_->heap()->array_buffer_collector()->QueueOrFreeGarbageAllocations(
std::move(backing_stores_to_free)); std::move(backing_stores_to_free));
} }
......
...@@ -2212,7 +2212,7 @@ void Heap::Scavenge() { ...@@ -2212,7 +2212,7 @@ void Heap::Scavenge() {
TRACE_GC(tracer(), GCTracer::Scope::SCAVENGER_PROCESS_ARRAY_BUFFERS); TRACE_GC(tracer(), GCTracer::Scope::SCAVENGER_PROCESS_ARRAY_BUFFERS);
ArrayBufferTracker::PrepareToFreeDeadInNewSpace(this); ArrayBufferTracker::PrepareToFreeDeadInNewSpace(this);
} }
array_buffer_collector()->FreeAllocationsOnBackgroundThread(); array_buffer_collector()->FreeAllocations();
RememberedSet<OLD_TO_NEW>::IterateMemoryChunks(this, [](MemoryChunk* chunk) { RememberedSet<OLD_TO_NEW>::IterateMemoryChunks(this, [](MemoryChunk* chunk) {
if (chunk->SweepingDone()) { if (chunk->SweepingDone()) {
......
...@@ -2263,6 +2263,7 @@ class Heap { ...@@ -2263,6 +2263,7 @@ class Heap {
// Classes in "heap" can be friends. // Classes in "heap" can be friends.
friend class AlwaysAllocateScope; friend class AlwaysAllocateScope;
friend class ArrayBufferCollector;
friend class ConcurrentMarking; friend class ConcurrentMarking;
friend class EphemeronHashTableMarkingTask; friend class EphemeronHashTableMarkingTask;
friend class GCCallbacksScope; friend class GCCallbacksScope;
......
...@@ -3255,7 +3255,7 @@ void MarkCompactCollector::UpdatePointersAfterEvacuation() { ...@@ -3255,7 +3255,7 @@ void MarkCompactCollector::UpdatePointersAfterEvacuation() {
GCTracer::BackgroundScope::MC_BACKGROUND_EVACUATE_UPDATE_POINTERS)); GCTracer::BackgroundScope::MC_BACKGROUND_EVACUATE_UPDATE_POINTERS));
} }
updating_job.Run(isolate()->async_counters()); updating_job.Run(isolate()->async_counters());
heap()->array_buffer_collector()->FreeAllocationsOnBackgroundThread(); heap()->array_buffer_collector()->FreeAllocations();
} }
} }
...@@ -3758,7 +3758,7 @@ void MinorMarkCompactCollector::UpdatePointersAfterEvacuation() { ...@@ -3758,7 +3758,7 @@ void MinorMarkCompactCollector::UpdatePointersAfterEvacuation() {
TRACE_GC(heap()->tracer(), TRACE_GC(heap()->tracer(),
GCTracer::Scope::MINOR_MC_EVACUATE_UPDATE_POINTERS_SLOTS); GCTracer::Scope::MINOR_MC_EVACUATE_UPDATE_POINTERS_SLOTS);
updating_job.Run(isolate()->async_counters()); updating_job.Run(isolate()->async_counters());
heap()->array_buffer_collector()->FreeAllocationsOnBackgroundThread(); heap()->array_buffer_collector()->FreeAllocations();
} }
{ {
......
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --mock-arraybuffer-allocator --mock-arraybuffer-allocator-limit=1300000000
// --mock-arraybuffer-allocator-limit should be above the hard limit external
// for memory. Below that limit anything is opportunistic and may be delayed,
// e.g., by tasks getting stalled and the event loop not being invoked.
for (var i = 0; i < 1536; i++) {
let garbage = new ArrayBuffer(1024*1024);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment