Skip to content

'transactions' example segmentation fault on windows x64 #309

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
s4s0l opened this issue Apr 4, 2025 · 1 comment
Open

'transactions' example segmentation fault on windows x64 #309

s4s0l opened this issue Apr 4, 2025 · 1 comment

Comments

@s4s0l
Copy link

s4s0l commented Apr 4, 2025

Running examples/transactions example in a loop results in segfault on windows.

If I run many transactions in a loop, they all succeed no matter how many transactions there are. But if you leave node running afterwards for a while, it will crush. It happens only when loop is long. Above 1000 iterations it happens all the time.

Segfault handler gives me this:

00007FFCCA739D06 (ntdll): (filename not available): RtlWow64GetCurrentCpuArea
00007FFCCA73A10E (ntdll): (filename not available): RtlWow64GetCurrentCpuArea
00007FFCCA86003E (ntdll): (filename not available): KiUserExceptionDispatcher
00007FFC3346D854 (index): (filename not available): napi_register_module_v1
00007FFC331B3FA2 (index): (filename not available): napi_register_module_v1
00007FFC33315ED2 (index): (filename not available): napi_register_module_v1
00007FFC32F3B602 (index): (filename not available): (function-name not available)
00007FFC32F17A2C (index): (filename not available): (function-name not available)
00007FFC32F33054 (index): (filename not available): (function-name not available)
00007FF7E5081024 (node): (filename not available): node_module_register
00007FF7E5081FA7 (node): (filename not available): node_module_register
00007FF7E50984D2 (node): (filename not available): v8::base::CPU::has_popcnt
00007FF7E5082610 (node): (filename not available): node_module_register
00007FF7E50D1300 (node): (filename not available): node_api_throw_syntax_error
00007FF7E50B6ADD (node): (filename not available): node_api_throw_syntax_error
00007FF7E5142151 (node): (filename not available): uv_pipe_pending_type
00007FF7E514E9FD (node): (filename not available): uv_run
00007FF7E511F125 (node): (filename not available): node::SpinEventLoop
00007FF7E4FF010A (node): (filename not available): X509_STORE_get_cleanup
00007FF7E508CB23 (node): (filename not available): node::Start
00007FF7E508B907 (node): (filename not available): node::Start
00007FF7E4DAE51C (node): (filename not available): AES_cbc_encrypt
00007FF7E68275B8 (node): (filename not available): inflateValidate
00007FFCC90EE8D7 (KERNEL32): (filename not available): BaseThreadInitThunk
00007FFCCA7B14FC (ntdll): (filename not available): RtlUserThreadStart
/c/nvm4w/nodejs/npm: line 65:  7834 Segmentation fault      "$NODE_EXE" "$NPM_CLI_JS" "$@"

I'm not capable enough to compile libsql with debug symbols, but maybe someone else would have an idea what libsql is doing here.

Tested with : libsql 0.4.6 & 0.5.4. Node 22.12.0 / 22.14.0 / 18.20.8 / 23.11.0.

On linux (x86_64) it does not crush.

Source example here
import { createClient } from "@libsql/client";
// stack trace
// requires "segfault-handler": "^1.3.0"
// import pkg from 'segfault-handler';
// const { registerHandler } = pkg;
// registerHandler('segfault-crush.log');
const client = createClient({
    url: "file:local.db",
});
await client.batch(
    [
        "DROP TABLE IF EXISTS users",
        "CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)",
        "INSERT INTO users (name) VALUES ('Iku Turso')",
    ],
    "write",
);
const names = ["John Doe", "Mary Smith", "Alice Jones", "Mark Taylor"];
let secondTransaction;

// for 400 it does not crush
// above 1000 it crushes always
// but all the transactions are committed ok
const theAmount = 1000;
try {
    for(let i = 0; i < theAmount; i++){

        secondTransaction = await client.transaction("write");
        for (const name of names) {
            await secondTransaction.execute({
                sql: "INSERT INTO users (name) VALUES (?)",
                args: [name],
            });
        }   
        await secondTransaction.commit();
        if (i % 100 === 0)
        console.log("user " + i + " added successfully")
    }
} catch (e) {
    console.error(e);
    await secondTransaction?.rollback();
}
const result = await client.execute("SELECT * FROM users");
console.log("Users:", result.rows.length); 
await new Promise(resolve => setTimeout(resolve, 10000)); // it will segfault here.
console.log("Done");
@s4s0l
Copy link
Author

s4s0l commented Apr 4, 2025

Here:

https://github.com/tursodatabase/libsql-client-ts/blob/1caf3fd2bc876651bf5645a23c60830090bedda9/packages/libsql-client/src/sqlite3.ts#L197C1-L202C6

 async transaction(mode: TransactionMode = "write"): Promise<Transaction> {
        const db = this.#getDb();
        executeStmt(db, transactionModeToBegin(mode), this.#intMode);
        this.#db = null; // A new connection will be lazily created on next use
        return new Sqlite3Transaction(db, this.#intMode);
    }

Commenting out this.#db = null; prevents segfault.

I suppose it was made like this to allow interleaved transactions ... but: #288.

I would like to notice that passing 'Database' object (db) to Sqlite3Transaction object has an ugly effect that old database object will never be closed, so I assume something funky happens when gc takes care of that.

EDIT:
Exacly same thing happens if I just create bunch of databases in a loop, execute one query in each of them, without closing them, and not storing any reference to them. On the other hand closing them all before gc takes over, is completely fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant