Repository navigation
sqlite: StatementSync#iterate fails with Error: statement has been finalized #57493
Description
Activity
- added a commit that references this issue
on Mar 16, 2025 - addedsqliteIssues and PRs related to the SQLite subsystem.Issues and PRs related to the SQLite subsystem.
on Mar 16, 2025 cc: @tpoisseau
Hello,
Today
.iterate()is tested only with.toArray(). I assert iftoArrayworks, it's OK for iteration and iterators helpers. And it's tested on a few rows.I don't know when I will have time to work on this, v8 CPP is really hard for me. It look like, somewhere, the statement is closed / free during the iteration. I have no idea why.
I think the problem is the iterator doesn't have a reference to the
StatementSyncobject returned bydatabase.prepare(). So as soon asdatabase.prepare("SELECT * from users;").iterate()has been called, the GC is free to collect the intermediateStatementSyncobject. And, when it does, the underlying sqlite statement is finalised.Proof: edit the code to this:
const stmt = database.prepare("SELECT * from users;"); const iter = stmt.iterate();
That seems to work till the cows come home, because I'm now hanging on to
stmtand so it can't be GC'd.I imagine the solution is to create a ref to the
StatementSyncobject in the iterator object returned byiterate()(i.e. from insideStatementSync::Iterate(const FunctionCallbackInfo<Value>&)) e.g. a[Symbol.stmt]property that links to it.@fuchsia I believe your assessment is correct. The only thing I'd add is that the iterator can hold a reference to the
StatementSyncobject without the reference being exposed to users in JavaScript.Reacted by fuchsiaI expect to have some time next week when I could have a go at writing a fix. I've not built node before, and getting my system set up to do that is the most daunting bit! I've plenty of experience of C++ and using sqlite3 from C++, although it's a bit rusty.
Reacted by Colin Ihrig and tpoisseau- addedconfirmed-bugIssues and PRs for confirmed bugs.Issues and PRs for confirmed bugs.
on Mar 20, 2025 - added a commit that references this issue
on Mar 20, 2025 I just encountered this issue while working on https://github.com/ducktors/Valkeyrie
I have this test case that returns this error while I would expect a different one. Something like "Error: the db is closed', but it throws "Error: statement has been finalized" instead.
This is the test case:
await test('Valkeyrie explicit resource management', async () => { let db2: Valkeyrie { await using db = await Valkeyrie.open() db2 = db const res = await db.get(['a']) assert.strictEqual(res.versionstamp, null) } await assert.rejects(() => db2.get(['a']), Error) })
Is this the expected behavior? Do I need to track the status of the database in the user land?
@fox1t what you're seeing is not the same as this issue, which is specific to
iterate().Is this the expected behavior?
You're using an additional API on top of
node:sqlite, but I think it's expected. In this code, I expectdbto close once it goes out of scope even though you've aliased it. Once the database is closed, all of the prepared statements associated with that database are finalized.Something like "Error: the db is closed', but it throws "Error: statement has been finalized" instead.
If you try to use a database after it's closed, you get a message that the database is closed. If you try to use an individual prepared statement after the database is closed or the statement is otherwise finalized, you get the finalized error.
Do I need to track the status of the database in the user land?
No, you shouldn't need to. There is also going to be a method for this in the next release (#57522).
Reacted by Maksim Sinik@fox1t what you're seeing is not the same as this issue, which is specific to
iterate().Is this the expected behavior?
You're using an additional API on top of
node:sqlite, but I think it's expected. In this code, I expectdbto close once it goes out of scope even though you've aliased it. Once the database is closed, all of the prepared statements associated with that database are finalized.Something like "Error: the db is closed', but it throws "Error: statement has been finalized" instead.
If you try to use a database after it's closed, you get a message that the database is closed. If you try to use an individual prepared statement after the database is closed or the statement is otherwise finalized, you get the finalized error.
Do I need to track the status of the database in the user land?
No, you shouldn't need to. There is also going to be a method for this in the next release (#57522).
Oh, @cjihrig, that's awesome. Thanks for sharing this info about the finalization of the statements. I didn't realize it until now. That's exactly what happens since I prepare all queries at the "driver" level. It is a tiny wrapper of the underlying backend (SQLite in this case) that doesn't add any logic. It's cool that isOpen is being added!
Reacted by Colin Ihrig- added a commit that references this issue
on Apr 3, 2025 - added 2 commits that reference this issue
on May 1, 2025 - added a commit that references this issue
on May 14, 2025
Version
v23.10.0
Platform
Subsystem
sqlite
What steps will reproduce the bug?
index.jsHow often does it reproduce? Is there a required condition?
Happens every time. Sometimes it errors, sometimes it just stops unexpectedly without an error.
It doesn't reproduce if you uncomment the line where I fetch all rows immediately by spreading it into an array and then turning it back into an iterator, so the issue comes from reading each value over time.
What is the expected behavior? Why is that the expected behavior?
It should not error and it should print 1000 rows.
What do you see instead?
90% of runs
OR
10% it just stops without an error. It should have printed up to 1000.
Sometimes the values of the last "successful" row is garbage. Sometimes for
nameit printsundefinedor something likeÙÙ¥Ñ88. The name should always match0-9a-zso there is some sort of corruption, too.Additional information
You can see on the Ubuntu run, it exits with an error. On the Windows run, it just exits for no apparent reason. That is random, though, not platform-dependent.