Implement process.on("beforeExit", cb) and process.on("exit", cb) (#3576)

* Support `process.on('beforeExit')` and `process.on('exit')`

* [bun:sqlite] Always call sqlite3_close on exit

* Update process.test.js

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
This commit is contained in:
Jarred Sumner
2023-07-08 14:26:19 -07:00
committed by GitHub
parent fa632c3331
commit aa8b832ef6
13 changed files with 402 additions and 78 deletions

View File

@@ -42,6 +42,35 @@ static JSC_DECLARE_CUSTOM_GETTER(Process_getPID);
static JSC_DECLARE_CUSTOM_GETTER(Process_getPPID);
static JSC_DECLARE_HOST_FUNCTION(Process_functionCwd);
static bool processIsExiting = false;
extern "C" uint8_t Bun__getExitCode(void*);
extern "C" uint8_t Bun__setExitCode(void*, uint8_t);
extern "C" void* Bun__getVM();
extern "C" Zig::GlobalObject* Bun__getDefaultGlobal();
static void dispatchExitInternal(JSC::JSGlobalObject* globalObject, Process* process, int exitCode)
{
if (processIsExiting)
return;
processIsExiting = true;
auto& emitter = process->wrapped();
auto& vm = globalObject->vm();
if (vm.hasTerminationRequest() || vm.hasExceptionsAfterHandlingTraps())
return;
auto event = Identifier::fromString(vm, "exit"_s);
if (!emitter.hasEventListeners(event)) {
return;
}
process->putDirect(vm, Identifier::fromString(vm, "_exiting"_s), jsBoolean(true), 0);
MarkedArgumentBuffer arguments;
arguments.append(jsNumber(exitCode));
emitter.emit(event, arguments);
}
static JSValue constructStdioWriteStream(JSC::JSGlobalObject* globalObject, int fd)
{
@@ -324,6 +353,29 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionUmask,
extern "C" uint64_t Bun__readOriginTimer(void*);
extern "C" double Bun__readOriginTimerStart(void*);
// https://github.com/nodejs/node/blob/1936160c31afc9780e4365de033789f39b7cbc0c/src/api/hooks.cc#L49
extern "C" void Process__dispatchOnBeforeExit(Zig::GlobalObject* globalObject, uint8_t exitCode)
{
if (!globalObject->hasProcessObject()) {
return;
}
auto* process = jsCast<Process*>(globalObject->processObject());
MarkedArgumentBuffer arguments;
arguments.append(jsNumber(exitCode));
process->wrapped().emit(Identifier::fromString(globalObject->vm(), "beforeExit"_s), arguments);
}
extern "C" void Process__dispatchOnExit(Zig::GlobalObject* globalObject, uint8_t exitCode)
{
if (!globalObject->hasProcessObject()) {
return;
}
auto* process = jsCast<Process*>(globalObject->processObject());
dispatchExitInternal(globalObject, process, exitCode);
}
JSC_DEFINE_HOST_FUNCTION(Process_functionUptime,
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
@@ -336,14 +388,38 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionUptime,
JSC_DEFINE_HOST_FUNCTION(Process_functionExit,
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
if (callFrame->argumentCount() == 0) {
// TODO: exitCode
Bun__Process__exit(globalObject, 0);
auto throwScope = DECLARE_THROW_SCOPE(globalObject->vm());
uint8_t exitCode = 0;
JSValue arg0 = callFrame->argument(0);
if (arg0.isNumber()) {
if (!arg0.isInt32()) {
throwRangeError(globalObject, throwScope, "The \"code\" argument must be an integer"_s);
return JSC::JSValue::encode(JSC::JSValue {});
}
int extiCode32 = arg0.toInt32(globalObject);
RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::JSValue {}));
if (extiCode32 < 0 || extiCode32 > 127) {
throwRangeError(globalObject, throwScope, "The \"code\" argument must be an integer between 0 and 127"_s);
return JSC::JSValue::encode(JSC::JSValue {});
}
exitCode = static_cast<uint8_t>(extiCode32);
} else if (!arg0.isUndefinedOrNull()) {
throwTypeError(globalObject, throwScope, "The \"code\" argument must be an integer"_s);
return JSC::JSValue::encode(JSC::JSValue {});
} else {
Bun__Process__exit(globalObject, callFrame->argument(0).toInt32(globalObject));
exitCode = Bun__getExitCode(Bun__getVM());
}
return JSC::JSValue::encode(JSC::jsUndefined());
auto* zigGlobal = jsDynamicCast<Zig::GlobalObject*>(globalObject);
if (UNLIKELY(!zigGlobal)) {
zigGlobal = Bun__getDefaultGlobal();
}
Process__dispatchOnExit(zigGlobal, exitCode);
Bun__Process__exit(zigGlobal, exitCode);
}
extern "C" uint64_t Bun__readOriginTimer(void*);
@@ -391,18 +467,15 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionHRTime,
array->setIndexQuickly(vm, 1, JSC::jsNumber(nanoseconds));
return JSC::JSValue::encode(JSC::JSValue(array));
}
static JSC_DECLARE_HOST_FUNCTION(Process_functionHRTimeBigInt);
static JSC_DEFINE_HOST_FUNCTION(Process_functionHRTimeBigInt,
JSC_DEFINE_HOST_FUNCTION(Process_functionHRTimeBigInt,
(JSC::JSGlobalObject * globalObject_, JSC::CallFrame* callFrame))
{
Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(globalObject_);
return JSC::JSValue::encode(JSValue(JSC::JSBigInt::createFrom(globalObject, Bun__readOriginTimer(globalObject->bunVM()))));
}
static JSC_DECLARE_HOST_FUNCTION(Process_functionChdir);
static JSC_DEFINE_HOST_FUNCTION(Process_functionChdir,
JSC_DEFINE_HOST_FUNCTION(Process_functionChdir,
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
@@ -611,6 +684,46 @@ JSC_DEFINE_CUSTOM_GETTER(Process_lazyExecArgvGetter, (JSC::JSGlobalObject * glob
return ret;
}
JSC_DEFINE_CUSTOM_GETTER(Process__getExitCode, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName name))
{
Process* process = jsDynamicCast<Process*>(JSValue::decode(thisValue));
if (!process) {
return JSValue::encode(jsUndefined());
}
return JSValue::encode(jsNumber(Bun__getExitCode(jsCast<Zig::GlobalObject*>(process->globalObject())->bunVM())));
}
JSC_DEFINE_CUSTOM_SETTER(Process__setExitCode, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, JSC::PropertyName))
{
Process* process = jsDynamicCast<Process*>(JSValue::decode(thisValue));
if (!process) {
return false;
}
auto throwScope = DECLARE_THROW_SCOPE(process->vm());
JSValue exitCode = JSValue::decode(value);
if (!exitCode.isNumber()) {
throwTypeError(lexicalGlobalObject, throwScope, "exitCode must be a number"_s);
return false;
}
if (!exitCode.isInt32()) {
throwRangeError(lexicalGlobalObject, throwScope, "The \"code\" argument must be an integer"_s);
return JSC::JSValue::encode(JSC::JSValue {});
}
int exitCodeInt = exitCode.toInt32(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, false);
if (exitCodeInt < 0 || exitCodeInt > 127) {
throwRangeError(lexicalGlobalObject, throwScope, "exitCode must be between 0 and 127"_s);
return false;
}
void* ptr = jsCast<Zig::GlobalObject*>(process->globalObject())->bunVM();
Bun__setExitCode(ptr, static_cast<uint8_t>(exitCodeInt));
return true;
}
JSC_DEFINE_CUSTOM_GETTER(Process_lazyExecPathGetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName name))
{
JSC::JSObject* thisObject = JSValue::decode(thisValue).getObject();
@@ -677,39 +790,42 @@ void Process::finishCreation(JSC::VM& vm)
vm, clientData->builtinNames().versionsPublicName(),
JSC::CustomGetterSetter::create(vm, Process_getVersionsLazy, Process_setVersionsLazy), 0);
// this should be transpiled out, but just incase
this->putDirect(this->vm(), JSC::Identifier::fromString(this->vm(), "browser"_s),
JSC::JSValue(false));
this->putDirect(vm, JSC::Identifier::fromString(vm, "browser"_s),
JSC::JSValue(false), PropertyAttribute::DontEnum | 0);
this->putDirect(this->vm(), JSC::Identifier::fromString(this->vm(), "exitCode"_s),
JSC::JSValue(JSC::jsNumber(0)));
this->putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "exitCode"_s),
JSC::CustomGetterSetter::create(vm,
Process__getExitCode,
Process__setExitCode),
0);
this->putDirect(this->vm(), clientData->builtinNames().versionPublicName(),
JSC::jsString(this->vm(), makeString("v", REPORTED_NODE_VERSION)));
this->putDirect(vm, clientData->builtinNames().versionPublicName(),
JSC::jsString(vm, makeString("v", REPORTED_NODE_VERSION)));
// this gives some way of identifying at runtime whether the SSR is happening in node or not.
// this should probably be renamed to what the name of the bundler is, instead of "notNodeJS"
// but it must be something that won't evaluate to truthy in Node.js
this->putDirect(this->vm(), JSC::Identifier::fromString(this->vm(), "isBun"_s), JSC::JSValue(true));
this->putDirect(vm, JSC::Identifier::fromString(vm, "isBun"_s), JSC::JSValue(true));
#if defined(__APPLE__)
this->putDirect(this->vm(), JSC::Identifier::fromString(this->vm(), "platform"_s),
JSC::jsString(this->vm(), makeAtomString("darwin")));
this->putDirect(vm, JSC::Identifier::fromString(vm, "platform"_s),
JSC::jsString(vm, makeAtomString("darwin")));
#else
this->putDirect(this->vm(), JSC::Identifier::fromString(this->vm(), "platform"_s),
JSC::jsString(this->vm(), makeAtomString("linux")));
this->putDirect(vm, JSC::Identifier::fromString(vm, "platform"_s),
JSC::jsString(vm, makeAtomString("linux")));
#endif
#if defined(__x86_64__)
this->putDirect(this->vm(), JSC::Identifier::fromString(this->vm(), "arch"_s),
JSC::jsString(this->vm(), makeAtomString("x64")));
this->putDirect(vm, JSC::Identifier::fromString(vm, "arch"_s),
JSC::jsString(vm, makeAtomString("x64")));
#elif defined(__i386__)
this->putDirect(this->vm(), JSC::Identifier::fromString(this->vm(), "arch"_s),
JSC::jsString(this->vm(), makeAtomString("x86")));
this->putDirect(vm, JSC::Identifier::fromString(vm, "arch"_s),
JSC::jsString(vm, makeAtomString("x86")));
#elif defined(__arm__)
this->putDirect(this->vm(), JSC::Identifier::fromString(this->vm(), "arch"_s),
JSC::jsString(this->vm(), makeAtomString("arm")));
this->putDirect(vm, JSC::Identifier::fromString(vm, "arch"_s),
JSC::jsString(vm, makeAtomString("arm")));
#elif defined(__aarch64__)
this->putDirect(this->vm(), JSC::Identifier::fromString(this->vm(), "arch"_s),
JSC::jsString(this->vm(), makeAtomString("arm64")));
this->putDirect(vm, JSC::Identifier::fromString(vm, "arch"_s),
JSC::jsString(vm, makeAtomString("arm64")));
#endif
JSC::JSFunction* hrtime = JSC::JSFunction::create(vm, globalObject, 0,
@@ -719,7 +835,7 @@ void Process::finishCreation(JSC::VM& vm)
MAKE_STATIC_STRING_IMPL("bigint"), Process_functionHRTimeBigInt, ImplementationVisibility::Public);
hrtime->putDirect(vm, JSC::Identifier::fromString(vm, "bigint"_s), hrtimeBigInt);
this->putDirect(this->vm(), JSC::Identifier::fromString(this->vm(), "hrtime"_s), hrtime);
this->putDirect(vm, JSC::Identifier::fromString(vm, "hrtime"_s), hrtime);
this->putDirectCustomAccessor(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "release"_s)),
JSC::CustomGetterSetter::create(vm, Process_getterRelease, Process_setterRelease), 0);
@@ -733,7 +849,10 @@ void Process::finishCreation(JSC::VM& vm)
this->putDirectCustomAccessor(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "stdin"_s)),
JSC::CustomGetterSetter::create(vm, Process_lazyStdinGetter, Process_defaultSetter), 0);
this->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(this->vm(), "abort"_s),
this->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "abort"_s),
0, Process_functionAbort, ImplementationVisibility::Public, NoIntrinsic, 0);
this->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "abort"_s),
0, Process_functionAbort, ImplementationVisibility::Public, NoIntrinsic, 0);
this->putDirectCustomAccessor(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "argv0"_s)),
@@ -745,13 +864,13 @@ void Process::finishCreation(JSC::VM& vm)
this->putDirectCustomAccessor(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "execArgv"_s)),
JSC::CustomGetterSetter::create(vm, Process_lazyExecArgvGetter, Process_defaultSetter), 0);
this->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(this->vm(), "uptime"_s),
this->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "uptime"_s),
0, Process_functionUptime, ImplementationVisibility::Public, NoIntrinsic, 0);
this->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(this->vm(), "umask"_s),
this->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "umask"_s),
1, Process_functionUmask, ImplementationVisibility::Public, NoIntrinsic, 0);
this->putDirectBuiltinFunction(vm, globalObject, JSC::Identifier::fromString(this->vm(), "binding"_s),
this->putDirectBuiltinFunction(vm, globalObject, JSC::Identifier::fromString(vm, "binding"_s),
processObjectInternalsBindingCodeGenerator(vm),
0);
@@ -788,7 +907,7 @@ void Process::finishCreation(JSC::VM& vm)
config->putDirect(vm, JSC::Identifier::fromString(vm, "variables"_s), variables, 0);
this->putDirect(vm, JSC::Identifier::fromString(vm, "config"_s), config, 0);
this->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(this->vm(), "emitWarning"_s),
this->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "emitWarning"_s),
1, Process_emitWarning, ImplementationVisibility::Public, NoIntrinsic, 0);
JSC::JSFunction* requireDotMainFunction = JSFunction::create(

View File

@@ -270,6 +270,8 @@ public:
JSWeakMap* vmModuleContextMap() { return m_vmModuleContextMap.getInitializedOnMainThread(this); }
bool hasProcessObject() const { return m_processObject.isInitialized(); }
JSC::JSObject* processObject()
{
return m_processObject.getInitializedOnMainThread(this);

View File

@@ -107,6 +107,50 @@ static JSC_DECLARE_HOST_FUNCTION(jsSQLStatementDeserialize);
return JSValue::encode(jsUndefined()); \
}
class VersionSqlite3 {
public:
explicit VersionSqlite3(sqlite3* db)
: db(db)
, version(0)
{
}
sqlite3* db;
std::atomic<uint64_t> version;
};
class SQLiteSingleton {
public:
Vector<VersionSqlite3*> databases;
Vector<std::atomic<uint64_t>> schema_versions;
};
static SQLiteSingleton* _instance = nullptr;
static Vector<VersionSqlite3*>& databases()
{
if (!_instance) {
_instance = new SQLiteSingleton();
_instance->databases = Vector<VersionSqlite3*>();
_instance->databases.reserveInitialCapacity(4);
_instance->schema_versions = Vector<std::atomic<uint64_t>>();
}
return _instance->databases;
}
extern "C" void Bun__closeAllSQLiteDatabasesForTermination()
{
if (!_instance) {
return;
}
auto& dbs = _instance->databases;
for (auto& db : dbs) {
if (db->db)
sqlite3_close_v2(db->db);
}
}
namespace WebCore {
using namespace JSC;
@@ -272,10 +316,6 @@ void JSSQLStatement::destroy(JSC::JSCell* cell)
void JSSQLStatementConstructor::destroy(JSC::JSCell* cell)
{
JSSQLStatementConstructor* thisObject = static_cast<JSSQLStatementConstructor*>(cell);
for (auto version_db : thisObject->databases) {
delete version_db;
}
}
static inline bool rebindValue(JSC::JSGlobalObject* lexicalGlobalObject, sqlite3_stmt* stmt, int i, JSC::JSValue value, JSC::ThrowScope& scope, bool clone)
@@ -547,8 +587,8 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementDeserialize, (JSC::JSGlobalObject * lexic
return JSValue::encode(JSC::jsUndefined());
}
auto count = thisObject->databases.size();
thisObject->databases.append(new VersionSqlite3(db));
auto count = databases().size();
databases().append(new VersionSqlite3(db));
RELEASE_AND_RETURN(scope, JSValue::encode(jsNumber(count)));
}
@@ -565,12 +605,12 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementSerialize, (JSC::JSGlobalObject * lexical
}
int32_t dbIndex = callFrame->argument(0).toInt32(lexicalGlobalObject);
if (UNLIKELY(dbIndex < 0 || dbIndex >= thisObject->databases.size())) {
if (UNLIKELY(dbIndex < 0 || dbIndex >= databases().size())) {
throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Invalid database handle"_s));
return JSValue::encode(JSC::jsUndefined());
}
sqlite3* db = thisObject->databases[dbIndex]->db;
sqlite3* db = databases()[dbIndex]->db;
if (UNLIKELY(!db)) {
throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Can't do this on a closed database"_s));
return JSValue::encode(JSC::jsUndefined());
@@ -606,7 +646,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementLoadExtensionFunction, (JSC::JSGlobalObje
}
int32_t dbIndex = callFrame->argument(0).toInt32(lexicalGlobalObject);
if (UNLIKELY(dbIndex < 0 || dbIndex >= thisObject->databases.size())) {
if (UNLIKELY(dbIndex < 0 || dbIndex >= databases().size())) {
throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Invalid database handle"_s));
return JSValue::encode(JSC::jsUndefined());
}
@@ -620,7 +660,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementLoadExtensionFunction, (JSC::JSGlobalObje
auto extensionString = extension.toWTFString(lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, {});
sqlite3* db = thisObject->databases[dbIndex]->db;
sqlite3* db = databases()[dbIndex]->db;
if (UNLIKELY(!db)) {
throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Can't do this on a closed database"_s));
return JSValue::encode(JSC::jsUndefined());
@@ -661,11 +701,11 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteFunction, (JSC::JSGlobalObject * l
}
int32_t handle = callFrame->argument(0).toInt32(lexicalGlobalObject);
if (thisObject->databases.size() < handle) {
if (databases().size() < handle) {
throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Invalid database handle"_s));
return JSValue::encode(JSC::jsUndefined());
}
sqlite3* db = thisObject->databases[handle]->db;
sqlite3* db = databases()[handle]->db;
if (UNLIKELY(!db)) {
throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Database has closed"_s));
@@ -724,7 +764,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteFunction, (JSC::JSGlobalObject * l
rc = sqlite3_step(statement);
if (!sqlite3_stmt_readonly(statement)) {
thisObject->databases[handle]->version++;
databases()[handle]->version++;
}
while (rc == SQLITE_ROW) {
@@ -765,12 +805,12 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementIsInTransactionFunction, (JSC::JSGlobalOb
int32_t handle = dbNumber.toInt32(lexicalGlobalObject);
if (handle < 0 || handle > thisObject->databases.size()) {
if (handle < 0 || handle > databases().size()) {
throwException(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "Invalid database handle"_s));
return JSValue::encode(JSC::jsUndefined());
}
sqlite3* db = thisObject->databases[handle]->db;
sqlite3* db = databases()[handle]->db;
if (UNLIKELY(!db)) {
throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Database has closed"_s));
@@ -803,12 +843,12 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementPrepareStatementFunction, (JSC::JSGlobalO
}
int32_t handle = dbNumber.toInt32(lexicalGlobalObject);
if (handle < 0 || handle > thisObject->databases.size()) {
if (handle < 0 || handle > databases().size()) {
throwException(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "Invalid database handle"_s));
return JSValue::encode(JSC::jsUndefined());
}
sqlite3* db = thisObject->databases[handle]->db;
sqlite3* db = databases()[handle]->db;
if (!db) {
throwException(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "Cannot use a closed database"_s));
return JSValue::encode(JSC::jsUndefined());
@@ -848,7 +888,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementPrepareStatementFunction, (JSC::JSGlobalO
auto* structure = JSSQLStatement::createStructure(vm, lexicalGlobalObject, lexicalGlobalObject->objectPrototype());
// auto* structure = JSSQLStatement::createStructure(vm, globalObject(), thisObject->getDirect(vm, vm.propertyNames->prototype));
JSSQLStatement* sqlStatement = JSSQLStatement::create(
structure, reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject), statement, thisObject->databases[handle]);
structure, reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject), statement, databases()[handle]);
if (bindings.isObject()) {
auto* castedThis = sqlStatement;
DO_REBIND(bindings)
@@ -924,8 +964,8 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementOpenStatementFunction, (JSC::JSGlobalObje
status = sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, NULL);
assert(status == SQLITE_OK);
auto count = constructor->databases.size();
constructor->databases.append(new VersionSqlite3(db));
auto count = databases().size();
databases().append(new VersionSqlite3(db));
RELEASE_AND_RETURN(scope, JSValue::encode(jsNumber(count)));
}
@@ -956,12 +996,12 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementCloseStatementFunction, (JSC::JSGlobalObj
int dbIndex = dbNumber.toInt32(lexicalGlobalObject);
if (dbIndex < 0 || dbIndex >= constructor->databases.size()) {
if (dbIndex < 0 || dbIndex >= databases().size()) {
throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Invalid database handle"_s));
return JSValue::encode(jsUndefined());
}
sqlite3* db = constructor->databases[dbIndex]->db;
sqlite3* db = databases()[dbIndex]->db;
// no-op if already closed
if (!db) {
return JSValue::encode(jsUndefined());
@@ -973,7 +1013,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementCloseStatementFunction, (JSC::JSGlobalObj
return JSValue::encode(jsUndefined());
}
constructor->databases[dbIndex]->db = nullptr;
databases()[dbIndex]->db = nullptr;
return JSValue::encode(jsUndefined());
}

View File

@@ -47,17 +47,6 @@
namespace WebCore {
class VersionSqlite3 {
public:
explicit VersionSqlite3(sqlite3* db)
: db(db)
, version(0)
{
}
sqlite3* db;
std::atomic<uint64_t> version;
};
class JSSQLStatementConstructor final : public JSC::JSFunction {
public:
using Base = JSC::JSFunction;
@@ -82,13 +71,9 @@ public:
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
Vector<VersionSqlite3*> databases;
Vector<std::atomic<uint64_t>> schema_versions;
private:
JSSQLStatementConstructor(JSC::VM& vm, NativeExecutable* native, JSGlobalObject* globalObject, JSC::Structure* structure)
: Base(vm, native, globalObject, structure)
, databases()
{
}

View File

@@ -334,6 +334,33 @@ pub export fn Bun__onDidAppendPlugin(jsc_vm: *VirtualMachine, globalObject: *JSG
jsc_vm.bundler.linker.plugin_runner = &jsc_vm.plugin_runner.?;
}
pub const ExitHandler = struct {
exit_code: u8 = 0,
pub export fn Bun__getExitCode(vm: *VirtualMachine) u8 {
return vm.exit_handler.exit_code;
}
pub export fn Bun__setExitCode(vm: *VirtualMachine, code: u8) void {
vm.exit_handler.exit_code = code;
}
extern fn Process__dispatchOnBeforeExit(*JSC.JSGlobalObject, code: u8) void;
extern fn Process__dispatchOnExit(*JSC.JSGlobalObject, code: u8) void;
extern fn Bun__closeAllSQLiteDatabasesForTermination() void;
pub fn dispatchOnExit(this: *ExitHandler) void {
var vm = @fieldParentPtr(VirtualMachine, "exit_handler", this);
Process__dispatchOnExit(vm.global, this.exit_code);
Bun__closeAllSQLiteDatabasesForTermination();
}
pub fn dispatchOnBeforeExit(this: *ExitHandler) void {
var vm = @fieldParentPtr(VirtualMachine, "exit_handler", this);
Process__dispatchOnBeforeExit(vm.global, this.exit_code);
}
};
/// TODO: rename this to ScriptExecutionContext
/// This is the shared global state for a single JS instance execution
/// Today, Bun is one VM per thread, so the name "VirtualMachine" sort of makes sense
@@ -376,6 +403,7 @@ pub const VirtualMachine = struct {
plugin_runner: ?PluginRunner = null,
is_main_thread: bool = false,
last_reported_error_for_dedupe: JSValue = .zero,
exit_handler: ExitHandler = .{},
/// Do not access this field directly
/// It exists in the VirtualMachine struct so that
@@ -620,7 +648,29 @@ pub const VirtualMachine = struct {
loop.run();
}
pub fn onBeforeExit(this: *VirtualMachine) void {
this.exit_handler.dispatchOnBeforeExit();
var dispatch = false;
while (true) {
while (this.eventLoop().tasks.count > 0 or this.active_tasks > 0 or this.uws_event_loop.?.active > 0) : (dispatch = true) {
this.tick();
this.eventLoop().autoTickActive();
}
if (dispatch) {
this.exit_handler.dispatchOnBeforeExit();
dispatch = false;
if (this.eventLoop().tasks.count > 0 or this.active_tasks > 0 or this.uws_event_loop.?.active > 0) continue;
}
break;
}
}
pub fn onExit(this: *VirtualMachine) void {
this.exit_handler.dispatchOnExit();
var rare_data = this.rare_data orelse return;
var hook = rare_data.cleanup_hook orelse return;
hook.execute();

View File

@@ -2202,7 +2202,9 @@ pub const Process = struct {
}
}
pub fn exit(_: *JSC.JSGlobalObject, code: i32) callconv(.C) void {
pub fn exit(globalObject: *JSC.JSGlobalObject, code: i32) callconv(.C) void {
globalObject.bunVM().onExit();
std.os.exit(@truncate(u8, @intCast(u32, @max(code, 0))));
}

View File

@@ -248,6 +248,8 @@ pub const Run = struct {
vm.eventLoop().tick();
vm.eventLoop().tickPossiblyForever();
} else {
vm.exit_handler.exit_code = 1;
vm.onExit();
Global.exit(1);
}
}
@@ -279,6 +281,8 @@ pub const Run = struct {
vm.eventLoop().tick();
vm.eventLoop().tickPossiblyForever();
} else {
vm.exit_handler.exit_code = 1;
vm.onExit();
Global.exit(1);
}
}
@@ -315,6 +319,8 @@ pub const Run = struct {
vm.eventLoop().autoTickActive();
}
vm.onBeforeExit();
if (this.vm.pending_internal_promise.status(vm.global.vm()) == .Rejected and prev_promise != this.vm.pending_internal_promise) {
prev_promise = this.vm.pending_internal_promise;
vm.onUnhandledError(this.vm.global, this.vm.pending_internal_promise.result(vm.global.vm()));
@@ -332,6 +338,8 @@ pub const Run = struct {
vm.tick();
vm.eventLoop().autoTickActive();
}
vm.onBeforeExit();
}
if (vm.log.msgs.items.len > 0) {
@@ -347,10 +355,14 @@ pub const Run = struct {
vm.onUnhandledRejection = &onUnhandledRejectionBeforeClose;
vm.global.handleRejectedPromises();
if (this.any_unhandled and this.vm.exit_handler.exit_code == 0) {
this.vm.exit_handler.exit_code = 1;
}
const exit_code = this.vm.exit_handler.exit_code;
vm.onExit();
if (!JSC.is_bindgen) JSC.napi.fixDeadCodeElimination();
Global.exit(@intFromBool(this.any_unhandled));
Global.exit(exit_code);
}
};

View File

@@ -0,0 +1,16 @@
process.on("beforeExit", () => {
throw new Error("process.on('beforeExit') called");
});
if (process._exiting) {
throw new Error("process._exiting should be undefined");
}
process.on("exit", () => {
if (!process._exiting) {
throw new Error("process.on('exit') called with process._exiting false");
}
console.log("PASS");
});
process.exit(0);

View File

@@ -0,0 +1,7 @@
process.exitCode = Number(process.argv.at(-1));
process.on("exit", code => {
if (code !== process.exitCode) {
throw new Error("process.exitCode should be " + process.exitCode);
}
console.log("PASS");
});

View File

@@ -0,0 +1,8 @@
process.exitCode = Number(process.argv.at(-1));
process.on("exit", code => {
if (code !== process.exitCode) {
throw new Error("process.exitCode should be " + process.exitCode);
}
console.log("PASS");
});
process.exit();

View File

@@ -0,0 +1,7 @@
process.on("beforeExit", () => {
console.log("beforeExit");
});
process.on("exit", () => {
console.log("exit");
});

View File

@@ -0,0 +1,18 @@
let counter = 0;
process.on("beforeExit", () => {
if (process._exiting) {
throw new Error("process._exiting should be undefined");
}
console.log("beforeExit:", counter);
if (!counter++) {
setTimeout(() => {}, 1);
}
});
process.on("exit", () => {
if (!process._exiting) {
throw new Error("process.on('exit') called with process._exiting false");
}
console.log("exit:", counter);
});

View File

@@ -1,8 +1,8 @@
import { resolveSync, which } from "bun";
import { resolveSync, spawnSync, which } from "bun";
import { describe, expect, it } from "bun:test";
import { existsSync, readFileSync, realpathSync } from "fs";
import { bunExe } from "harness";
import { basename, resolve } from "path";
import { bunEnv, bunExe } from "harness";
import { basename, join, resolve } from "path";
it("process", () => {
// this property isn't implemented yet but it should at least return a string
@@ -233,3 +233,61 @@ it("process.argv in testing", () => {
// assert we aren't creating a new process.argv each call
expect(process.argv).toBe(process.argv);
});
describe("process.exitCode", () => {
it("validates int", () => {
expect(() => (process.exitCode = "potato")).toThrow("exitCode must be a number");
expect(() => (process.exitCode = 1.2)).toThrow('The "code" argument must be an integer');
expect(() => (process.exitCode = NaN)).toThrow('The "code" argument must be an integer');
expect(() => (process.exitCode = Infinity)).toThrow('The "code" argument must be an integer');
expect(() => (process.exitCode = -Infinity)).toThrow('The "code" argument must be an integer');
expect(() => (process.exitCode = -1)).toThrow("exitCode must be between 0 and 127");
});
it("works with implicit process.exit", () => {
const { exitCode, stdout } = spawnSync({
cmd: [bunExe(), join(import.meta.dir, "process-exitCode-with-exit.js"), "42"],
env: bunEnv,
});
expect(exitCode).toBe(42);
expect(stdout.toString().trim()).toBe("PASS");
});
it("works with explicit process.exit", () => {
const { exitCode, stdout } = spawnSync({
cmd: [bunExe(), join(import.meta.dir, "process-exitCode-fixture.js"), "42"],
env: bunEnv,
});
expect(exitCode).toBe(42);
expect(stdout.toString().trim()).toBe("PASS");
});
});
it("process.exit", () => {
const { exitCode, stdout } = spawnSync({
cmd: [bunExe(), join(import.meta.dir, "process-exit-fixture.js")],
env: bunEnv,
});
expect(exitCode).toBe(0);
expect(stdout.toString().trim()).toBe("PASS");
});
describe("process.onBeforeExit", () => {
it("emitted", () => {
const { exitCode, stdout } = spawnSync({
cmd: [bunExe(), join(import.meta.dir, "process-onBeforeExit-fixture.js")],
env: bunEnv,
});
expect(exitCode).toBe(0);
expect(stdout.toString().trim()).toBe("beforeExit\nexit");
});
it("works with explicit process.exit", () => {
const { exitCode, stdout } = spawnSync({
cmd: [bunExe(), join(import.meta.dir, "process-onBeforeExit-keepAlive.js")],
env: bunEnv,
});
expect(exitCode).toBe(0);
expect(stdout.toString().trim()).toBe("beforeExit: 0\nbeforeExit: 1\nexit: 2");
});
});