Compare commits

...

1 Commits

Author SHA1 Message Date
Jarred Sumner
b3c92135b4 Add a way to get the calling function from process.nextTick 2025-04-13 07:24:23 -07:00
5 changed files with 147 additions and 14 deletions

View File

@@ -2751,6 +2751,7 @@ void Process::visitChildrenImpl(JSCell* cell, Visitor& visitor)
thisObject->m_memoryUsageStructure.visit(visitor);
thisObject->m_bindingUV.visit(visitor);
thisObject->m_bindingNatives.visit(visitor);
thisObject->m_nextTickQueueEntryStructure.visit(visitor);
}
DEFINE_VISIT_CHILDREN(Process);
@@ -3622,6 +3623,10 @@ void Process::finishCreation(JSC::VM& vm)
init.set(Bun::ProcessBindingNatives::create(init.vm, ProcessBindingNatives::createStructure(init.vm, init.owner->globalObject())));
});
m_nextTickQueueEntryStructure.initLater([](const JSC::LazyProperty<Process, JSC::Structure>::Initializer& init) {
init.set(createNextTickQueueEntryStructure(init.vm, init.owner->globalObject()));
});
putDirect(vm, vm.propertyNames->toStringTagSymbol, jsString(vm, String("process"_s)), 0);
putDirect(vm, Identifier::fromString(vm, "_exiting"_s), jsBoolean(false), 0);
}

View File

@@ -18,6 +18,7 @@ extern "C" int getRSS(size_t* rss);
class Process : public WebCore::JSEventEmitter {
using Base = WebCore::JSEventEmitter;
LazyProperty<Process, Structure> m_nextTickQueueEntryStructure;
LazyProperty<Process, Structure> m_cpuUsageStructure;
LazyProperty<Process, Structure> m_memoryUsageStructure;
LazyProperty<Process, JSObject> m_bindingUV;
@@ -52,6 +53,8 @@ public:
void queueNextTick(JSC::JSGlobalObject* globalObject, JSValue);
void queueNextTick(JSC::JSGlobalObject* globalObject, JSValue, JSValue);
Structure* nextTickQueueEntryStructure() { return m_nextTickQueueEntryStructure.getInitializedOnMainThread(this); }
template<size_t NumArgs>
void queueNextTick(JSC::JSGlobalObject* globalObject, JSValue func, const JSValue (&args)[NumArgs]);

View File

@@ -13,11 +13,78 @@
#include "ExtendedDOMClientIsoSubspaces.h"
#include "ExtendedDOMIsoSubspaces.h"
#include "BunClientData.h"
#include "NodeValidator.h"
#include "ZigGlobalObject.h"
#include "BunProcess.h"
namespace Bun {
using namespace JSC;
class JSNextTickQueueEntry : public JSC::JSInternalFieldObjectImpl<5> {
public:
using Base = JSC::JSInternalFieldObjectImpl<5>;
static constexpr bool needsDestruction = false;
static JSNextTickQueueEntry* create(JSC::VM& vm, JSC::Structure* structure)
{
JSNextTickQueueEntry* entry = new (NotNull, JSC::allocateCell<JSNextTickQueueEntry>(vm)) JSNextTickQueueEntry(vm, structure);
entry->finishCreation(vm);
return entry;
}
static JSNextTickQueueEntry* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSGlobalObject* globalObject, JSC::JSValue callback, JSC::JSValue args, JSC::JSValue frame, JSC::JSValue callee, BytecodeIndex bytecodeIndex)
{
JSNextTickQueueEntry* entry = JSNextTickQueueEntry::create(vm, structure);
entry->internalField(static_cast<unsigned>(Fields::Callback)).set(vm, entry, callback);
entry->internalField(static_cast<unsigned>(Fields::Args)).set(vm, entry, args);
entry->internalField(static_cast<unsigned>(Fields::Frame)).set(vm, entry, frame);
entry->internalField(static_cast<unsigned>(Fields::Callee)).set(vm, entry, callee);
entry->m_bytecodeIndex = bytecodeIndex;
return entry;
}
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFieldTupleType, StructureFlags), info());
}
DECLARE_INFO;
DECLARE_VISIT_CHILDREN;
enum class Fields : unsigned {
Callback = 0,
Args = 1,
Frame = 2,
Callee = 3,
};
BytecodeIndex m_bytecodeIndex;
template<typename CellType, JSC::SubspaceAccess mode>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
return WebCore::subspaceForImpl<JSNextTickQueueEntry, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForJSNextTickQueueEntry.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSNextTickQueueEntry = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForJSNextTickQueueEntry.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForJSNextTickQueueEntry = std::forward<decltype(space)>(space); });
}
private:
JSNextTickQueueEntry(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure)
{
}
void finishCreation(JSC::VM& vm)
{
Base::finishCreation(vm);
}
};
const JSC::ClassInfo JSNextTickQueueEntry::s_info = { "NextTickQueueEntry"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSNextTickQueueEntry) };
const JSC::ClassInfo JSNextTickQueue::s_info = { "NextTickQueue"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSNextTickQueue) };
template<typename, JSC::SubspaceAccess mode>
@@ -93,4 +160,55 @@ void JSNextTickQueue::drain(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
}
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionCreateNextTickQueueEntry, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
VM& vm = lexicalGlobalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue callback;
RETURN_IF_EXCEPTION(scope, {});
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
ArgList args;
BytecodeIndex bytecodeIndex = BytecodeIndex();
JSValue functionExecutableValue = jsUndefined();
if (auto* callerFrame = callFrame->callerFrame()) {
callback = callerFrame->argument(0);
args = callFrame->argumentCount() > 1 ? JSC::ArgList(callFrame, 1) : JSC::ArgList();
if (auto* calleeObject = callerFrame->jsCallee()) {
if (auto* callee = jsDynamicCast<JSFunction*>(calleeObject)) {
if (callee->isNonBoundHostFunction()) {
if (auto* executable = callee->jsExecutable()) {
functionExecutableValue = executable;
bytecodeIndex = callerFrame->bytecodeIndex();
}
}
}
}
}
JSC::JSValue argsValue = jsUndefined();
if (args.size() > 0) {
argsValue = JSC::constructArray(globalObject, static_cast<ArrayAllocationProfile*>(nullptr), args);
}
auto* asyncContext = globalObject->m_asyncContextData.get();
auto frame = asyncContext->getInternalField(0);
auto* processObject = jsCast<Process*>(globalObject->processObject());
auto* structure = processObject->nextTickQueueEntryStructure();
auto entry = JSNextTickQueueEntry::create(vm, structure, lexicalGlobalObject, callback, argsValue, frame, functionExecutableValue, bytecodeIndex);
return JSValue::encode(entry);
}
template<typename Visitor>
void JSNextTickQueueEntry::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
JSNextTickQueueEntry* thisObject = jsCast<JSNextTickQueueEntry*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);
}
DEFINE_VISIT_CHILDREN(JSNextTickQueueEntry);
}

