Version
v27.0.0-pre (current main)
Platform
Linux ari-cachyos 7.1.8-1-cachyos #1 SMP PREEMPT_DYNAMIC Mon, 10 Aug 2026 19:39:40 +0000 x86_64 GNU/Linux
Subsystem
node:sqlite
What steps will reproduce the bug?
const{ DatabaseSync, constants }=require('node:sqlite');for(const[name,close]of[['close()',(session)=>session.close()],['[Symbol.dispose]()',(session)=>session[Symbol.dispose]()],]){constdb=newDatabaseSync(':memory:');db.exec('CREATE TABLE data(key INTEGER PRIMARY KEY, value TEXT)');constsession=db.createSession({table: 'data'});db.exec("INSERT INTO data VALUES (1, 'value')");letattempted=false;db.setAuthorizer(()=>{if(!attempted){attempted=true;try{close(session);console.log(`${name}: no throw`);}catch(error){console.log(`${name}: ${error.code}: ${error.message}`);}}returnconstants.SQLITE_OK;});session.changeset();db.setAuthorizer(null);session.close();db.close();}How often does it reproduce? Is there a required condition?
Every time disposal is attempted while the session is generating a changeset or patchset.
What is the expected behavior? Why is that the expected behavior?
session[Symbol.dispose]() should throw the same ERR_INVALID_STATE as session.close() when the session cannot be closed safely. StatementSync already follows this pattern: its disposal propagates the active-statement guard, with coverage in test/parallel/test-sqlite-diagnostic-channel.js under “subscriber cannot close the database or statement”.
Expected output:
close(): ERR_INVALID_STATE: session is currently in use
[Symbol.dispose](): ERR_INVALID_STATE: session is currently in use
What do you see instead?
close(): ERR_INVALID_STATE: session is currently in use
[Symbol.dispose](): no throw
Session::Dispose() catches and discards the exception from Session::Close(). The session remains open even though disposal appeared to succeed.
Additional information
The reentrancy guard added by #65349 works: sqlite3session_delete() is not called while changeset generation is active, so this does not reproduce the use-after-free that the guard prevents. The remaining bug is the silent no-op and inconsistent error propagation from [Symbol.dispose]().
Version
v27.0.0-pre (current main)
Platform
Subsystem
node:sqlite
What steps will reproduce the bug?
How often does it reproduce? Is there a required condition?
Every time disposal is attempted while the session is generating a changeset or patchset.
What is the expected behavior? Why is that the expected behavior?
session[Symbol.dispose]()should throw the sameERR_INVALID_STATEassession.close()when the session cannot be closed safely.StatementSyncalready follows this pattern: its disposal propagates the active-statement guard, with coverage intest/parallel/test-sqlite-diagnostic-channel.jsunder “subscriber cannot close the database or statement”.Expected output:
What do you see instead?
Session::Dispose()catches and discards the exception fromSession::Close(). The session remains open even though disposal appeared to succeed.Additional information
The reentrancy guard added by #65349 works:
sqlite3session_delete()is not called while changeset generation is active, so this does not reproduce the use-after-free that the guard prevents. The remaining bug is the silent no-op and inconsistent error propagation from[Symbol.dispose]().