Skip to content

Commit

Permalink
process signal events (#3569)
Browse files Browse the repository at this point in the history
* signal events

* simple tests

* ignore SIGSTOP

* better tests

* use `EventEmitter`

* use `Bun__getDefaultGlobal`

* progress

* don't use 'Bun__getDefaultGlobal`

* fix tests

* remove signals from map

* update tests

* don't overwrite event emitter methods

* avoid two lookups

* use `std::once`

* releaseEarly()

* Remove signal handler after use

* Update call-raise.js

* Create process-signal-handler.fixture.js

* Don't register duplicates

* Add missing lock

* another test

* update test

* revert some changes

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
  • Loading branch information
dylan-conway and Jarred-Sumner committed Jul 13, 2023
1 parent afd6f26 commit 96cad48
Show file tree
Hide file tree
Showing 12 changed files with 352 additions and 92 deletions.
3 changes: 2 additions & 1 deletion src/bun.js/bindings/JSBundlerPlugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ class JSBundlerPlugin final : public JSC::JSNonFinalObject {
JSC::LazyProperty<JSBundlerPlugin, JSC::JSFunction> setupFunction;

private:
JSBundlerPlugin(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure, void* config, BunPluginTarget target, JSBundlerPluginAddErrorCallback addError, JSBundlerPluginOnLoadAsyncCallback onLoadAsync, JSBundlerPluginOnResolveAsyncCallback onResolveAsync)
JSBundlerPlugin(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure, void* config, BunPluginTarget target,
JSBundlerPluginAddErrorCallback addError, JSBundlerPluginOnLoadAsyncCallback onLoadAsync, JSBundlerPluginOnResolveAsyncCallback onResolveAsync)
: JSC::JSNonFinalObject(vm, structure)
, plugin(BundlerPlugin(config, target, addError, onLoadAsync, onResolveAsync))
{
Expand Down
290 changes: 208 additions & 82 deletions src/bun.js/bindings/Process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -437,91 +437,215 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionChdir,
return JSC::JSValue::encode(result);
}

// static const NeverDestroyed<String> signalNames[] = {
// MAKE_STATIC_STRING_IMPL("SIGHUP"),
// MAKE_STATIC_STRING_IMPL("SIGINT"),
// MAKE_STATIC_STRING_IMPL("SIGQUIT"),
// MAKE_STATIC_STRING_IMPL("SIGILL"),
// MAKE_STATIC_STRING_IMPL("SIGTRAP"),
// MAKE_STATIC_STRING_IMPL("SIGABRT"),
// MAKE_STATIC_STRING_IMPL("SIGIOT"),
// MAKE_STATIC_STRING_IMPL("SIGBUS"),
// MAKE_STATIC_STRING_IMPL("SIGFPE"),
// MAKE_STATIC_STRING_IMPL("SIGKILL"),
// MAKE_STATIC_STRING_IMPL("SIGUSR1"),
// MAKE_STATIC_STRING_IMPL("SIGSEGV"),
// MAKE_STATIC_STRING_IMPL("SIGUSR2"),
// MAKE_STATIC_STRING_IMPL("SIGPIPE"),
// MAKE_STATIC_STRING_IMPL("SIGALRM"),
// MAKE_STATIC_STRING_IMPL("SIGTERM"),
// MAKE_STATIC_STRING_IMPL("SIGCHLD"),
// MAKE_STATIC_STRING_IMPL("SIGCONT"),
// MAKE_STATIC_STRING_IMPL("SIGSTOP"),
// MAKE_STATIC_STRING_IMPL("SIGTSTP"),
// MAKE_STATIC_STRING_IMPL("SIGTTIN"),
// MAKE_STATIC_STRING_IMPL("SIGTTOU"),
// MAKE_STATIC_STRING_IMPL("SIGURG"),
// MAKE_STATIC_STRING_IMPL("SIGXCPU"),
// MAKE_STATIC_STRING_IMPL("SIGXFSZ"),
// MAKE_STATIC_STRING_IMPL("SIGVTALRM"),
// MAKE_STATIC_STRING_IMPL("SIGPROF"),
// MAKE_STATIC_STRING_IMPL("SIGWINCH"),
// MAKE_STATIC_STRING_IMPL("SIGIO"),
// MAKE_STATIC_STRING_IMPL("SIGINFO"),
// MAKE_STATIC_STRING_IMPL("SIGSYS"),
// };
// static const int signalNumbers[] = {
// SIGHUP,
// SIGINT,
// SIGQUIT,
// SIGILL,
// SIGTRAP,
// SIGABRT,
// SIGIOT,
// SIGBUS,
// SIGFPE,
// SIGKILL,
// SIGUSR1,
// SIGSEGV,
// SIGUSR2,
// SIGPIPE,
// SIGALRM,
// SIGTERM,
// SIGCHLD,
// SIGCONT,
// SIGSTOP,
// SIGTSTP,
// SIGTTIN,
// SIGTTOU,
// SIGURG,
// SIGXCPU,
// SIGXFSZ,
// SIGVTALRM,
// SIGPROF,
// SIGWINCH,
// SIGIO,
// SIGINFO,
// SIGSYS,
// };

// JSC_DEFINE_HOST_FUNCTION(jsFunctionProcessOn, (JSGlobalObject * globalObject, CallFrame* callFrame))
// {
// VM& vm = globalObject->vm();
// auto scope = DECLARE_THROW_SCOPE(vm);

// if (callFrame->argumentCount() < 2) {
// throwVMError(globalObject, scope, "Not enough arguments"_s);
// return JSValue::encode(jsUndefined());
// }

// String eventName = callFrame->uncheckedArgument(0).toWTFString(globalObject);
// RETURN_IF_EXCEPTION(scope, encodedJSValue());
// }
static HashMap<String, int>* signalNameToNumberMap = nullptr;
static HashMap<int, String>* signalNumberToNameMap = nullptr;

// signal number to array of script execution context ids that care about the signal
static HashMap<int, HashSet<uint32_t>>* signalToContextIdsMap = nullptr;
static Lock signalToContextIdsMapLock;

static void onDidChangeListeners(EventEmitter& eventEmitter, const Identifier& eventName, bool isAdded)
{

static const NeverDestroyed<String> signalNames[] = {
MAKE_STATIC_STRING_IMPL("SIGHUP"),
MAKE_STATIC_STRING_IMPL("SIGINT"),
MAKE_STATIC_STRING_IMPL("SIGQUIT"),
MAKE_STATIC_STRING_IMPL("SIGILL"),
MAKE_STATIC_STRING_IMPL("SIGTRAP"),
MAKE_STATIC_STRING_IMPL("SIGABRT"),
MAKE_STATIC_STRING_IMPL("SIGIOT"),
MAKE_STATIC_STRING_IMPL("SIGBUS"),
MAKE_STATIC_STRING_IMPL("SIGFPE"),
MAKE_STATIC_STRING_IMPL("SIGKILL"),
MAKE_STATIC_STRING_IMPL("SIGUSR1"),
MAKE_STATIC_STRING_IMPL("SIGSEGV"),
MAKE_STATIC_STRING_IMPL("SIGUSR2"),
MAKE_STATIC_STRING_IMPL("SIGPIPE"),
MAKE_STATIC_STRING_IMPL("SIGALRM"),
MAKE_STATIC_STRING_IMPL("SIGTERM"),
MAKE_STATIC_STRING_IMPL("SIGCHLD"),
MAKE_STATIC_STRING_IMPL("SIGCONT"),
MAKE_STATIC_STRING_IMPL("SIGSTOP"),
MAKE_STATIC_STRING_IMPL("SIGTSTP"),
MAKE_STATIC_STRING_IMPL("SIGTTIN"),
MAKE_STATIC_STRING_IMPL("SIGTTOU"),
MAKE_STATIC_STRING_IMPL("SIGURG"),
MAKE_STATIC_STRING_IMPL("SIGXCPU"),
MAKE_STATIC_STRING_IMPL("SIGXFSZ"),
MAKE_STATIC_STRING_IMPL("SIGVTALRM"),
MAKE_STATIC_STRING_IMPL("SIGPROF"),
MAKE_STATIC_STRING_IMPL("SIGWINCH"),
MAKE_STATIC_STRING_IMPL("SIGIO"),
MAKE_STATIC_STRING_IMPL("SIGINFO"),
MAKE_STATIC_STRING_IMPL("SIGSYS"),
};

static std::once_flag signalNameToNumberMapOnceFlag;
std::call_once(signalNameToNumberMapOnceFlag, [] {
signalNameToNumberMap = new HashMap<String, int>();
signalNameToNumberMap->reserveInitialCapacity(31);
signalNameToNumberMap->add(signalNames[0], SIGHUP);
signalNameToNumberMap->add(signalNames[1], SIGINT);
signalNameToNumberMap->add(signalNames[2], SIGQUIT);
signalNameToNumberMap->add(signalNames[3], SIGILL);
signalNameToNumberMap->add(signalNames[4], SIGTRAP);
signalNameToNumberMap->add(signalNames[5], SIGABRT);
signalNameToNumberMap->add(signalNames[6], SIGIOT);
signalNameToNumberMap->add(signalNames[7], SIGBUS);
signalNameToNumberMap->add(signalNames[8], SIGFPE);
// signalNameToNumberMap->add(signalNames[9], SIGKILL);
signalNameToNumberMap->add(signalNames[10], SIGUSR1);
signalNameToNumberMap->add(signalNames[11], SIGSEGV);
signalNameToNumberMap->add(signalNames[12], SIGUSR2);
signalNameToNumberMap->add(signalNames[13], SIGPIPE);
signalNameToNumberMap->add(signalNames[14], SIGALRM);
signalNameToNumberMap->add(signalNames[15], SIGTERM);
signalNameToNumberMap->add(signalNames[16], SIGCHLD);
signalNameToNumberMap->add(signalNames[17], SIGCONT);
// signalNameToNumberMap->add(signalNames[18], SIGSTOP);
signalNameToNumberMap->add(signalNames[19], SIGTSTP);
signalNameToNumberMap->add(signalNames[20], SIGTTIN);
signalNameToNumberMap->add(signalNames[21], SIGTTOU);
signalNameToNumberMap->add(signalNames[22], SIGURG);
signalNameToNumberMap->add(signalNames[23], SIGXCPU);
signalNameToNumberMap->add(signalNames[24], SIGXFSZ);
signalNameToNumberMap->add(signalNames[25], SIGVTALRM);
signalNameToNumberMap->add(signalNames[26], SIGPROF);
signalNameToNumberMap->add(signalNames[27], SIGWINCH);
signalNameToNumberMap->add(signalNames[28], SIGIO);
#ifdef SIGINFO
signalNameToNumberMap->add(signalNames[29], SIGINFO);
#endif

#ifndef SIGINFO
signalNameToNumberMap->add(signalNames[29], 255);
#endif
signalNameToNumberMap->add(signalNames[30], SIGSYS);
});

static std::once_flag signalNumberToNameMapOnceFlag;
std::call_once(signalNumberToNameMapOnceFlag, [] {
signalNumberToNameMap = new HashMap<int, String>();
signalNumberToNameMap->reserveInitialCapacity(31);
signalNumberToNameMap->add(SIGHUP, signalNames[0]);
signalNumberToNameMap->add(SIGINT, signalNames[1]);
signalNumberToNameMap->add(SIGQUIT, signalNames[2]);
signalNumberToNameMap->add(SIGILL, signalNames[3]);
signalNumberToNameMap->add(SIGTRAP, signalNames[4]);
signalNumberToNameMap->add(SIGABRT, signalNames[5]);
signalNumberToNameMap->add(SIGIOT, signalNames[6]);
signalNumberToNameMap->add(SIGBUS, signalNames[7]);
signalNumberToNameMap->add(SIGFPE, signalNames[8]);
// signalNumberToNameMap->add(SIGKILL, signalNames[9]);
signalNumberToNameMap->add(SIGUSR1, signalNames[10]);
signalNumberToNameMap->add(SIGSEGV, signalNames[11]);
signalNumberToNameMap->add(SIGUSR2, signalNames[12]);
signalNumberToNameMap->add(SIGPIPE, signalNames[13]);
signalNumberToNameMap->add(SIGALRM, signalNames[14]);
signalNumberToNameMap->add(SIGTERM, signalNames[15]);
signalNumberToNameMap->add(SIGCHLD, signalNames[16]);
signalNumberToNameMap->add(SIGCONT, signalNames[17]);
// signalNumberToNameMap->add(SIGSTOP, signalNames[18]);
signalNumberToNameMap->add(SIGTSTP, signalNames[19]);
signalNumberToNameMap->add(SIGTTIN, signalNames[20]);
signalNumberToNameMap->add(SIGTTOU, signalNames[21]);
signalNumberToNameMap->add(SIGURG, signalNames[22]);
signalNumberToNameMap->add(SIGXCPU, signalNames[23]);
signalNumberToNameMap->add(SIGXFSZ, signalNames[24]);
signalNumberToNameMap->add(SIGVTALRM, signalNames[25]);
signalNumberToNameMap->add(SIGPROF, signalNames[26]);
signalNumberToNameMap->add(SIGWINCH, signalNames[27]);
signalNumberToNameMap->add(SIGIO, signalNames[28]);
#ifdef SIGINFO
signalNameToNumberMap->add(signalNames[29], SIGINFO);
#endif
signalNumberToNameMap->add(SIGSYS, signalNames[30]);
});

if (!signalToContextIdsMap) {
signalToContextIdsMap = new HashMap<int, HashSet<uint32_t>>();
}

if (isAdded) {
if (auto signalNumber = signalNameToNumberMap->get(eventName.string())) {
uint32_t contextId = eventEmitter.scriptExecutionContext()->identifier();
Locker lock { signalToContextIdsMapLock };
if (!signalToContextIdsMap->contains(signalNumber)) {
HashSet<uint32_t> contextIds;
contextIds.add(contextId);
signalToContextIdsMap->set(signalNumber, contextIds);

lock.unlockEarly();

struct sigaction action;
memset(&action, 0, sizeof(struct sigaction));

// Set the handler in the action struct
action.sa_handler = [](int signalNumber) {
if (UNLIKELY(signalNumberToNameMap->find(signalNumber) == signalNumberToNameMap->end()))
return;

Locker lock { signalToContextIdsMapLock };
if (UNLIKELY(signalToContextIdsMap->find(signalNumber) == signalToContextIdsMap->end()))
return;
auto contextIds = signalToContextIdsMap->get(signalNumber);

for (int contextId : contextIds) {
auto* context = ScriptExecutionContext::getScriptExecutionContext(contextId);
if (UNLIKELY(!context))
continue;

JSGlobalObject* lexicalGlobalObject = context->jsGlobalObject();
Zig::GlobalObject* globalObject = static_cast<Zig::GlobalObject*>(lexicalGlobalObject);

Process* process = jsCast<Process*>(globalObject->processObject());

context->postCrossThreadTask(*process, &Process::emitSignalEvent, signalNumber);
}
};

// Clear the sa_mask
sigemptyset(&action.sa_mask);
sigaddset(&action.sa_mask, signalNumber);
action.sa_flags = SA_RESTART;

sigaction(signalNumber, &action, nullptr);
} else {
auto contextIds = signalToContextIdsMap->get(signalNumber);
contextIds.add(contextId);
signalToContextIdsMap->set(signalNumber, contextIds);
}
}
} else {
if (auto signalNumber = signalNameToNumberMap->get(eventName.string())) {
uint32_t contextId = eventEmitter.scriptExecutionContext()->identifier();
Locker lock { signalToContextIdsMapLock };
if (signalToContextIdsMap->find(signalNumber) != signalToContextIdsMap->end()) {
HashSet<uint32_t> contextIds = signalToContextIdsMap->get(signalNumber);
contextIds.remove(contextId);
if (contextIds.isEmpty()) {
signal(signalNumber, SIG_DFL);
signalToContextIdsMap->remove(signalNumber);
} else {
signalToContextIdsMap->set(signalNumber, contextIds);
}
}
}
}
}

void Process::emitSignalEvent(int signalNumber)
{
String signalName = signalNumberToNameMap->get(signalNumber);
Identifier signalNameIdentifier = Identifier::fromString(vm(), signalName);
MarkedArgumentBuffer args;
args.append(jsNumber(signalNumber));
wrapped().emitForBindings(signalNameIdentifier, args);
}

Process::~Process()
{
for (auto& listener : this->wrapped().eventListenerMap().entries()) {
}
}

JSC_DEFINE_HOST_FUNCTION(Process_functionAbort, (JSGlobalObject * globalObject, CallFrame*))
Expand Down Expand Up @@ -1550,6 +1674,8 @@ void Process::finishCreation(JSC::VM& vm)
{
Base::finishCreation(vm);

this->wrapped().onDidChangeListener = &onDidChangeListeners;

this->cpuUsageStructure.initLater([](const JSC::LazyProperty<JSC::JSObject, JSC::Structure>::Initializer& init) {
init.set(constructCPUUsageStructure(init.vm, init.owner->globalObject()));
});
Expand Down
2 changes: 2 additions & 0 deletions src/bun.js/bindings/Process.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class Process : public WebCore::JSEventEmitter {
{
}

void emitSignalEvent(int signalNumber);

DECLARE_EXPORT_INFO;

static void destroy(JSC::JSCell* cell)
Expand Down
6 changes: 6 additions & 0 deletions src/bun.js/bindings/ScriptExecutionContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ static HashMap<ScriptExecutionContextIdentifier, ScriptExecutionContext*>& allSc
return contexts;
}

ScriptExecutionContext* ScriptExecutionContext::getScriptExecutionContext(ScriptExecutionContextIdentifier identifier)
{
Locker locker { allScriptExecutionContextsMapLock };
return allScriptExecutionContextsMap().get(identifier);
}

template<bool SSL, bool isServer>
static void registerHTTPContextForWebSocket(ScriptExecutionContext* script, us_socket_context_t* ctx, us_loop_t* loop)
{
Expand Down
8 changes: 7 additions & 1 deletion src/bun.js/bindings/ScriptExecutionContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,12 @@ class ScriptExecutionContext : public CanMakeWeakPtr<ScriptExecutionContext> {
}
}

const WTF::URL& url() const { return m_url; }
static ScriptExecutionContext* getScriptExecutionContext(ScriptExecutionContextIdentifier identifier);

const WTF::URL& url() const
{
return m_url;
}
bool activeDOMObjectsAreSuspended() { return false; }
bool activeDOMObjectsAreStopped() { return false; }
bool isContextThread() { return true; }
Expand Down Expand Up @@ -141,6 +146,7 @@ class ScriptExecutionContext : public CanMakeWeakPtr<ScriptExecutionContext> {
auto* task = new EventLoopTask(WTFMove(lambda));
postTaskOnTimeout(task, timeout);
}

template<typename... Arguments>
void postCrossThreadTask(Arguments&&... arguments)
{
Expand Down
7 changes: 7 additions & 0 deletions src/bun.js/bindings/webcore/EventEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ bool EventEmitter::addListener(const Identifier& eventType, Ref<EventListener>&&
}

eventListenersDidChange();
if (this->onDidChangeListener)
this->onDidChangeListener(*this, eventType, true);
return true;
}

Expand Down Expand Up @@ -62,6 +64,9 @@ bool EventEmitter::removeListener(const Identifier& eventType, EventListener& li

if (data->eventListenerMap.remove(eventType, listener)) {
eventListenersDidChange();

if (this->onDidChangeListener)
this->onDidChangeListener(*this, eventType, false);
return true;
}
return false;
Expand Down Expand Up @@ -93,6 +98,8 @@ bool EventEmitter::removeAllListeners(const Identifier& eventType)

if (data->eventListenerMap.removeAll(eventType)) {
eventListenersDidChange();
if (this->onDidChangeListener)
this->onDidChangeListener(*this, eventType, false);
return true;
}
return false;
Expand Down
Loading

0 comments on commit 96cad48

Please sign in to comment.