View File

@@ -8,9 +8,11 @@
namespace Bun {
using namespace JSC;
class JSNextTickQueue : public JSC::JSInternalFieldObjectImpl<3> {
JSC_DECLARE_HOST_FUNCTION(jsFunctionCreateNextTickQueueEntry);
class JSNextTickQueue : public JSC::JSInternalFieldObjectImpl<4> {
public:
using Base = JSC::JSInternalFieldObjectImpl<3>;
using Base = JSC::JSInternalFieldObjectImpl<4>;
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm);
@@ -25,6 +27,7 @@ public:
jsNumber(-1),
jsUndefined(),
jsUndefined(),
jsUndefined(),
} };
}
@@ -37,4 +40,7 @@ public:
bool isEmpty();
void drain(JSC::VM& vm, JSC::JSGlobalObject* globalObject);
};
Structure* createNextTickQueueEntryStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject);
}

View File

@@ -264,11 +264,14 @@ export function initializeNextTickQueue(process, nextTickQueue, drainMicrotasksF
var tock;
do {
while ((tock = queue.shift()) !== null) {
var callback = tock.callback;
var args = tock.args;
var frame = tock.frame;
var callback = $getInternalField(tock, 0);
var args = $getInternalField(tock, 1);
var frame = $getInternalField(tock, 2);
var prevQueueEntry = $getInternalField(nextTickQueue, 3);
$putInternalField(nextTickQueue, 3, tock);
var restore = $getInternalField($asyncContext, 0);
$putInternalField($asyncContext, 0, frame);
try {
if (args === undefined) {
callback();
@@ -294,6 +297,8 @@ export function initializeNextTickQueue(process, nextTickQueue, drainMicrotasksF
} catch (e) {
reportUncaughtException(e);
} finally {
$putInternalField(tock, 3, undefined);
$putInternalField(nextTickQueue, 3, prevQueueEntry);
$putInternalField($asyncContext, 0, restore);
}
}
@@ -308,7 +313,9 @@ export function initializeNextTickQueue(process, nextTickQueue, drainMicrotasksF
setup = undefined;
};
function nextTick(cb, ...args) {
const createNextTickQueueEntry = $newCppFunction("JSNextTickQueue.cpp", "jsFunctionCreateNextTickQueueEntry", 1);
function nextTick(cb) {
validateFunction(cb, "callback");
if (setup) {
setup();
@@ -316,13 +323,7 @@ export function initializeNextTickQueue(process, nextTickQueue, drainMicrotasksF
}
if (process._exiting) return;
queue.push({
callback: cb,
// We want to avoid materializing the args if there are none because it's
// a waste of memory and Array.prototype.slice shows up in profiling.
args: $argumentCount() > 1 ? args : undefined,
frame: $getInternalField($asyncContext, 0),
});
queue.push(createNextTickQueueEntry());
$putInternalField(nextTickQueue, 0, 1);
}