| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -78,6 +78,9 @@ console.log(query.all()); | |||
| 78 | 78 | <!-- YAML | |
| 79 | 79 | added: v22.5.0 | |
| 80 | 80 | changes: | |
| 81 | + - version: REPLACEME | ||
| 82 | + pr-url: https://github.com/nodejs/node/pull/57752 | ||
| 83 | + description: Add `timeout` option. | ||
| 81 | 84 | - version: v22.15.0 | |
| 82 | 85 | pr-url: https://github.com/nodejs/node/pull/56991 | |
| 83 | 86 | description: The `path` argument now supports Buffer and URL objects. | |
@@ -116,6 +119,9 @@ added: v22.5.0 | |||
| 116 | 119 | and the `loadExtension()` method are enabled. | |
| 117 | 120 | You can call `enableLoadExtension(false)` later to disable this feature. | |
| 118 | 121 | **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`. | ||
| 119 | 125 | ||
| 120 | 126 | Constructs a new `DatabaseSync` instance. | |
| 121 | 127 | ||
@@ -821,6 +827,7 @@ resolution handler passed to [`database.applyChangeset()`][]. See also | |||
| 821 | 827 | [`sqlite3session_create()`]: https://www.sqlite.org/session/sqlite3session_create.html | |
| 822 | 828 | [`sqlite3session_delete()`]: https://www.sqlite.org/session/sqlite3session_delete.html | |
| 823 | 829 | [`sqlite3session_patchset()`]: https://www.sqlite.org/session/sqlite3session_patchset.html | |
| 830 | + [busy timeout]: https://sqlite.org/c3ref/busy_timeout.html | ||
| 824 | 831 | [connection]: https://www.sqlite.org/c3ref/sqlite3.html | |
| 825 | 832 | [data types]: https://www.sqlite.org/datatype3.html | |
| 826 | 833 | [double-quoted string literals]: https://www.sqlite.org/quirks.html#dblquote | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -731,6 +731,8 @@ bool DatabaseSync::Open() { | |||
| 731 | 731 | CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false); | |
| 732 | 732 | CHECK_EQ(foreign_keys_enabled, open_config_.get_enable_foreign_keys()); | |
| 733 | 733 | ||
| 734 | + sqlite3_busy_timeout(connection_, open_config_.get_timeout()); | ||
| 735 | + | ||
| 734 | 736 | if (allow_load_extension_) { | |
| 735 | 737 | if (env()->permission()->enabled()) [[unlikely]] { | |
| 736 | 738 | THROW_ERR_LOAD_SQLITE_EXTENSION(env(), | |
@@ -940,6 +942,23 @@ void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) { | |||
| 940 | 942 | } | |
| 941 | 943 | allow_load_extension = allow_extension_v.As<Boolean>()->Value(); | |
| 942 | 944 | } | |
| 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 | + } | ||
| 943 | 962 | } | |
| 944 | 963 | ||
| 945 | 964 | new DatabaseSync( | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -35,11 +35,16 @@ class DatabaseOpenConfiguration { | |||
| 35 | 35 | ||
| 36 | 36 | inline void set_enable_dqs(bool flag) { enable_dqs_ = flag; } | |
| 37 | 37 | ||
| 38 | + inline void set_timeout(int timeout) { timeout_ = timeout; } | ||
| 39 | + | ||
| 40 | + inline int get_timeout() { return timeout_; } | ||
| 41 | + | ||
| 38 | 42 | private: | |
| 39 | 43 | std::string location_; | |
| 40 | 44 | bool read_only_ = false; | |
| 41 | 45 | bool enable_foreign_keys_ = true; | |
| 42 | 46 | bool enable_dqs_ = false; | |
| 47 | + int timeout_ = 0; | ||
| 43 | 48 | }; | |
| 44 | 49 | ||
| 45 | 50 | class StatementSync; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -77,6 +77,15 @@ suite('DatabaseSync() constructor', () => { | |||
| 77 | 77 | }); | |
| 78 | 78 | }); | |
| 79 | 79 | ||
| 80 | + test('throws if options.timeout is provided but is not an integer', (t) => { | ||
| 81 | + t.assert.throws(() => { | ||
| 82 | + new DatabaseSync('foo', { timeout: .99 }); | ||
| 83 | + }, { | ||
| 84 | + code: 'ERR_INVALID_ARG_TYPE', | ||
| 85 | + message: /The "options\.timeout" argument must be an integer/, | ||
| 86 | + }); | ||
| 87 | + }); | ||
| 88 | + | ||
| 80 | 89 | test('is not read-only by default', (t) => { | |
| 81 | 90 | const dbPath = nextDb(); | |
| 82 | 91 | const db = new DatabaseSync(dbPath); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,72 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + require('../common'); | ||
| 3 | + const tmpdir = 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 | + let cnt = 0; | ||
| 10 | + | ||
| 11 | + tmpdir.refresh(); | ||
| 12 | + | ||
| 13 | + function nextDb() { | ||
| 14 | + return join(tmpdir.path, `database-${cnt++}.db`); | ||
| 15 | + } | ||
| 16 | + | ||
| 17 | + test('waits to acquire lock', async (t) => { | ||
| 18 | + const DB_PATH = nextDb(); | ||
| 19 | + const conn = new DatabaseSync(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 | + const worker = new Worker(` | ||
| 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 | + await once(worker, 'online'); | ||
| 44 | + conn.exec('COMMIT;'); | ||
| 45 | + await once(worker, 'exit'); | ||
| 46 | + }); | ||
| 47 | + | ||
| 48 | + test('throws if the lock cannot be acquired before timeout', (t) => { | ||
| 49 | + const DB_PATH = nextDb(); | ||
| 50 | + const conn1 = new DatabaseSync(DB_PATH); | ||
| 51 | + t.after(() => { | ||
| 52 | + try { | ||
| 53 | + conn1.close(); | ||
| 54 | + } catch { | ||
| 55 | + // Ignore. | ||
| 56 | + } | ||
| 57 | + }); | ||
| 58 | + const conn2 = new DatabaseSync(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 | + }, /database is locked/); | ||
| 72 | + }); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments