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
2 changes: 1 addition & 1 deletion common.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

# Reset this number to 0 on major V8 upgrades.
# Increment by one for each non-official patch applied to deps/v8.
'v8_embedder_string': '-node.26',
'v8_embedder_string': '-node.27',

##### V8 defaults for Node.js #####

Expand Down
1 change: 1 addition & 0 deletions deps/v8/AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Artem Kobzar <artem.kobzar@jetbrains.com>
Arthur Islamov <arthur@islamov.ai>
Asuka Shikina <shikina.asuka@gmail.com>
Aurèle Barrière <aurele.barriere@gmail.com>
Aviv Keller <me@aviv.sh>
Bala Avulapati <bavulapati@gmail.com>
Bangfu Tao <bangfu.tao@samsung.com>
Ben Coe <bencoe@gmail.com>
Expand Down
39 changes: 38 additions & 1 deletion deps/v8/src/inspector/injected-script.cc
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,16 @@ class InjectedScript::ProtocolPromiseHandler {
m_throwOnSideEffect(throwOnSideEffect),
m_callback(std::move(callback)),
m_evaluationResult(m_inspector->isolate(), evaluationResult) {
if (!m_replMode) {
makeWeak(id);
}
}

void makeWeak(PromiseHandlerTracker::Id id) {
if (m_isActive || m_evaluationResult.IsEmpty() ||
m_evaluationResult.IsWeak()) {
return;
}
m_evaluationResult.SetWeak(reinterpret_cast<PromiseHandlerTracker::Id*>(id),
cleanup, v8::WeakCallbackType::kParameter);
}
Expand All @@ -238,6 +248,7 @@ class InjectedScript::ProtocolPromiseHandler {
}

void thenCallback(v8::Local<v8::Value> value) {
m_isActive = true;
// We don't need the m_evaluationResult in the `thenCallback`, but we also
// don't want `cleanup` running in case we re-enter JS.
m_evaluationResult.Reset();
Expand Down Expand Up @@ -285,9 +296,10 @@ class InjectedScript::ProtocolPromiseHandler {
}

void catchCallback(v8::Local<v8::Value> result) {
m_isActive = true;
// Hold strongly onto m_evaluationResult now to prevent `cleanup` from
// running in case any code below triggers GC.
m_evaluationResult.ClearWeak();
if (m_evaluationResult.IsWeak()) m_evaluationResult.ClearWeak<void>();
V8InspectorSessionImpl* session =
m_inspector->sessionById(m_contextGroupId, m_sessionId);
if (!session) return;
Expand Down Expand Up @@ -393,6 +405,7 @@ class InjectedScript::ProtocolPromiseHandler {
std::unique_ptr<WrapOptions> m_wrapOptions;
bool m_replMode;
bool m_throwOnSideEffect;
bool m_isActive = false;
std::weak_ptr<EvaluateCallback> m_callback;
v8::Global<v8::Promise> m_evaluationResult;
};
Expand Down Expand Up @@ -1225,6 +1238,30 @@ InjectedScript::ProtocolPromiseHandler* PromiseHandlerTracker::get(
return iter->second.get();
}

void PromiseHandlerTracker::makeWeakForContext(int executionContextId) {
for (auto& [id, handler] : m_promiseHandlers) {
if (handler->m_executionContextId == executionContextId) {
handler->makeWeak(id);
}
}
}

void PromiseHandlerTracker::makeWeakForObjectGroup(
int sessionId, const String16& objectGroup) {
for (auto& [id, handler] : m_promiseHandlers) {
if (handler->m_sessionId == sessionId &&
handler->m_objectGroup == objectGroup) {
handler->makeWeak(id);
}
}
}

void PromiseHandlerTracker::makeWeakForSession(int sessionId) {
for (auto& [id, handler] : m_promiseHandlers) {
if (handler->m_sessionId == sessionId) handler->makeWeak(id);
}
}

void PromiseHandlerTracker::sendFailure(
InjectedScript::ProtocolPromiseHandler* handler,
const protocol::DispatchResponse& response) const {
Expand Down
3 changes: 3 additions & 0 deletions deps/v8/src/inspector/injected-script.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,9 @@ class PromiseHandlerTracker {
Id create(Args&&... args);
void discard(Id id, DiscardReason reason);
InjectedScript::ProtocolPromiseHandler* get(Id id) const;
void makeWeakForContext(int executionContextId);
void makeWeakForObjectGroup(int sessionId, const String16& objectGroup);
void makeWeakForSession(int sessionId);

private:
void sendFailure(InjectedScript::ProtocolPromiseHandler* handler,
Expand Down
1 change: 1 addition & 0 deletions deps/v8/src/inspector/v8-inspector-impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ void V8InspectorImpl::contextCollected(int groupId, int contextId) {
session->runtimeAgent()->reportExecutionContextDestroyed(inspectedContext);
});
discardInspectedContext(groupId, contextId);
m_promiseHandlerTracker.makeWeakForContext(contextId);
}

void V8InspectorImpl::resetContextGroup(int contextGroupId) {
Expand Down
5 changes: 5 additions & 0 deletions deps/v8/src/inspector/v8-inspector-session-impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ void V8InspectorSessionImpl::discardInjectedScripts() {
[&sessionId](InspectedContext* context) {
context->discardInjectedScript(sessionId);
});
m_inspector->promiseHandlerTracker().makeWeakForSession(sessionId);
}

Response V8InspectorSessionImpl::findInjectedScript(
Expand Down Expand Up @@ -260,6 +261,10 @@ void V8InspectorSessionImpl::releaseObjectGroup(const String16& objectGroup) {
InjectedScript* injectedScript = context->getInjectedScript(sessionId);
if (injectedScript) injectedScript->releaseObjectGroup(objectGroup);
});
if (!objectGroup.isEmpty()) {
m_inspector->promiseHandlerTracker().makeWeakForObjectGroup(m_sessionId,
objectGroup);
}
}

bool V8InspectorSessionImpl::unwrapObject(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Tests the lifetime of Runtime.evaluate REPL promises.

Running test: testPromiseIsKeptAlive
{
id : <messageId>
result : {
result : {
description : 42
type : number
value : 42
}
}
}

Running test: testObjectGroupReleaseMakesPromiseCollectible
{
error : {
code : -32000
message : Promise was collected
}
id : <messageId>
}

Running test: testContextDestructionDiscardsPromise
{
error : {
code : -32000
message : Execution context was destroyed.
}
id : <messageId>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2026 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: --no-stress-incremental-marking

let {Protocol} = InspectorTest.start(
'Tests the lifetime of Runtime.evaluate REPL promises.');

InspectorTest.runAsyncTestSuite([
async function testPromiseIsKeptAlive() {
const evaluation = Protocol.Runtime.evaluate({
expression:
'await new Promise(resolve => globalThis.resolve = resolve); 42',
replMode: true,
});

await Protocol.HeapProfiler.collectGarbage();
await Protocol.Runtime.evaluate({expression: 'resolve()'});

InspectorTest.logMessage(await evaluation);
},

async function testObjectGroupReleaseMakesPromiseCollectible() {
const evaluation = Protocol.Runtime.evaluate({
expression: 'await new Promise(() => {})',
objectGroup: 'evaluation',
replMode: true,
});

await Protocol.Runtime.releaseObjectGroup({objectGroup: 'evaluation'});
await Protocol.HeapProfiler.collectGarbage();

InspectorTest.logMessage(await evaluation);
},

async function testContextDestructionDiscardsPromise() {
const evaluation = Protocol.Runtime.evaluate({
expression: 'await new Promise(() => {})',
replMode: true,
});

await Protocol.Runtime.evaluate(
{expression: 'inspector.fireContextDestroyed()'});

InspectorTest.logMessage(await evaluation);
},
]);
Loading