Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions JSTests/stress/microtask-queue-more-than-2-25-tasks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//@ skip if $memoryLimited
//@ slow!
//@ runDefault
Comment thread
robobun marked this conversation as resolved.

// The microtask queue is a WTF::Deque. The 2^25th pending task makes it grow to 2^26 tasks, which is more
// than the 2^31 - 1 bytes a Vector buffer can have, and that aborted the process. This needs about 4.5 GB.

function shouldBe(actual, expected)
{
if (actual !== expected)
throw new Error(`bad value: expected ${expected} but got ${actual}`);
}

const count = 2 ** 25 + 1;
const settled = Promise.resolve();
let ran = 0;
const job = () => { ++ran; };
for (let i = 0; i < count; ++i)
settled.then(job);
drainMicrotasks();
shouldBe(ran, count);

// The queue still works after it grew that far.
let order = "";
for (let i = 0; i < 5; ++i)
settled.then(() => { order += i; });
drainMicrotasks();
shouldBe(order, "01234");
25 changes: 25 additions & 0 deletions Source/JavaScriptCore/runtime/MicrotaskQueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,27 @@ static_assert(sizeof(QueuedTask) <= 32, "Size of QueuedTask is critical for perf
#endif
static_assert(std::is_trivially_destructible_v<QueuedTask>);

#if USE(BUN_JSC_ADDITIONS)
} // namespace JSC

namespace WTF {

// A Vector buffer holds at most 2^31 - 1 bytes. For Deque<QueuedTask> that is a capacity of 2^25 tasks, and the
// Deque calls CRASH() when it has to grow past it. Script reaches that count (2^25 reactions on one promise), and
// MicrotaskQueue::enqueue() cannot fail. Allow every capacity that VectorBufferBase::m_capacity can hold:
// Deque and VectorBufferBase::allocateBuffer() compute sizes in size_t.
// This has to come before the first use of Deque<QueuedTask>.
template<>
constexpr inline bool isValidCapacityForVector<JSC::QueuedTask>(size_t capacity)
{
return capacity <= std::min<size_t>(std::numeric_limits<unsigned>::max() >> 1, std::numeric_limits<size_t>::max() / sizeof(JSC::QueuedTask));
Comment thread
robobun marked this conversation as resolved.
}

} // namespace WTF

namespace JSC {
#endif

class MarkedMicrotaskDeque {
public:
friend class MicrotaskQueue;
Expand Down Expand Up @@ -208,6 +229,10 @@ class MarkedMicrotaskDeque {
DECLARE_VISIT_AGGREGATE;

private:
#if USE(BUN_JSC_ADDITIONS)
// No CI configuration has the memory for the test of this, microtask-queue-more-than-2-25-tasks.js.
static_assert(WTF::isValidCapacityForVector<QueuedTask>(1 << 26), "The specialization of isValidCapacityForVector for QueuedTask has to come before MarkedMicrotaskDeque");
#endif
Deque<QueuedTask> m_queue;
size_t m_markedBefore { 0 };
};
Expand Down
Loading