mirror of
https://github.com/oven-sh/bun
synced 2026-02-11 03:18:53 +00:00
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:
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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))));
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
16
test/js/node/process/process-exit-fixture.js
Normal file
16
test/js/node/process/process-exit-fixture.js
Normal 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);
|
||||
7
test/js/node/process/process-exitCode-fixture.js
Normal file
7
test/js/node/process/process-exitCode-fixture.js
Normal 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");
|
||||
});
|
||||
8
test/js/node/process/process-exitCode-with-exit.js
Normal file
8
test/js/node/process/process-exitCode-with-exit.js
Normal 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();
|
||||
7
test/js/node/process/process-onBeforeExit-fixture.js
Normal file
7
test/js/node/process/process-onBeforeExit-fixture.js
Normal file
@@ -0,0 +1,7 @@
|
||||
process.on("beforeExit", () => {
|
||||
console.log("beforeExit");
|
||||
});
|
||||
|
||||
process.on("exit", () => {
|
||||
console.log("exit");
|
||||
});
|
||||
18
test/js/node/process/process-onBeforeExit-keepAlive.js
Normal file
18
test/js/node/process/process-onBeforeExit-keepAlive.js
Normal 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);
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user