Skip to content

Commit

Permalink
fix test-process-beforeexit.js
Browse files Browse the repository at this point in the history
  • Loading branch information
nektro committed Jan 1, 2025
1 parent d098c67 commit b2e5ac7
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 16 deletions.
13 changes: 9 additions & 4 deletions src/bun.js/bindings/BunProcess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -511,12 +511,16 @@ extern "C" void Process__dispatchOnBeforeExit(Zig::GlobalObject* globalObject, u
if (!globalObject->hasProcessObject()) {
return;
}

auto& vm = globalObject->vm();
auto* process = jsCast<Process*>(globalObject->processObject());
MarkedArgumentBuffer arguments;
arguments.append(jsNumber(exitCode));
Bun__VirtualMachine__exitDuringUncaughtException(bunVM(globalObject->vm()));
process->wrapped().emit(Identifier::fromString(globalObject->vm(), "beforeExit"_s), arguments);
Bun__VirtualMachine__exitDuringUncaughtException(bunVM(vm));
auto fired = process->wrapped().emit(Identifier::fromString(vm, "beforeExit"_s), arguments);
if (fired) {
auto nextTickQueue = jsCast<JSNextTickQueue*>(globalObject->m_nextTickQueue.get());
nextTickQueue->drain(vm, globalObject);
}
}

extern "C" void Process__dispatchOnExit(Zig::GlobalObject* globalObject, uint8_t exitCode)
Expand Down Expand Up @@ -3013,7 +3017,8 @@ JSValue Process::constructNextTickFn(JSC::VM& vm, Zig::GlobalObject* globalObjec
{
JSValue nextTickQueueObject;
if (!globalObject->m_nextTickQueue) {
nextTickQueueObject = Bun::JSNextTickQueue::create(globalObject);
auto nextTickQueue = Bun::JSNextTickQueue::create(globalObject);
nextTickQueueObject = nextTickQueue;
globalObject->m_nextTickQueue.set(vm, globalObject, nextTickQueueObject);
} else {
nextTickQueueObject = jsCast<Bun::JSNextTickQueue*>(globalObject->m_nextTickQueue.get());
Expand Down
23 changes: 14 additions & 9 deletions src/bun.js/bindings/webcore/EventEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ bool EventEmitter::emitForBindings(const Identifier& eventType, const MarkedArgu
return true;
}

void EventEmitter::emit(const Identifier& eventType, const MarkedArgumentBuffer& arguments)
bool EventEmitter::emit(const Identifier& eventType, const MarkedArgumentBuffer& arguments)
{
fireEventListeners(eventType, arguments);
return fireEventListeners(eventType, arguments);
}

void EventEmitter::uncaughtExceptionInEventHandler()
Expand Down Expand Up @@ -175,37 +175,38 @@ Vector<JSObject*> EventEmitter::getListeners(const Identifier& eventType)
}

// https://dom.spec.whatwg.org/#concept-event-listener-invoke
void EventEmitter::fireEventListeners(const Identifier& eventType, const MarkedArgumentBuffer& arguments)
bool EventEmitter::fireEventListeners(const Identifier& eventType, const MarkedArgumentBuffer& arguments)
{

auto* data = eventTargetData();
if (!data)
return;
return false;

auto* listenersVector = data->eventListenerMap.find(eventType);
if (UNLIKELY(!listenersVector)) {
if (eventType == scriptExecutionContext()->vm().propertyNames->error && arguments.size() > 0) {
Ref<EventEmitter> protectedThis(*this);
auto* thisObject = protectedThis->m_thisObject.get();
if (!thisObject)
return;
return false;

Bun__reportUnhandledError(thisObject->globalObject(), JSValue::encode(arguments.at(0)));
return;
return false;
}
return;
return false;
}

bool prevFiringEventListeners = data->isFiringEventListeners;
data->isFiringEventListeners = true;
innerInvokeEventListeners(eventType, *listenersVector, arguments);
auto fired = innerInvokeEventListeners(eventType, *listenersVector, arguments);
data->isFiringEventListeners = prevFiringEventListeners;
return fired;
}

// Intentionally creates a copy of the listeners vector to avoid event listeners added after this point from being run.
// Note that removal still has an effect due to the removed field in RegisteredEventListener.
// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke
void EventEmitter::innerInvokeEventListeners(const Identifier& eventType, SimpleEventListenerVector listeners, const MarkedArgumentBuffer& arguments)
bool EventEmitter::innerInvokeEventListeners(const Identifier& eventType, SimpleEventListenerVector listeners, const MarkedArgumentBuffer& arguments)
{
Ref<EventEmitter> protectedThis(*this);
ASSERT(!listeners.isEmpty());
Expand All @@ -216,6 +217,7 @@ void EventEmitter::innerInvokeEventListeners(const Identifier& eventType, Simple

auto* thisObject = protectedThis->m_thisObject.get();
JSC::JSValue thisValue = thisObject ? JSC::JSValue(thisObject) : JSC::jsUndefined();
auto fired = false;

for (auto& registeredListener : listeners) {
// The below code used to be in here, but it's WRONG. Even if a listener is removed,
Expand Down Expand Up @@ -244,6 +246,7 @@ void EventEmitter::innerInvokeEventListeners(const Identifier& eventType, Simple
if (UNLIKELY(callData.type == JSC::CallData::Type::None))
continue;

fired = true;
WTF::NakedPtr<JSC::Exception> exceptionPtr;
call(lexicalGlobalObject, jsFunction, callData, thisValue, arguments, exceptionPtr);
auto* exception = exceptionPtr.get();
Expand All @@ -265,6 +268,8 @@ void EventEmitter::innerInvokeEventListeners(const Identifier& eventType, Simple
}
}
}

return fired;
}

Vector<Identifier> EventEmitter::eventTypes()
Expand Down
6 changes: 3 additions & 3 deletions src/bun.js/bindings/webcore/EventEmitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class EventEmitter final : public ScriptWrappable, public CanMakeWeakPtr<EventEm
WEBCORE_EXPORT bool removeListener(const Identifier& eventType, EventListener&);
WEBCORE_EXPORT bool removeAllListeners(const Identifier& eventType);

WEBCORE_EXPORT void emit(const Identifier&, const MarkedArgumentBuffer&);
WEBCORE_EXPORT bool emit(const Identifier&, const MarkedArgumentBuffer&);
WEBCORE_EXPORT void uncaughtExceptionInEventHandler();

WEBCORE_EXPORT Vector<Identifier> getEventNames();
Expand All @@ -76,7 +76,7 @@ class EventEmitter final : public ScriptWrappable, public CanMakeWeakPtr<EventEm
Vector<Identifier> eventTypes();
const SimpleEventListenerVector& eventListeners(const Identifier& eventType);

void fireEventListeners(const Identifier& eventName, const MarkedArgumentBuffer& arguments);
bool fireEventListeners(const Identifier& eventName, const MarkedArgumentBuffer& arguments);
bool isFiringEventListeners() const;

void invalidateJSEventListeners(JSC::JSObject*);
Expand Down Expand Up @@ -109,7 +109,7 @@ class EventEmitter final : public ScriptWrappable, public CanMakeWeakPtr<EventEm
{
}

void innerInvokeEventListeners(const Identifier&, SimpleEventListenerVector, const MarkedArgumentBuffer& arguments);
bool innerInvokeEventListeners(const Identifier&, SimpleEventListenerVector, const MarkedArgumentBuffer& arguments);
void invalidateEventListenerRegions();

EventEmitterData m_eventTargetData;
Expand Down
81 changes: 81 additions & 0 deletions test/js/node/test/parallel/test-process-beforeexit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

'use strict';
const common = require('../common');
const net = require('net');

process.once('beforeExit', common.mustCall(tryImmediate));

function tryImmediate() {
setImmediate(common.mustCall(() => {
process.once('beforeExit', common.mustCall(tryTimer));
}));
}

function tryTimer() {
setTimeout(common.mustCall(() => {
process.once('beforeExit', common.mustCall(tryListen));
}), 1);
}

function tryListen() {
net.createServer()
.listen(0)
.on('listening', common.mustCall(function() {
this.close();
process.once('beforeExit', common.mustCall(tryRepeatedTimer));
}));
}

// Test that a function invoked from the beforeExit handler can use a timer
// to keep the event loop open, which can use another timer to keep the event
// loop open, etc.
//
// After N times, call function `tryNextTick` to test behaviors of the
// `process.nextTick`.
function tryRepeatedTimer() {
const N = 5;
let n = 0;
const repeatedTimer = common.mustCall(function() {
if (++n < N)
setTimeout(repeatedTimer, 1);
else // n == N
process.once('beforeExit', common.mustCall(tryNextTickSetImmediate));
}, N);
setTimeout(repeatedTimer, 1);
}

// Test if the callback of `process.nextTick` can be invoked.
function tryNextTickSetImmediate() {
process.nextTick(common.mustCall(function() {
setImmediate(common.mustCall(() => {
process.once('beforeExit', common.mustCall(tryNextTick));
}));
}));
}

// Test that `process.nextTick` won't keep the event loop running by itself.
function tryNextTick() {
process.nextTick(common.mustCall(function() {
process.once('beforeExit', common.mustNotCall());
}));
}

0 comments on commit b2e5ac7

Please sign in to comment.