Skip to content
Open
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
8 changes: 6 additions & 2 deletions src/node_sqlite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1109,7 +1109,7 @@ bool VirtualTableModule::CanCallIntoJS() const {
bool VirtualTableModule::CloseIterator(NodeVTabCursor* cursor) {
VirtualTableModule* mod = cursor->module;

// Skipped in two cases:
// Skipped in three cases:
//
// - While the database is being torn down from a destructor, because those
// run from a garbage collection callback where JavaScript cannot be
Expand All @@ -1120,7 +1120,11 @@ bool VirtualTableModule::CloseIterator(NodeVTabCursor* cursor) {
// A generator whose own body threw has already run its `finally` as part of
// that throw, so this only affects an iterator abandoned while suspended
// because something else failed.
if (cursor->iterator.IsEmpty() || !mod->CanCallIntoJS() ||
// - When next() has already reported `done: true`, because the iteration ran
// to completion. `return()` is only called on abrupt termination in
// `for...of`; a finished iterator must not be asked to clean up again, and
// a custom iterable may throw from `return()` even after completion.
if (cursor->iterator.IsEmpty() || cursor->done || !mod->CanCallIntoJS() ||

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This misses the protocol-error paths. If next() returns a non-object (or next isn't a function), done stays false, so return() still runs on close and can mask the original error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: what about resetting cursor->iterator in xNext on done/error instead? IsEmpty() would cover it and fix the above too.

mod->env_->isolate()->HasPendingException()) {
return false;
}
Expand Down
35 changes: 35 additions & 0 deletions test/parallel/test-sqlite-virtual-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,41 @@ suite('Database.prototype.createModule()', () => {
assert.deepStrictEqual(cleanedUp, [1, 2]);
});

test('does not call return() on a normally exhausted iterator', () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get a re-filter test too? A correlated subquery should hit it.

// Once next() reports `done: true` the iterator is finished, so no
// cleanup should run. A throwing return() must not fail an otherwise
// successful query. Matches `for...of`, which only calls return() on
// abrupt termination.
const db = new Database(':memory:');
for (const [label, makeRows] of [
['empty', () => {
return {
[Symbol.iterator]() { return this; },
next() { return { value: [], done: true }; },
return() { throw new Error('cleanup boom'); },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe count return() calls and assert 0? Right now a swallowed error would still pass.

};
}],
['fully_consumed', () => {
let i = 0;
return {
[Symbol.iterator]() { return this; },
next() { return { value: [i++], done: i > 2 }; },
return() { throw new Error('cleanup boom'); },
};
}],
]) {
db.createModule(`exhausted_${label}`, {
columns: [{ name: 'v', type: 'INTEGER' }],
rows: makeRows,
});

const rows = db.prepare(`SELECT v FROM exhausted_${label}`).all();
assert.deepStrictEqual(
rows.map((row) => row.v),
label === 'empty' ? [] : [0, 1]);
}
});

test('does not run cleanup when the statement is collected', () => {
// The destructor runs from a GC callback, where JavaScript cannot be
// executed. An abandoned generator does not run `finally` in JavaScript
Expand Down
Loading