Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions src/jsc/bindings/sqlite/JSSQLStatement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ static inline int lazyLoadSQLite()
#define ENABLE_SQLITE_FAST_MALLOC (BENABLE(MALLOC_SIZE) && BENABLE(MALLOC_GOOD_SIZE))
#endif

static std::atomic<int64_t> sqlite_malloc_amount = 0;
// Read as a delta across one synchronous call, so per-thread is enough.
static thread_local int64_t sqlite_malloc_amount = 0;

static void enableFastMallocForSQLite()
{
Expand Down Expand Up @@ -135,6 +136,11 @@ extern "C" void Bun__initializeSQLite()
{
static std::once_flag onceFlag;
std::call_once(onceFlag, [] {
// Off, SQLite skips its process-wide mutex in every malloc and free.
int returnCode = sqlite3_config(SQLITE_CONFIG_MEMSTATUS, 0);
Comment thread
robobun marked this conversation as resolved.
Resolved
ASSERT_WITH_MESSAGE(returnCode == SQLITE_OK, "Unable to disable SQLite memory statistics");
UNUSED_PARAM(returnCode);

enableFastMallocForSQLite();
});
}
Expand Down Expand Up @@ -1730,8 +1736,6 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementPrepareStatementFunction, (JSC::JSGlobalO

sqlite3_stmt* statement = nullptr;

// This is inherently somewhat racy if using Worker
// but that should be okay.
int64_t currentMemoryUsage = sqlite_malloc_amount;

int rc = SQLITE_OK;
Expand Down
51 changes: 51 additions & 0 deletions test/js/bun/sqlite/sqlite.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2889,3 +2889,54 @@ it.skipIf(!sqliteAllowsMoreThan65535Parameters)(
expect(db.query("SELECT a FROM t").all()).toEqual([{ a: 7 }]);
},
);

// SQLite memory statistics are off (SQLITE_CONFIG_MEMSTATUS=0, like Node's
// SQLITE_DEFAULT_MEMSTATUS=0). With them on, every sqlite3Malloc/sqlite3_free
// takes one process-wide mutex, so Workers with private connections serialize
// on it. The only JS-visible effect of the setting is that the heap-limit
// pragmas have nothing to count: with statistics on, a limit of 1 byte fails
// the next allocation with SQLITE_NOMEM. Each case runs in its own process
// because the limit is process-global. On macOS the system libsqlite3 already
// ships DEFAULT_MEMSTATUS=0, so only the bundled build (Linux, Windows) can
// fail this without the runtime config.
describe.concurrent("memory statistics are off", () => {
const inserts = `
db.exec("CREATE TABLE t (id INTEGER PRIMARY KEY, s TEXT)");
const insert = db.prepare("INSERT INTO t (s) VALUES (?)");
const row = Buffer.alloc(4096, "x").toString();
for (let i = 0; i < 200; i++) insert.run(row);
console.log(JSON.stringify(db.prepare("SELECT count(*) AS n FROM t").get()));
`;

it.each([
[
"bun:sqlite",
`
const { Database } = require("bun:sqlite");
const db = new Database(":memory:");
db.run("PRAGMA hard_heap_limit = 1");
${inserts}
`,
],
[
"node:sqlite",
`
const { DatabaseSync } = require("node:sqlite");
const db = new DatabaseSync(":memory:");
db.exec("PRAGMA hard_heap_limit = 1");
${inserts}
`,
],
])("%s: PRAGMA hard_heap_limit does not fail allocations", async (_, code) => {
Comment thread
robobun marked this conversation as resolved.
Resolved
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", code],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout.trim()).toBe(JSON.stringify({ n: 200 }));
expect(exitCode).toBe(0);
});
});
Loading