Skip to content

Commit c12cd2a

Browse files
geeksilva97aduh95
authored andcommitted
sqlite: add timeout options to DatabaseSync
PR-URL: #57752 Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1 parent 8856712 commit c12cd2a

5 files changed

Lines changed: 112 additions & 0 deletions

File tree

‎doc/api/sqlite.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ console.log(query.all());
7878
<!-- YAML
7979
added: v22.5.0
8080
changes:
81+
- version: REPLACEME
82+
pr-url: https://github.com/nodejs/node/pull/57752
83+
description: Add `timeout` option.
8184
- version: v22.15.0
8285
pr-url: https://github.com/nodejs/node/pull/56991
8386
description: The `path` argument now supports Buffer and URL objects.
@@ -116,6 +119,9 @@ added: v22.5.0
116119
and the `loadExtension()` method are enabled.
117120
You can call `enableLoadExtension(false)` later to disable this feature.
118121
**Default:**`false`.
122+
*`timeout` {number} The [busy timeout][] in milliseconds. This is the maximum amount of
123+
time that SQLite will wait for a database lock to be released before
124+
returning an error. **Default:**`0`.
119125

120126
Constructs a new `DatabaseSync` instance.
121127

@@ -821,6 +827,7 @@ resolution handler passed to [`database.applyChangeset()`][]. See also
821827
[`sqlite3session_create()`]: https://www.sqlite.org/session/sqlite3session_create.html
822828
[`sqlite3session_delete()`]: https://www.sqlite.org/session/sqlite3session_delete.html
823829
[`sqlite3session_patchset()`]: https://www.sqlite.org/session/sqlite3session_patchset.html
830+
[busy timeout]: https://sqlite.org/c3ref/busy_timeout.html
824831
[connection]: https://www.sqlite.org/c3ref/sqlite3.html
825832
[data types]: https://www.sqlite.org/datatype3.html
826833
[double-quoted string literals]: https://www.sqlite.org/quirks.html#dblquote

‎src/node_sqlite.cc‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,8 @@ bool DatabaseSync::Open() {
731731
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
732732
CHECK_EQ(foreign_keys_enabled, open_config_.get_enable_foreign_keys());
733733

734+
sqlite3_busy_timeout(connection_, open_config_.get_timeout());
735+
734736
if (allow_load_extension_) {
735737
if (env()->permission()->enabled()) [[unlikely]] {
736738
THROW_ERR_LOAD_SQLITE_EXTENSION(env(),
@@ -940,6 +942,23 @@ void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
940942
}
941943
allow_load_extension = allow_extension_v.As<Boolean>()->Value();
942944
}
945+
946+
Local<Value> timeout_v;
947+
if (!options->Get(env->context(), env->timeout_string())
948+
.ToLocal(&timeout_v)) {
949+
return;
950+
}
951+
952+
if (!timeout_v->IsUndefined()) {
953+
if (!timeout_v->IsInt32()) {
954+
THROW_ERR_INVALID_ARG_TYPE(
955+
env->isolate(),
956+
"The \"options.timeout\" argument must be an integer.");
957+
return;
958+
}
959+
960+
open_config.set_timeout(timeout_v.As<Int32>()->Value());
961+
}
943962
}
944963

945964
newDatabaseSync(

‎src/node_sqlite.h‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,16 @@ class DatabaseOpenConfiguration {
3535

3636
inlinevoidset_enable_dqs(bool flag) { enable_dqs_ = flag; }
3737

38+
inlinevoidset_timeout(int timeout) { timeout_ = timeout; }
39+
40+
inlineintget_timeout() { return timeout_; }
41+
3842
private:
3943
std::string location_;
4044
bool read_only_ = false;
4145
bool enable_foreign_keys_ = true;
4246
bool enable_dqs_ = false;
47+
int timeout_ = 0;
4348
};
4449

4550
classStatementSync;

‎test/parallel/test-sqlite-database-sync.js‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,15 @@ suite('DatabaseSync() constructor', () => {
7777
});
7878
});
7979

80+
test('throws if options.timeout is provided but is not an integer',(t)=>{
81+
t.assert.throws(()=>{
82+
newDatabaseSync('foo',{timeout: .99});
83+
},{
84+
code: 'ERR_INVALID_ARG_TYPE',
85+
message: /The"options\.timeout"argumentmustbeaninteger/,
86+
});
87+
});
88+
8089
test('is not read-only by default',(t)=>{
8190
constdbPath=nextDb();
8291
constdb=newDatabaseSync(dbPath);
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
'use strict';
2+
require('../common');
3+
consttmpdir=require('../common/tmpdir');
4+
const{ join }=require('node:path');
5+
const{ DatabaseSync }=require('node:sqlite');
6+
const{ test }=require('node:test');
7+
const{ once }=require('node:events');
8+
const{ Worker }=require('node:worker_threads');
9+
letcnt=0;
10+
11+
tmpdir.refresh();
12+
13+
functionnextDb(){
14+
returnjoin(tmpdir.path,`database-${cnt++}.db`);
15+
}
16+
17+
test('waits to acquire lock',async(t)=>{
18+
constDB_PATH=nextDb();
19+
constconn=newDatabaseSync(DB_PATH);
20+
t.after(()=>{
21+
try{
22+
conn.close();
23+
}catch{
24+
// Ignore.
25+
}
26+
});
27+
28+
conn.exec('CREATE TABLE IF NOT EXISTS data (value TEXT)');
29+
conn.exec('BEGIN EXCLUSIVE;');
30+
constworker=newWorker(`
31+
'use strict';
32+
const { DatabaseSync } = require('node:sqlite');
33+
const { workerData } = require('node:worker_threads');
34+
const conn = new DatabaseSync(workerData.database, { timeout: 30000 });
35+
conn.exec('SELECT * FROM data');
36+
conn.close();
37+
`,{
38+
eval: true,
39+
workerData: {
40+
database: DB_PATH,
41+
}
42+
});
43+
awaitonce(worker,'online');
44+
conn.exec('COMMIT;');
45+
awaitonce(worker,'exit');
46+
});
47+
48+
test('throws if the lock cannot be acquired before timeout',(t)=>{
49+
constDB_PATH=nextDb();
50+
constconn1=newDatabaseSync(DB_PATH);
51+
t.after(()=>{
52+
try{
53+
conn1.close();
54+
}catch{
55+
// Ignore.
56+
}
57+
});
58+
constconn2=newDatabaseSync(DB_PATH,{timeout: 1});
59+
t.after(()=>{
60+
try{
61+
conn2.close();
62+
}catch{
63+
// Ignore.
64+
}
65+
});
66+
67+
conn1.exec('CREATE TABLE IF NOT EXISTS data (value TEXT)');
68+
conn1.exec('PRAGMA locking_mode = EXCLUSIVE; BEGIN EXCLUSIVE;');
69+
t.assert.throws(()=>{
70+
conn2.exec('SELECT * FROM data');
71+
},/databaseislocked/);
72+
});

0 commit comments

Comments
 (0)