Compare commits

...

64 Commits

Author SHA1 Message Date
Ciro Spaciari
c226a947c9 dont touch JS in the finalizer 2025-09-11 17:10:54 -07:00
Ciro Spaciari
a6d749276c dont make it an impossible to catch error 2025-09-10 23:18:49 -07:00
Ciro Spaciari
d83103fe49 actually handle the exceptions here 2025-09-10 23:15:39 -07:00
Ciro Spaciari
4400f39b30 dont do weird stuff 2025-09-10 22:47:57 -07:00
Ciro Spaciari
2bd01af7e8 fix js tag 2025-09-10 22:46:25 -07:00
Ciro Spaciari
b616fe1db6 wip 2025-09-10 22:42:14 -07:00
Ciro Spaciari
b4a9f5f0ff no need to use tryGet if we have the callframe this value 2025-09-10 22:15:18 -07:00
Ciro Spaciari
ca80e070de remove .zero from JSref 2025-09-10 22:05:27 -07:00
Ciro Spaciari
2b66c6f4a4 opsie 2025-09-10 22:01:12 -07:00
Ciro Spaciari
a39179a109 remove allowGC 2025-09-10 21:36:11 -07:00
Ciro Spaciari
5ddc772c50 opsie 2025-09-10 21:31:11 -07:00
Ciro Spaciari
324c72c537 more 2025-09-10 21:29:22 -07:00
Ciro Spaciari
019ccc338d queueMicrotask again 2025-09-10 21:18:02 -07:00
Ciro Spaciari
6cc1e75d0f Merge branch 'main' into ciro/fix-onclose 2025-09-10 21:03:50 -07:00
Ciro Spaciari
e5cc5d5b4b fix test-queue-microtask 2025-09-10 20:53:15 -07:00
Ciro Spaciari
bb85cc40a7 allow no asyncContext 2025-09-10 20:08:39 -07:00
Ciro Spaciari
00ce0847d9 opsie 2025-09-10 19:48:16 -07:00
Ciro Spaciari
07631e0742 test 2025-09-10 19:18:36 -07:00
Ciro Spaciari
155f0a8bbb Merge branch 'main' into ciro/fix-onclose 2025-09-10 18:35:13 -07:00
Ciro Spaciari
61bd3e67da Merge branch 'main' into ciro/fix-onclose 2025-09-10 17:56:00 -07:00
Ciro Spaciari
8a64dd86c6 more 2025-09-10 17:04:41 -07:00
Ciro Spaciari
a9a56e6230 Merge branch 'main' into ciro/fix-onclose 2025-09-10 16:21:43 -07:00
Ciro Spaciari
477aa56aa4 Ciro/fix onclose refactor (#22556)
### What does this PR do?

### How did you verify your code works?
2025-09-10 16:20:51 -07:00
Jarred Sumner
1a5660ba39 Merge branch 'main' into ciro/fix-onclose 2025-09-10 00:32:56 -07:00
Ciro Spaciari
8eb6b933b6 revert this 2025-09-09 22:56:10 -07:00
Ciro Spaciari
2cc90a7615 more 2025-09-09 22:53:29 -07:00
Ciro Spaciari
7d7c3daccf more 2025-09-09 22:50:21 -07:00
Ciro Spaciari
e557195dde opsie 2025-09-09 22:22:39 -07:00
Ciro Spaciari
32d5797866 opsie 2025-09-09 22:20:18 -07:00
Ciro Spaciari
bca76bf378 opsie 2025-09-09 21:57:03 -07:00
Ciro Spaciari
75fde6ce45 more 2025-09-09 21:48:55 -07:00
Ciro Spaciari
6d789e2eb7 opsie 2025-09-09 21:11:23 -07:00
Ciro Spaciari
09099b3747 test without microtask 2025-09-09 21:10:40 -07:00
Ciro Spaciari
1c0ae14239 lets go back and test it again 2025-09-09 21:08:38 -07:00
Ciro Spaciari
ec1fc80111 use bool instead of i32 here 2025-09-09 20:24:01 -07:00
Ciro Spaciari
054de551c2 maybe2 2025-09-09 19:42:46 -07:00
Ciro Spaciari
38a798baa8 maybe 2025-09-09 19:40:27 -07:00
Ciro Spaciari
f763180b95 test 2025-09-09 18:28:30 -07:00
Ciro Spaciari
08f3270203 dont timeout when disconnected 2025-09-09 17:38:00 -07:00
Ciro Spaciari
c4996b2d99 more 2025-09-09 17:30:24 -07:00
Ciro Spaciari
fa0febc520 opsie 2025-09-09 17:02:30 -07:00
Ciro Spaciari
f1a52633a3 opsie 2025-09-09 16:56:59 -07:00
Ciro Spaciari
b778121cf1 ok 2025-09-09 16:50:12 -07:00
Ciro Spaciari
3e84a2964d ensureStillAlive 2025-09-09 16:30:54 -07:00
Ciro Spaciari
7753820bd6 delay clos 2025-09-09 16:23:21 -07:00
Ciro Spaciari
b79f0d1e39 tst 2025-09-09 16:00:37 -07:00
Ciro Spaciari
c5fbe102e2 opsie 2025-09-09 15:16:39 -07:00
Ciro Spaciari
7c30edd20e opsie 2025-09-09 15:15:09 -07:00
Ciro Spaciari
60d4f92491 enable more 2025-09-09 15:14:10 -07:00
Ciro Spaciari
3d053712d4 more consistent 2025-09-09 15:06:12 -07:00
Ciro Spaciari
173309ba95 fix test 2025-09-09 15:04:28 -07:00
Ciro Spaciari
b3d46fa99a check that 2025-09-09 15:01:11 -07:00
Ciro Spaciari
edeca4602d dont pass zero 2025-09-09 15:00:02 -07:00
Ciro Spaciari
f424308a31 fix disconnect inside finalie 2025-09-09 13:49:39 -07:00
Ciro Spaciari
07ca61f814 a 2025-09-09 13:23:28 -07:00
Ciro Spaciari
becf7777ef test 2025-09-08 21:49:07 -07:00
Ciro Spaciari
413dd86bf8 test 2025-09-08 21:48:37 -07:00
Ciro Spaciari
7eecefecf0 test 2025-09-08 20:51:17 -07:00
Ciro Spaciari
b809cd297e test 2025-09-08 20:50:40 -07:00
Ciro Spaciari
6f125e0375 toError 2025-09-08 19:43:40 -07:00
Ciro Spaciari
edec93a475 test 2025-09-08 19:25:15 -07:00
Ciro Spaciari
3be879682e wip 2025-09-08 18:28:10 -07:00
Ciro Spaciari
8bf8b8e32f test 2025-09-08 17:29:01 -07:00
Ciro Spaciari
4654a8a886 dont override this let it use binded value 2025-09-08 15:28:35 -07:00
22 changed files with 324 additions and 246 deletions

View File

@@ -627,8 +627,8 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
}
pub fn jsValueAssertAlive(server: *ThisServer) jsc.JSValue {
// With JSRef, we can safely access the JS value even after stop() via weak reference
return server.js_value.get();
bun.assert(server.js_value.isNotEmpty());
return server.js_value.tryGet().?;
}
pub fn requestIP(this: *ThisServer, request: *jsc.WebCore.Request) bun.JSError!jsc.JSValue {
@@ -1124,7 +1124,7 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
this.onReloadFromZig(&new_config, globalThis);
return this.js_value.get();
return this.js_value.tryGet() orelse .js_undefined;
}
pub fn onFetch(this: *ThisServer, ctx: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!jsc.JSValue {
@@ -1426,7 +1426,7 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
pub fn finalize(this: *ThisServer) void {
httplog("finalize", .{});
this.js_value.deinit();
this.js_value.finalize();
this.flags.has_js_deinited = true;
this.deinitIfWeCan();
}
@@ -1539,8 +1539,7 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
}
pub fn stop(this: *ThisServer, abrupt: bool) void {
const current_value = this.js_value.get();
this.js_value.setWeak(current_value);
this.js_value.downgrade();
if (this.config.allow_hot and this.config.id.len > 0) {
if (this.globalThis.bunVM().hotMap()) |hot| {

View File

@@ -1981,7 +1981,7 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool,
this.flags.has_called_error_handler = true;
const result = server.config.onError.call(
server.globalThis,
server.js_value.get(),
server.js_value.tryGet() orelse .js_undefined,
&.{value},
) catch |err| server.globalThis.takeException(err);
defer result.ensureStillAlive();

View File

@@ -9,7 +9,7 @@ for (const type of types) {
construct: true,
finalize: true,
configurable: false,
hasPendingActivity: true,
hasPendingActivity: type === "PostgresSQL",
klass: {
// escapeString: {
// fn: "escapeString",
@@ -60,7 +60,6 @@ for (const type of types) {
construct: true,
finalize: true,
configurable: false,
JSType: "0b11101110",
klass: {},
proto: {

View File

@@ -4,33 +4,28 @@ pub const JSRef = union(enum) {
finalized: void,
pub fn initWeak(value: jsc.JSValue) @This() {
bun.assert(!value.isEmptyOrUndefinedOrNull());
return .{ .weak = value };
}
pub fn initStrong(value: jsc.JSValue, globalThis: *jsc.JSGlobalObject) @This() {
bun.assert(!value.isEmptyOrUndefinedOrNull());
return .{ .strong = .create(value, globalThis) };
}
pub fn empty() @This() {
return .{ .weak = .zero };
return .{ .weak = .js_undefined };
}
pub fn get(this: *@This()) jsc.JSValue {
pub fn tryGet(this: *const @This()) ?jsc.JSValue {
return switch (this.*) {
.weak => this.weak,
.strong => this.strong.get() orelse .zero,
.finalized => .zero,
};
}
pub fn tryGet(this: *@This()) ?jsc.JSValue {
return switch (this.*) {
.weak => if (this.weak != .zero) this.weak else null,
.weak => if (this.weak.isEmptyOrUndefinedOrNull()) null else this.weak,
.strong => this.strong.get(),
.finalized => null,
};
}
pub fn setWeak(this: *@This(), value: jsc.JSValue) void {
bun.assert(!value.isEmptyOrUndefinedOrNull());
switch (this.*) {
.weak => {},
.strong => {
@@ -44,6 +39,7 @@ pub const JSRef = union(enum) {
}
pub fn setStrong(this: *@This(), value: jsc.JSValue, globalThis: *jsc.JSGlobalObject) void {
bun.assert(!value.isEmptyOrUndefinedOrNull());
if (this.* == .strong) {
this.strong.set(globalThis, value);
return;
@@ -54,7 +50,7 @@ pub const JSRef = union(enum) {
pub fn upgrade(this: *@This(), globalThis: *jsc.JSGlobalObject) void {
switch (this.*) {
.weak => {
bun.assert(this.weak != .zero);
bun.assert(!this.weak.isEmptyOrUndefinedOrNull());
this.* = .{ .strong = .create(this.weak, globalThis) };
},
.strong => {},
@@ -64,10 +60,41 @@ pub const JSRef = union(enum) {
}
}
pub fn downgrade(this: *@This()) void {
switch (this.*) {
.weak => {},
.strong => |*strong| {
const value = strong.trySwap() orelse .js_undefined;
value.ensureStillAlive();
strong.deinit();
this.* = .{ .weak = value };
},
.finalized => {
bun.debugAssert(false);
},
}
}
pub fn isEmpty(this: *const @This()) bool {
return switch (this.*) {
.weak => this.weak.isEmptyOrUndefinedOrNull(),
.strong => !this.strong.has(),
.finalized => true,
};
}
pub fn isNotEmpty(this: *const @This()) bool {
return switch (this.*) {
.weak => !this.weak.isEmptyOrUndefinedOrNull(),
.strong => this.strong.has(),
.finalized => false,
};
}
pub fn deinit(this: *@This()) void {
switch (this.*) {
.weak => {
this.weak = .zero;
this.weak = .js_undefined;
},
.strong => {
this.strong.deinit();
@@ -75,6 +102,11 @@ pub const JSRef = union(enum) {
.finalized => {},
}
}
pub fn finalize(this: *@This()) void {
this.deinit();
this.* = .{ .finalized = {} };
}
};
const bun = @import("bun");

View File

@@ -1548,9 +1548,18 @@ JSC_DEFINE_HOST_FUNCTION(functionQueueMicrotask,
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
JSC::JSValue asyncContext = globalObject->m_asyncContextData.get()->getInternalField(0);
auto function = globalObject->performMicrotaskFunction();
#if ASSERT_ENABLED
ASSERT_WITH_MESSAGE(function, "Invalid microtask function");
ASSERT_WITH_MESSAGE(!callback.isEmpty(), "Invalid microtask callback");
#endif
if (asyncContext.isEmpty()) {
asyncContext = JSC::jsUndefined();
}
// This is a JSC builtin function
lexicalGlobalObject->queueMicrotask(globalObject->performMicrotaskFunction(), callback, asyncContext,
lexicalGlobalObject->queueMicrotask(function, callback, asyncContext,
JSC::JSValue {}, JSC::JSValue {});
return JSC::JSValue::encode(JSC::jsUndefined());
@@ -4147,6 +4156,12 @@ extern "C" void JSC__JSGlobalObject__queueMicrotaskCallback(Zig::GlobalObject* g
{
JSFunction* function = globalObject->nativeMicrotaskTrampoline();
#if ASSERT_ENABLED
ASSERT_WITH_MESSAGE(function, "Invalid microtask function");
ASSERT_WITH_MESSAGE(ptr, "Invalid microtask context");
ASSERT_WITH_MESSAGE(callback, "Invalid microtask callback");
#endif
// Do not use JSCell* here because the GC will try to visit it.
globalObject->queueMicrotask(function, JSValue(std::bit_cast<double>(reinterpret_cast<uintptr_t>(ptr))), JSValue(std::bit_cast<double>(reinterpret_cast<uintptr_t>(callback))), jsUndefined(), jsUndefined());
}

View File

@@ -3453,6 +3453,7 @@ void JSC__JSPromise__rejectOnNextTickWithHandled(JSC::JSPromise* promise, JSC::J
JSC::EncodedJSValue encoedValue, bool handled)
{
JSC::JSValue value = JSC::JSValue::decode(encoedValue);
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
uint32_t flags = promise->internalField(JSC::JSPromise::Field::Flags).get().asUInt32();
@@ -3463,10 +3464,28 @@ void JSC__JSPromise__rejectOnNextTickWithHandled(JSC::JSPromise* promise, JSC::J
promise->internalField(JSC::JSPromise::Field::Flags).set(vm, promise, jsNumber(flags | JSC::JSPromise::isFirstResolvingFunctionCalledFlag));
auto* globalObject = jsCast<Zig::GlobalObject*>(promise->globalObject());
auto microtaskFunction = globalObject->performMicrotaskFunction();
auto rejectPromiseFunction = globalObject->rejectPromiseFunction();
auto asyncContext = globalObject->m_asyncContextData.get()->getInternalField(0);
#if ASSERT_ENABLED
ASSERT_WITH_MESSAGE(microtaskFunction, "Invalid microtask function");
ASSERT_WITH_MESSAGE(rejectPromiseFunction, "Invalid microtask callback");
ASSERT_WITH_MESSAGE(!value.isEmpty(), "Invalid microtask value");
#endif
if (asyncContext.isEmpty()) {
asyncContext = jsUndefined();
}
if (value.isEmpty()) {
value = jsUndefined();
}
globalObject->queueMicrotask(
globalObject->performMicrotaskFunction(),
globalObject->rejectPromiseFunction(),
microtaskFunction,
rejectPromiseFunction,
globalObject->m_asyncContextData.get()->getInternalField(0),
promise,
value);
@@ -6121,8 +6140,9 @@ extern "C" void JSC__JSGlobalObject__queueMicrotaskJob(JSC::JSGlobalObject* arg0
if (microtaskArgs[3].isEmpty()) {
microtaskArgs[3] = jsUndefined();
}
auto microTaskFunction = globalObject->performMicrotaskFunction();
#if ASSERT_ENABLED
ASSERT_WITH_MESSAGE(microTaskFunction, "Invalid microtask function");
auto& vm = globalObject->vm();
if (microtaskArgs[0].isCell()) {
JSC::Integrity::auditCellFully(vm, microtaskArgs[0].asCell());
@@ -6139,10 +6159,11 @@ extern "C" void JSC__JSGlobalObject__queueMicrotaskJob(JSC::JSGlobalObject* arg0
if (microtaskArgs[3].isCell()) {
JSC::Integrity::auditCellFully(vm, microtaskArgs[3].asCell());
}
#endif
globalObject->queueMicrotask(
globalObject->performMicrotaskFunction(),
microTaskFunction,
WTFMove(microtaskArgs[0]),
WTFMove(microtaskArgs[1]),
WTFMove(microtaskArgs[2]),

View File

@@ -98,8 +98,8 @@ for (let i = 0; i < nativeStartIndex; i++) {
// TODO: there is no reason this cannot be converted automatically.
// import { ... } from '...' -> `const { ... } = require('...')`
const scannedImports = t.scanImports(input);
for (const imp of scannedImports) {
const scannedImports = t.scan(input);
for (const imp of scannedImports.imports) {
if (imp.kind === "import-statement") {
var isBuiltin = true;
try {
@@ -120,6 +120,14 @@ for (let i = 0; i < nativeStartIndex; i++) {
}
}
if (scannedImports.exports.includes("default") && scannedImports.exports.length > 1) {
const err = new Error(
`Using \`export default\` AND named exports together in builtin modules is unsupported. See src/js/README.md (from ${moduleList[i]})`,
);
err.name = "BunError";
err.fileName = moduleList[i];
throw err;
}
let importStatements: string[] = [];
const processed = sliceSourceCode(

View File

@@ -291,7 +291,7 @@ const SQL: typeof Bun.SQL = function SQL(
reserved_sql.connect = () => {
if (state.connectionState & ReservedConnectionState.closed) {
return Promise.$reject(this.connectionClosedError());
return Promise.$reject(pool.connectionClosedError());
}
return Promise.$resolve(reserved_sql);
};
@@ -322,7 +322,7 @@ const SQL: typeof Bun.SQL = function SQL(
reserved_sql.beginDistributed = (name: string, fn: TransactionCallback) => {
// begin is allowed the difference is that we need to make sure to use the same connection and never release it
if (state.connectionState & ReservedConnectionState.closed) {
return Promise.$reject(this.connectionClosedError());
return Promise.$reject(pool.connectionClosedError());
}
let callback = fn;
@@ -346,7 +346,7 @@ const SQL: typeof Bun.SQL = function SQL(
state.connectionState & ReservedConnectionState.closed ||
!(state.connectionState & ReservedConnectionState.acceptQueries)
) {
return Promise.$reject(this.connectionClosedError());
return Promise.$reject(pool.connectionClosedError());
}
let callback = fn;
let options: string | undefined = options_or_fn as unknown as string;
@@ -369,7 +369,7 @@ const SQL: typeof Bun.SQL = function SQL(
reserved_sql.flush = () => {
if (state.connectionState & ReservedConnectionState.closed) {
throw this.connectionClosedError();
throw pool.connectionClosedError();
}
// Use pooled connection's flush if available, otherwise use adapter's flush
if (pooledConnection.flush) {
@@ -429,7 +429,7 @@ const SQL: typeof Bun.SQL = function SQL(
state.connectionState & ReservedConnectionState.closed ||
!(state.connectionState & ReservedConnectionState.acceptQueries)
) {
return Promise.$reject(this.connectionClosedError());
return Promise.$reject(pool.connectionClosedError());
}
// just release the connection back to the pool
state.connectionState |= ReservedConnectionState.closed;
@@ -552,7 +552,7 @@ const SQL: typeof Bun.SQL = function SQL(
function run_internal_transaction_sql(string) {
if (state.connectionState & ReservedConnectionState.closed) {
return Promise.$reject(this.connectionClosedError());
return Promise.$reject(pool.connectionClosedError());
}
return unsafeQueryFromTransaction(string, [], pooledConnection, state.queries);
}
@@ -564,7 +564,7 @@ const SQL: typeof Bun.SQL = function SQL(
state.connectionState & ReservedConnectionState.closed ||
!(state.connectionState & ReservedConnectionState.acceptQueries)
) {
return Promise.$reject(this.connectionClosedError());
return Promise.$reject(pool.connectionClosedError());
}
if ($isArray(strings)) {
// detect if is tagged template
@@ -593,7 +593,7 @@ const SQL: typeof Bun.SQL = function SQL(
transaction_sql.connect = () => {
if (state.connectionState & ReservedConnectionState.closed) {
return Promise.$reject(this.connectionClosedError());
return Promise.$reject(pool.connectionClosedError());
}
return Promise.$resolve(transaction_sql);
@@ -732,7 +732,7 @@ const SQL: typeof Bun.SQL = function SQL(
state.connectionState & ReservedConnectionState.closed ||
!(state.connectionState & ReservedConnectionState.acceptQueries)
) {
throw this.connectionClosedError();
throw pool.connectionClosedError();
}
if ($isCallable(name)) {
@@ -816,7 +816,7 @@ const SQL: typeof Bun.SQL = function SQL(
sql.reserve = () => {
if (pool.closed) {
return Promise.$reject(this.connectionClosedError());
return Promise.$reject(pool.connectionClosedError());
}
// Check if adapter supports reserved connections
@@ -831,7 +831,7 @@ const SQL: typeof Bun.SQL = function SQL(
};
sql.rollbackDistributed = async function (name: string) {
if (pool.closed) {
throw this.connectionClosedError();
throw pool.connectionClosedError();
}
if (!pool.getRollbackDistributedSQL) {
@@ -844,7 +844,7 @@ const SQL: typeof Bun.SQL = function SQL(
sql.commitDistributed = async function (name: string) {
if (pool.closed) {
throw this.connectionClosedError();
throw pool.connectionClosedError();
}
if (!pool.getCommitDistributedSQL) {
@@ -857,7 +857,7 @@ const SQL: typeof Bun.SQL = function SQL(
sql.beginDistributed = (name: string, fn: TransactionCallback) => {
if (pool.closed) {
return Promise.$reject(this.connectionClosedError());
return Promise.$reject(pool.connectionClosedError());
}
let callback = fn;
@@ -876,7 +876,7 @@ const SQL: typeof Bun.SQL = function SQL(
sql.begin = (options_or_fn: string | TransactionCallback, fn?: TransactionCallback) => {
if (pool.closed) {
return Promise.$reject(this.connectionClosedError());
return Promise.$reject(pool.connectionClosedError());
}
let callback = fn;
let options: string | undefined = options_or_fn as unknown as string;
@@ -896,7 +896,7 @@ const SQL: typeof Bun.SQL = function SQL(
};
sql.connect = () => {
if (pool.closed) {
return Promise.$reject(this.connectionClosedError());
return Promise.$reject(pool.connectionClosedError());
}
if (pool.isConnected()) {

View File

@@ -344,10 +344,13 @@ class PooledMySQLConnection {
/// queryCount is used to indicate the number of queries using the connection, if a connection is reserved or if its a transaction queryCount will be 1 independently of the number of queries
queryCount: number = 0;
#onConnected(err, _) {
#onConnected(err, connection) {
if (err) {
err = wrapError(err);
} else {
this.connection = connection;
}
const connectionInfo = this.connectionInfo;
if (connectionInfo?.onconnect) {
connectionInfo.onconnect(err);
@@ -413,12 +416,8 @@ class PooledMySQLConnection {
this.#startConnection();
}
async #startConnection() {
this.connection = await PooledMySQLConnection.createConnection(
this.connectionInfo,
this.#onConnected.bind(this),
this.#onClose.bind(this),
);
#startConnection() {
PooledMySQLConnection.createConnection(this.connectionInfo, this.#onConnected.bind(this), this.#onClose.bind(this));
}
onClose(onClose: (err: Error) => void) {
@@ -482,7 +481,7 @@ class PooledMySQLConnection {
}
}
export class MySQLAdapter
class MySQLAdapter
implements
DatabaseAdapter<PooledMySQLConnection, $ZigGeneratedClasses.MySQLConnection, $ZigGeneratedClasses.MySQLQuery>
{

View File

@@ -499,7 +499,7 @@ class PooledPostgresConnection {
}
}
export class PostgresAdapter
class PostgresAdapter
implements
DatabaseAdapter<
PooledPostgresConnection,

View File

@@ -293,7 +293,7 @@ function parseSQLQuery(query: string): SQLParsedInfo {
return { command, firstKeyword, hasReturning };
}
export class SQLiteQueryHandle implements BaseQueryHandle<BunSQLiteModule.Database> {
class SQLiteQueryHandle implements BaseQueryHandle<BunSQLiteModule.Database> {
private mode = SQLQueryResultMode.objects;
private readonly sql: string;
@@ -380,9 +380,7 @@ export class SQLiteQueryHandle implements BaseQueryHandle<BunSQLiteModule.Databa
}
}
export class SQLiteAdapter
implements DatabaseAdapter<BunSQLiteModule.Database, BunSQLiteModule.Database, SQLiteQueryHandle>
{
class SQLiteAdapter implements DatabaseAdapter<BunSQLiteModule.Database, BunSQLiteModule.Database, SQLiteQueryHandle> {
public readonly connectionInfo: Bun.SQL.__internal.DefinedSQLiteOptions;
public db: BunSQLiteModule.Database | null = null;
public storedError: Error | null = null;
@@ -807,4 +805,5 @@ export default {
SQLCommand,
commandToString,
parseSQLQuery,
SQLiteQueryHandle,
};

View File

@@ -21,8 +21,7 @@ poll_ref: bun.Async.KeepAlive = .{},
globalObject: *jsc.JSGlobalObject,
vm: *jsc.VirtualMachine,
pending_activity_count: std.atomic.Value(u32) = std.atomic.Value(u32).init(0),
js_value: JSValue = .js_undefined,
js_value: jsc.JSRef = jsc.JSRef.empty(),
server_version: bun.ByteList = .{},
connection_id: u32 = 0,
@@ -122,14 +121,14 @@ pub const AuthState = union(enum) {
};
};
pub fn hasPendingActivity(this: *MySQLConnection) bool {
return this.pending_activity_count.load(.acquire) > 0;
}
fn updateHasPendingActivity(this: *MySQLConnection) void {
const a: u32 = if (this.requests.readableLength() > 0) 1 else 0;
const b: u32 = if (this.status != .disconnected) 1 else 0;
this.pending_activity_count.store(a + b, .release);
fn updateReferenceType(this: *MySQLConnection) void {
if (this.js_value.isNotEmpty()) {
if (this.requests.readableLength() > 0 or (this.status != .disconnected and this.status != .failed)) {
this.js_value.upgrade(this.globalObject);
return;
}
this.js_value.downgrade();
}
}
fn hasDataToSend(this: *@This()) bool {
@@ -225,15 +224,16 @@ pub fn onConnectionTimeout(this: *@This()) bun.api.Timer.EventLoopTimer.Arm {
.connected => {
this.failFmt(error.IdleTimeout, "Idle timeout reached after {}", .{bun.fmt.fmtDurationOneDecimal(@as(u64, this.idle_timeout_interval_ms) *| std.time.ns_per_ms)});
},
else => {
.connecting => {
this.failFmt(error.ConnectionTimedOut, "Connection timeout after {}", .{bun.fmt.fmtDurationOneDecimal(@as(u64, this.connection_timeout_ms) *| std.time.ns_per_ms)});
},
.handshaking,
.authenticating,
.authentication_awaiting_pk,
=> {
this.failFmt(error.ConnectionTimedOut, "Connection timed out after {} (during authentication)", .{bun.fmt.fmtDurationOneDecimal(@as(u64, this.connection_timeout_ms) *| std.time.ns_per_ms)});
this.failFmt(error.ConnectionTimedOut, "Connection timeout after {} (during authentication)", .{bun.fmt.fmtDurationOneDecimal(@as(u64, this.connection_timeout_ms) *| std.time.ns_per_ms)});
},
.disconnected, .failed => {},
}
return .disarm;
}
@@ -264,28 +264,24 @@ fn drainInternal(this: *@This()) void {
}
}
}
pub fn finalize(this: *MySQLConnection) void {
this.stopTimers();
debug("MySQLConnection finalize", .{});
// Ensure we disconnect before finalizing
if (this.status != .disconnected) {
this.disconnect();
}
this.js_value = .zero;
this.js_value.finalize();
this.deref();
}
pub fn doRef(this: *@This(), _: *jsc.JSGlobalObject, _: *jsc.CallFrame) bun.JSError!JSValue {
this.poll_ref.ref(this.vm);
this.updateHasPendingActivity();
this.updateReferenceType();
return .js_undefined;
}
pub fn doUnref(this: *@This(), _: *jsc.JSGlobalObject, _: *jsc.CallFrame) bun.JSError!JSValue {
this.poll_ref.unref(this.vm);
this.updateHasPendingActivity();
this.updateReferenceType();
return .js_undefined;
}
@@ -352,7 +348,10 @@ pub fn stopTimers(this: *@This()) void {
}
pub fn getQueriesArray(this: *const @This()) JSValue {
return js.queriesGetCached(this.js_value) orelse .js_undefined;
if (this.js_value.tryGet()) |value| {
return js.queriesGetCached(value) orelse .js_undefined;
}
return .js_undefined;
}
pub fn failFmt(this: *@This(), error_code: AnyMySQLError.Error, comptime fmt: [:0]const u8, args: anytype) void {
const message = bun.handleOom(std.fmt.allocPrint(bun.default_allocator, fmt, args));
@@ -362,28 +361,35 @@ pub fn failFmt(this: *@This(), error_code: AnyMySQLError.Error, comptime fmt: [:
this.failWithJSValue(err);
}
pub fn failWithJSValue(this: *MySQLConnection, value: JSValue) void {
defer this.updateHasPendingActivity();
defer this.updateReferenceType();
this.stopTimers();
if (this.status == .failed) return;
this.setStatus(.failed);
this.ref();
defer this.deref();
// we defer the refAndClose so the on_close will be called first before we reject the pending requests
defer this.refAndClose(value);
const on_close = this.consumeOnCloseCallback(this.globalObject) orelse return;
defer {
// we defer the refAndClose so the on_close will be called first before we reject the pending requests
this.refAndClose(value);
this.deref();
}
this.status = .failed;
if (this.vm.isShuttingDown()) return;
const on_close = this.consumeOnCloseCallback(this.globalObject) orelse return;
on_close.ensureStillAlive();
const loop = this.vm.eventLoop();
loop.enter();
defer loop.exit();
_ = on_close.call(
this.globalObject,
this.js_value,
&[_]JSValue{
value,
this.getQueriesArray(),
},
) catch |e| this.globalObject.reportActiveExceptionAsUnhandled(e);
var js_error = value.toError() orelse value;
if (js_error == .zero) {
js_error = AnyMySQLError.mysqlErrorToJS(this.globalObject, "Connection closed", error.ConnectionClosed);
}
js_error.ensureStillAlive();
const queries_array = this.getQueriesArray();
queries_array.ensureStillAlive();
this.globalObject.queueMicrotask(on_close, &[_]JSValue{ js_error, queries_array });
}
pub fn fail(this: *MySQLConnection, message: []const u8, err: AnyMySQLError.Error) void {
@@ -392,30 +398,36 @@ pub fn fail(this: *MySQLConnection, message: []const u8, err: AnyMySQLError.Erro
this.failWithJSValue(instance);
}
pub fn onClose(this: *MySQLConnection) void {
var vm = this.vm;
defer vm.drainMicrotasks();
pub fn onEnd(this: *MySQLConnection) void {
// no more socket
this.fail("Connection closed", error.ConnectionClosed);
}
pub fn onClose(this: *MySQLConnection) void {
// no more socket
defer this.deref();
this.onEnd();
}
fn refAndClose(this: *@This(), js_reason: ?jsc.JSValue) void {
// refAndClose is always called when we wanna to disconnect or when we are closed
// cleanup requests
this.cleanUpRequests(js_reason);
if (!this.socket.isClosed()) {
// event loop need to be alive to close the socket
this.poll_ref.ref(this.vm);
// will unref on socket close
this.socket.close();
}
// cleanup requests
this.cleanUpRequests(js_reason);
}
pub fn disconnect(this: *@This()) void {
this.stopTimers();
if (this.status == .connected) {
this.setStatus(.disconnected);
defer this.updateReferenceType();
this.status = .disconnected;
this.poll_ref.disable();
const requests = this.requests.readableSlice(0);
@@ -721,12 +733,12 @@ fn SocketHandler(comptime ssl: bool) type {
pub fn onEnd(this: *MySQLConnection, socket: SocketType) void {
_ = socket;
this.onClose();
this.onEnd();
}
pub fn onConnectError(this: *MySQLConnection, socket: SocketType, _: i32) void {
_ = socket;
this.onClose();
this.onEnd();
}
pub fn onTimeout(this: *MySQLConnection, socket: SocketType) void {
@@ -747,7 +759,7 @@ fn SocketHandler(comptime ssl: bool) type {
}
pub fn onTimeout(this: *MySQLConnection) void {
this.fail("Connection timed out", error.ConnectionTimedOut);
this.fail("Connection timeout", error.ConnectionTimedOut);
}
pub fn onDrain(this: *MySQLConnection) void {
@@ -938,12 +950,11 @@ pub fn call(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JS
}
}
ptr.setStatus(.connecting);
ptr.updateHasPendingActivity();
ptr.resetConnectionTimeout();
ptr.poll_ref.ref(vm);
const js_value = ptr.toJS(globalObject);
js_value.ensureStillAlive();
ptr.js_value = js_value;
ptr.js_value.setStrong(js_value, globalObject);
js.onconnectSetCached(js_value, globalObject, on_connect);
js.oncloseSetCached(js_value, globalObject, on_close);
@@ -951,7 +962,6 @@ pub fn call(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JS
}
pub fn deinit(this: *MySQLConnection) void {
this.disconnect();
this.stopTimers();
debug("MySQLConnection deinit", .{});
@@ -962,10 +972,7 @@ pub fn deinit(this: *MySQLConnection) void {
// Clear any pending requests first
for (requests.readableSlice(0)) |request| {
this.finishRequest(request);
request.onError(.{
.error_code = 2013,
.error_message = .{ .temporary = "Connection closed" },
}, this.globalObject);
request.deref();
}
this.write_buffer.deinit(bun.default_allocator);
this.read_buffer.deinit(bun.default_allocator);
@@ -1002,10 +1009,11 @@ pub fn onOpen(this: *MySQLConnection, socket: Socket) void {
this.socket = socket;
if (socket == .SocketTCP) {
// when upgrading to TLS the onOpen callback will be called again and at this moment we dont wanna to change the status to handshaking
this.setStatus(.handshaking);
this.status = .handshaking;
this.ref(); // keep a ref for the socket
}
this.poll_ref.ref(this.vm);
this.updateHasPendingActivity();
this.updateReferenceType();
}
pub fn onHandshake(this: *MySQLConnection, success: i32, ssl_error: uws.us_bun_verify_error_t) void {
@@ -1143,11 +1151,12 @@ pub fn processPackets(this: *MySQLConnection, comptime Context: type, reader: Ne
// Read packet header
const header = PacketHeader.decode(reader.peek()) orelse return AnyMySQLError.Error.ShortRead;
const header_length = header.length;
const packet_length: usize = header_length + PacketHeader.size;
debug("sequence_id: {d} header: {d}", .{ this.sequence_id, header_length });
// Ensure we have the full packet
reader.ensureCapacity(header_length + PacketHeader.size) catch return AnyMySQLError.Error.ShortRead;
reader.ensureCapacity(packet_length) catch return AnyMySQLError.Error.ShortRead;
// always skip the full packet, we dont care about padding or unreaded bytes
defer reader.setOffsetFromStart(header_length + PacketHeader.size);
defer reader.setOffsetFromStart(packet_length);
reader.skip(PacketHeader.size);
// Update sequence id
@@ -1270,24 +1279,35 @@ fn handleHandshakeDecodePublicKey(this: *MySQLConnection, comptime Context: type
pub fn consumeOnConnectCallback(this: *const @This(), globalObject: *jsc.JSGlobalObject) ?jsc.JSValue {
debug("consumeOnConnectCallback", .{});
const on_connect = js.onconnectGetCached(this.js_value) orelse return null;
debug("consumeOnConnectCallback exists", .{});
js.onconnectSetCached(this.js_value, globalObject, .zero);
return on_connect;
if (this.js_value.tryGet()) |value| {
const on_connect = js.onconnectGetCached(value) orelse return null;
debug("consumeOnConnectCallback exists", .{});
js.onconnectSetCached(value, globalObject, .zero);
if (on_connect == .zero) {
return null;
}
return on_connect;
}
return null;
}
pub fn consumeOnCloseCallback(this: *const @This(), globalObject: *jsc.JSGlobalObject) ?jsc.JSValue {
debug("consumeOnCloseCallback", .{});
const on_close = js.oncloseGetCached(this.js_value) orelse return null;
debug("consumeOnCloseCallback exists", .{});
js.oncloseSetCached(this.js_value, globalObject, .zero);
return on_close;
if (this.js_value.tryGet()) |value| {
const on_close = js.oncloseGetCached(value) orelse return null;
debug("consumeOnCloseCallback exists", .{});
js.oncloseSetCached(value, globalObject, .zero);
if (on_close == .zero) {
return null;
}
return on_close;
}
return null;
}
pub fn setStatus(this: *@This(), status: ConnectionState) void {
if (this.status == status) return;
defer this.updateHasPendingActivity();
defer this.updateReferenceType();
this.status = status;
this.resetConnectionTimeout();
@@ -1296,7 +1316,8 @@ pub fn setStatus(this: *@This(), status: ConnectionState) void {
switch (status) {
.connected => {
const on_connect = this.consumeOnConnectCallback(this.globalObject) orelse return;
const js_value = this.js_value;
on_connect.ensureStillAlive();
var js_value = this.js_value.tryGet() orelse .js_undefined;
js_value.ensureStillAlive();
this.globalObject.queueMicrotask(on_connect, &[_]JSValue{ JSValue.jsNull(), js_value });
this.poll_ref.unref(this.vm);
@@ -1306,8 +1327,8 @@ pub fn setStatus(this: *@This(), status: ConnectionState) void {
}
pub fn updateRef(this: *@This()) void {
this.updateHasPendingActivity();
if (this.pending_activity_count.raw > 0) {
this.updateReferenceType();
if (this.js_value == .strong) {
this.poll_ref.ref(this.vm);
} else {
this.poll_ref.unref(this.vm);
@@ -1765,7 +1786,7 @@ fn handleResultSetOK(this: *MySQLConnection, request: *MySQLQuery, statement: *M
request.onResult(
statement.result_count,
this.globalObject,
this.js_value,
this.getQueriesArray(),
this.flags.is_ready_for_query,
last_insert_id,
affected_rows,
@@ -1874,7 +1895,7 @@ pub fn handleResultSet(this: *MySQLConnection, comptime Context: type, reader: N
var cached_structure: ?CachedStructure = null;
switch (request.flags.result_mode) {
.objects => {
cached_structure = statement.structure(this.js_value, this.globalObject);
cached_structure = if (this.js_value.tryGet()) |value| statement.structure(value, this.globalObject) else null;
structure = cached_structure.?.jsValue() orelse .js_undefined;
},
.raw, .values => {
@@ -1884,7 +1905,7 @@ pub fn handleResultSet(this: *MySQLConnection, comptime Context: type, reader: N
defer row.deinit(allocator);
try row.decode(allocator, reader);
const pending_value = MySQLQuery.js.pendingValueGetCached(request.thisValue.get()) orelse .zero;
const pending_value = (if (request.thisValue.tryGet()) |value| MySQLQuery.js.pendingValueGetCached(value) else .js_undefined) orelse .js_undefined;
// Process row data
const row_value = row.toJS(
@@ -1902,8 +1923,10 @@ pub fn handleResultSet(this: *MySQLConnection, comptime Context: type, reader: N
}
statement.result_count += 1;
if (pending_value == .zero) {
MySQLQuery.js.pendingValueSetCached(request.thisValue.get(), this.globalObject, row_value);
if (pending_value.isEmptyOrUndefinedOrNull()) {
if (request.thisValue.tryGet()) |value| {
MySQLQuery.js.pendingValueSetCached(value, this.globalObject, row_value);
}
}
}
},

View File

@@ -1,5 +1,5 @@
const MySQLQuery = @This();
const RefCount = bun.ptr.ThreadSafeRefCount(@This(), "ref_count", deinit, .{});
const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{});
statement: ?*MySQLStatement = null,
query: bun.String = bun.String.empty,
@@ -42,12 +42,7 @@ pub const Status = enum(u8) {
}
};
pub fn hasPendingActivity(this: *@This()) bool {
return this.ref_count.load(.monotonic) > 1;
}
pub fn deinit(this: *@This()) void {
this.thisValue.deinit();
if (this.statement) |statement| {
statement.deref();
}
@@ -66,11 +61,7 @@ pub fn finalize(this: *@This()) void {
this.statement = null;
}
if (this.thisValue == .weak) {
// clean up if is a weak reference, if is a strong reference we need to wait until the query is done
// if we are a strong reference, here is probably a bug because GC'd should not happen
this.thisValue.weak = .zero;
}
this.thisValue.finalize();
this.deref();
}
@@ -81,24 +72,24 @@ pub fn onWriteFail(
queries_array: JSValue,
) void {
this.status = .fail;
const thisValue = this.thisValue.get();
defer this.thisValue.deinit();
const targetValue = this.getTarget(globalObject, true);
if (thisValue == .zero or targetValue == .zero) {
return;
}
const thisValue = this.thisValue.tryGet() orelse return;
defer this.thisValue.downgrade();
const targetValue = this.getTarget(globalObject, true) orelse return;
const instance = AnyMySQLError.mysqlErrorToJS(globalObject, "Failed to bind query", err);
const js_err = instance.toError() orelse instance;
bun.assertf(js_err != .zero, "js_err is zero", .{});
js_err.ensureStillAlive();
const vm = jsc.VirtualMachine.get();
const function = vm.rareData().mysql_context.onQueryRejectFn.get().?;
const function = vm.rareData().mysql_context.onQueryRejectFn.get() orelse return;
bun.assertf(function.isCallable(), "onQueryRejectFn is not callable", .{});
const event_loop = vm.eventLoop();
const js_array = if (queries_array == .zero) .js_undefined else queries_array;
js_array.ensureStillAlive();
event_loop.runCallback(function, globalObject, thisValue, &.{
targetValue,
// TODO: add mysql error to JS
// postgresErrorToJS(globalObject, null, err),
instance,
queries_array,
js_err,
js_array,
});
}
@@ -124,9 +115,9 @@ pub fn bindAndExecute(this: *MySQLQuery, writer: anytype, statement: *MySQLState
}
fn bind(this: *MySQLQuery, execute: *PreparedStatement.Execute, globalObject: *jsc.JSGlobalObject) AnyMySQLError.Error!void {
const thisValue = this.thisValue.get();
const binding_value = js.bindingGetCached(thisValue) orelse .zero;
const columns_value = js.columnsGetCached(thisValue) orelse .zero;
const thisValue = this.thisValue.tryGet() orelse return error.InvalidState;
const binding_value = js.bindingGetCached(thisValue) orelse .js_undefined;
const columns_value = js.columnsGetCached(thisValue) orelse .js_undefined;
var iter = try QueryBindingIterator.init(binding_value, columns_value, globalObject);
@@ -167,24 +158,27 @@ pub fn onJSError(this: *@This(), err: jsc.JSValue, globalObject: *jsc.JSGlobalOb
this.ref();
defer this.deref();
this.status = .fail;
const thisValue = this.thisValue.get();
defer this.thisValue.deinit();
const targetValue = this.getTarget(globalObject, true);
if (thisValue == .zero or targetValue == .zero) {
return;
}
const thisValue = this.thisValue.tryGet() orelse return;
defer this.thisValue.downgrade();
const targetValue = this.getTarget(globalObject, true) orelse return;
var vm = jsc.VirtualMachine.get();
const function = vm.rareData().mysql_context.onQueryRejectFn.get().?;
const function = vm.rareData().mysql_context.onQueryRejectFn.get() orelse return;
bun.assertf(function.isCallable(), "onQueryRejectFn is not callable", .{});
const event_loop = vm.eventLoop();
var js_error = err.toError() orelse err;
if (js_error == .zero) {
js_error = AnyMySQLError.mysqlErrorToJS(globalObject, "Query failed", error.UnknownError);
}
js_error.ensureStillAlive();
event_loop.runCallback(function, globalObject, thisValue, &.{
targetValue,
err,
js_error,
});
}
pub fn getTarget(this: *@This(), globalObject: *jsc.JSGlobalObject, clean_target: bool) jsc.JSValue {
const thisValue = this.thisValue.tryGet() orelse return .zero;
const target = js.targetGetCached(thisValue) orelse return .zero;
pub fn getTarget(this: *@This(), globalObject: *jsc.JSGlobalObject, clean_target: bool) ?jsc.JSValue {
const thisValue = this.thisValue.tryGet() orelse return null;
const target = js.targetGetCached(thisValue) orelse return null;
if (clean_target) {
js.targetSetCached(thisValue, globalObject, .zero);
}
@@ -197,17 +191,6 @@ fn consumePendingValue(thisValue: jsc.JSValue, globalObject: *jsc.JSGlobalObject
return pending_value;
}
pub fn allowGC(thisValue: jsc.JSValue, globalObject: *jsc.JSGlobalObject) void {
if (thisValue == .zero) {
return;
}
defer thisValue.ensureStillAlive();
js.bindingSetCached(thisValue, globalObject, .zero);
js.pendingValueSetCached(thisValue, globalObject, .zero);
js.targetSetCached(thisValue, globalObject, .zero);
}
fn u64ToJSValue(value: u64) JSValue {
if (value <= jsc.MAX_SAFE_INTEGER) {
return JSValue.jsNumber(value);
@@ -215,36 +198,39 @@ fn u64ToJSValue(value: u64) JSValue {
return JSValue.jsBigInt(value);
}
pub fn onResult(this: *@This(), result_count: u64, globalObject: *jsc.JSGlobalObject, connection: jsc.JSValue, is_last: bool, last_insert_id: u64, affected_rows: u64) void {
pub fn onResult(this: *@This(), result_count: u64, globalObject: *jsc.JSGlobalObject, queries_array: jsc.JSValue, is_last: bool, last_insert_id: u64, affected_rows: u64) void {
this.ref();
defer this.deref();
const thisValue = this.thisValue.get();
const targetValue = this.getTarget(globalObject, is_last);
if (is_last) {
this.status = .success;
} else {
this.status = .partial_response;
}
const tag: CommandTag = .{ .SELECT = result_count };
const js_tag = tag.toJSTag(globalObject) catch |e| return this.onJSError(globalObject.takeException(e), globalObject);
js_tag.ensureStillAlive();
const thisValue = this.thisValue.tryGet() orelse return;
defer if (is_last) {
allowGC(thisValue, globalObject);
this.thisValue.deinit();
this.thisValue.downgrade();
};
if (thisValue == .zero or targetValue == .zero) {
return;
}
const targetValue = this.getTarget(globalObject, is_last) orelse return;
const vm = jsc.VirtualMachine.get();
const function = vm.rareData().mysql_context.onQueryResolveFn.get().?;
const function = vm.rareData().mysql_context.onQueryResolveFn.get() orelse return;
bun.assertf(function.isCallable(), "onQueryResolveFn is not callable", .{});
const event_loop = vm.eventLoop();
const tag: CommandTag = .{ .SELECT = result_count };
event_loop.runCallback(function, globalObject, thisValue, &.{
targetValue,
consumePendingValue(thisValue, globalObject) orelse .js_undefined,
tag.toJSTag(globalObject),
js_tag,
tag.toJSNumber(),
if (connection == .zero) .js_undefined else MySQLConnection.js.queriesGetCached(connection) orelse .js_undefined,
queries_array,
JSValue.jsBoolean(is_last),
JSValue.jsNumber(last_insert_id),
JSValue.jsNumber(affected_rows),
@@ -324,9 +310,9 @@ pub fn call(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSEr
return this_value;
}
pub fn setPendingValue(this: *@This(), globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!JSValue {
pub fn setPendingValue(_: *@This(), globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!JSValue {
const result = callframe.argument(0);
const thisValue = this.thisValue.tryGet() orelse return .js_undefined;
const thisValue = callframe.this();
js.pendingValueSetCached(thisValue, globalObject, result);
return .js_undefined;
}
@@ -364,7 +350,7 @@ pub fn doRun(this: *MySQLQuery, globalObject: *jsc.JSGlobalObject, callframe: *j
return globalObject.throw("connection must be a MySQLConnection", .{});
};
connection.poll_ref.ref(globalObject.bunVM());
defer connection.updateRef();
var query = arguments[1];
if (!query.isObject()) {

View File

@@ -33,6 +33,8 @@ pub const Error = error{
InvalidErrorPacket,
UnexpectedPacket,
ShortRead,
UnknownError,
InvalidState,
};
pub fn mysqlErrorToJS(globalObject: *jsc.JSGlobalObject, message: ?[]const u8, err: Error) JSValue {
@@ -64,6 +66,8 @@ pub fn mysqlErrorToJS(globalObject: *jsc.JSGlobalObject, message: ?[]const u8, e
error.MissingAuthData => "ERR_MYSQL_MISSING_AUTH_DATA",
error.FailedToEncryptPassword => "ERR_MYSQL_FAILED_TO_ENCRYPT_PASSWORD",
error.InvalidPublicKey => "ERR_MYSQL_INVALID_PUBLIC_KEY",
error.UnknownError => "ERR_MYSQL_UNKNOWN_ERROR",
error.InvalidState => "ERR_MYSQL_INVALID_STATE",
error.JSError => {
return globalObject.takeException(error.JSError);
},

View File

@@ -19,7 +19,7 @@ pub fn createMySQLError(
message: []const u8,
options: MySQLErrorOptions,
) bun.JSError!JSValue {
const opts_obj = JSValue.createEmptyObject(globalObject, 18);
const opts_obj = JSValue.createEmptyObject(globalObject, 0);
opts_obj.ensureStillAlive();
opts_obj.put(globalObject, JSC.ZigString.static("code"), try bun.String.createUTF8ForJS(globalObject, options.code));
if (options.errno) |errno| {

View File

@@ -29,7 +29,7 @@ pub const CommandTag = union(enum) {
other: []const u8,
pub fn toJSTag(this: CommandTag, globalObject: *jsc.JSGlobalObject) JSValue {
pub fn toJSTag(this: CommandTag, globalObject: *jsc.JSGlobalObject) bun.JSError!jsc.JSValue {
return switch (this) {
.INSERT => JSValue.jsNumber(1),
.DELETE => JSValue.jsNumber(2),
@@ -39,7 +39,7 @@ pub const CommandTag = union(enum) {
.MOVE => JSValue.jsNumber(6),
.FETCH => JSValue.jsNumber(7),
.COPY => JSValue.jsNumber(8),
.other => |tag| jsc.ZigString.init(tag).toJS(globalObject),
.other => |tag| bun.String.createUTF8ForJS(globalObject, tag),
};
}

View File

@@ -219,7 +219,7 @@ pub fn onConnectionTimeout(this: *PostgresSQLConnection) bun.api.Timer.EventLoop
this.failFmt("ERR_POSTGRES_CONNECTION_TIMEOUT", "Connection timeout after {}", .{bun.fmt.fmtDurationOneDecimal(@as(u64, this.connection_timeout_ms) *| std.time.ns_per_ms)});
},
.sent_startup_message => {
this.failFmt("ERR_POSTGRES_CONNECTION_TIMEOUT", "Connection timed out after {} (sent startup message, but never received response)", .{bun.fmt.fmtDurationOneDecimal(@as(u64, this.connection_timeout_ms) *| std.time.ns_per_ms)});
this.failFmt("ERR_POSTGRES_CONNECTION_TIMEOUT", "Connection timeout after {} (sent startup message, but never received response)", .{bun.fmt.fmtDurationOneDecimal(@as(u64, this.connection_timeout_ms) *| std.time.ns_per_ms)});
},
}
return .disarm;
@@ -311,7 +311,7 @@ pub fn failWithJSValue(this: *PostgresSQLConnection, value: JSValue) void {
this.stopTimers();
if (this.status == .failed) return;
this.setStatus(.failed);
this.status = .failed;
this.ref();
defer this.deref();
@@ -321,12 +321,17 @@ pub fn failWithJSValue(this: *PostgresSQLConnection, value: JSValue) void {
const loop = this.vm.eventLoop();
loop.enter();
var js_error = value.toError() orelse value;
if (js_error == .zero) {
js_error = postgresErrorToJS(this.globalObject, "Connection closed", error.ConnectionClosed);
}
js_error.ensureStillAlive();
defer loop.exit();
_ = on_close.call(
this.globalObject,
this.js_value,
.js_undefined,
&[_]JSValue{
value.toError() orelse value,
js_error,
this.getQueriesArray(),
},
) catch |e| this.globalObject.reportActiveExceptionAsUnhandled(e);
@@ -1350,6 +1355,9 @@ fn advance(this: *PostgresSQLConnection) void {
}
pub fn getQueriesArray(this: *const PostgresSQLConnection) JSValue {
if (this.js_value.isEmptyOrUndefinedOrNull()) {
return .js_undefined;
}
return js.queriesGetCached(this.js_value) orelse .js_undefined;
}

View File

@@ -1,5 +1,5 @@
const PostgresSQLQuery = @This();
const RefCount = bun.ptr.ThreadSafeRefCount(@This(), "ref_count", deinit, .{});
const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{});
statement: ?*PostgresSQLStatement = null,
query: bun.String = bun.String.empty,
cursor_name: bun.String = bun.String.empty,
@@ -23,9 +23,9 @@ flags: packed struct(u8) {
pub const ref = RefCount.ref;
pub const deref = RefCount.deref;
pub fn getTarget(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, clean_target: bool) jsc.JSValue {
const thisValue = this.thisValue.tryGet() orelse return .zero;
const target = js.targetGetCached(thisValue) orelse return .zero;
pub fn getTarget(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, clean_target: bool) ?jsc.JSValue {
const thisValue = this.thisValue.tryGet() orelse return null;
const target = js.targetGetCached(thisValue) orelse return null;
if (clean_target) {
js.targetSetCached(thisValue, globalObject, .zero);
}
@@ -51,12 +51,7 @@ pub const Status = enum(u8) {
}
};
pub fn hasPendingActivity(this: *@This()) bool {
return this.ref_count.get() > 1;
}
pub fn deinit(this: *@This()) void {
this.thisValue.deinit();
if (this.statement) |statement| {
statement.deref();
}
@@ -67,11 +62,7 @@ pub fn deinit(this: *@This()) void {
pub fn finalize(this: *@This()) void {
debug("PostgresSQLQuery finalize", .{});
if (this.thisValue == .weak) {
// clean up if is a weak reference, if is a strong reference we need to wait until the query is done
// if we are a strong reference, here is probably a bug because GC'd should not happen
this.thisValue.weak = .zero;
}
this.thisValue.finalize();
this.deref();
}
@@ -84,12 +75,9 @@ pub fn onWriteFail(
this.ref();
defer this.deref();
this.status = .fail;
const thisValue = this.thisValue.get();
defer this.thisValue.deinit();
const targetValue = this.getTarget(globalObject, true);
if (thisValue == .zero or targetValue == .zero) {
return;
}
const thisValue = this.thisValue.tryGet() orelse return;
defer this.thisValue.downgrade();
const targetValue = this.getTarget(globalObject, true) orelse return;
const vm = jsc.VirtualMachine.get();
const function = vm.rareData().postgresql_context.onQueryRejectFn.get().?;
@@ -105,12 +93,9 @@ pub fn onJSError(this: *@This(), err: jsc.JSValue, globalObject: *jsc.JSGlobalOb
this.ref();
defer this.deref();
this.status = .fail;
const thisValue = this.thisValue.get();
defer this.thisValue.deinit();
const targetValue = this.getTarget(globalObject, true);
if (thisValue == .zero or targetValue == .zero) {
return;
}
const thisValue = this.thisValue.tryGet() orelse return;
defer this.thisValue.downgrade();
const targetValue = this.getTarget(globalObject, true) orelse return;
var vm = jsc.VirtualMachine.get();
const function = vm.rareData().postgresql_context.onQueryRejectFn.get().?;
@@ -145,31 +130,30 @@ fn consumePendingValue(thisValue: jsc.JSValue, globalObject: *jsc.JSGlobalObject
pub fn onResult(this: *@This(), command_tag_str: []const u8, globalObject: *jsc.JSGlobalObject, connection: jsc.JSValue, is_last: bool) void {
this.ref();
defer this.deref();
const thisValue = this.thisValue.get();
const targetValue = this.getTarget(globalObject, is_last);
if (is_last) {
this.status = .success;
} else {
this.status = .partial_response;
}
const tag = CommandTag.init(command_tag_str);
const js_tag = tag.toJSTag(globalObject) catch |e| return this.onJSError(globalObject.takeException(e), globalObject);
js_tag.ensureStillAlive();
const thisValue = this.thisValue.tryGet() orelse return;
defer if (is_last) {
allowGC(thisValue, globalObject);
this.thisValue.deinit();
this.thisValue.downgrade();
};
if (thisValue == .zero or targetValue == .zero) {
return;
}
const targetValue = this.getTarget(globalObject, is_last) orelse return;
const vm = jsc.VirtualMachine.get();
const function = vm.rareData().postgresql_context.onQueryResolveFn.get().?;
const event_loop = vm.eventLoop();
const tag = CommandTag.init(command_tag_str);
event_loop.runCallback(function, globalObject, thisValue, &.{
targetValue,
consumePendingValue(thisValue, globalObject) orelse .js_undefined,
tag.toJSTag(globalObject),
js_tag,
tag.toJSNumber(),
if (connection == .zero) .js_undefined else PostgresSQLConnection.js.queriesGetCached(connection) orelse .js_undefined,
JSValue.jsBoolean(is_last),
@@ -257,9 +241,9 @@ pub fn doDone(this: *@This(), globalObject: *jsc.JSGlobalObject, _: *jsc.CallFra
this.flags.is_done = true;
return .js_undefined;
}
pub fn setPendingValue(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!JSValue {
pub fn setPendingValue(_: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!JSValue {
const result = callframe.argument(0);
const thisValue = this.thisValue.tryGet() orelse return .js_undefined;
const thisValue = callframe.this();
js.pendingValueSetCached(thisValue, globalObject, result);
return .js_undefined;
}

View File

@@ -11,6 +11,10 @@ array_length: usize = 0,
any_failed: bool = false,
pub fn next(this: *ObjectIterator) ?jsc.JSValue {
if (this.array.isEmptyOrUndefinedOrNull() or this.columns.isEmptyOrUndefinedOrNull()) {
this.any_failed = true;
return null;
}
if (this.row_i >= this.array_length) {
return null;
}

View File

@@ -862,11 +862,6 @@ export function isDockerEnabled(): boolean {
return false;
}
// TODO: investigate why its not starting on Linux arm64
if ((isLinux && process.arch === "arm64") || isMacOS) {
return false;
}
try {
const info = execSync(`${dockerCLI} info`, { stdio: ["ignore", "pipe", "inherit"] });
return info.toString().indexOf("Server Version:") !== -1;
@@ -924,7 +919,7 @@ export async function describeWithContainer(
return;
}
const { arch, platform } = process;
if ((archs && !archs?.includes(arch)) || platform === "win32" || platform === "darwin") {
if ((archs && !archs?.includes(arch)) || platform === "win32") {
test.skip(`docker image is not supported on ${platform}/${arch}, skipped: ${image}`, () => {});
return false;
}

View File

@@ -616,6 +616,7 @@ if (docker) {
expect(e.message).toBe("password error");
}
});
test("Support dynamic async password function that throws", async () => {
await using sql = new SQL({
...options,
@@ -633,6 +634,7 @@ if (docker) {
expect(e.message).toBe("password error");
}
});
test("sql file", async () => {
await using sql = new SQL(options);
expect((await sql.file(rel("select.sql")))[0].x).toBe(1);
@@ -884,7 +886,7 @@ if (docker) {
} catch (e) {
expect(e).toBeInstanceOf(Error);
expect(e.code).toBe("ERR_MYSQL_CONNECTION_TIMEOUT");
expect(e.message).toMatch(/Connection timed out after 200ms/);
expect(e.message).toMatch(/Connection timeout after 200ms/);
} finally {
sql.close();
server.close();

View File

@@ -2685,7 +2685,7 @@ if (isDockerEnabled()) {
expect(e).toBeInstanceOf(SQL.SQLError);
expect(e).toBeInstanceOf(SQL.PostgresError);
expect(e.code).toBe("ERR_POSTGRES_CONNECTION_TIMEOUT");
expect(e.message).toMatch(/Connection timed out after 200ms/);
expect(e.message).toMatch(/Connection timeout after 200ms/);
} finally {
sql.close();
server.close();