@bobfrankston/rmfmail 1.2.7 → 1.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,51 @@
1
+ /** TRUE cross-thread two-writer test: a worker thread and the main thread each
2
+ * hammer UPDATEs on their own write connection to one WAL file for 3s. Counts
3
+ * SQLITE_BUSY ("database is locked") under each busy-handling mode. This is the
4
+ * real scenario the sync worker + main thread are in. */
5
+ import { DatabaseSync } from "node:sqlite";
6
+ import { Worker } from "node:worker_threads";
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import os from "node:os";
10
+ import { fileURLToPath } from "node:url";
11
+
12
+ const here = path.dirname(fileURLToPath(import.meta.url));
13
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "rmf-2wt-"));
14
+ const dbPath = path.join(tmp, "t.db");
15
+
16
+ function fresh() {
17
+ try { for (const s of ["", "-wal", "-shm"]) fs.rmSync(dbPath + s, { force: true }); } catch {}
18
+ const d = new DatabaseSync(dbPath);
19
+ d.exec("PRAGMA journal_mode = WAL");
20
+ d.exec("CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)");
21
+ for (let i = 0; i < 200; i++) d.prepare("INSERT INTO t (id,v) VALUES (?,?)").run(i, "x");
22
+ d.close();
23
+ }
24
+
25
+ async function run(mode, ms) {
26
+ fresh();
27
+ const worker = new Worker(path.join(here, "two-writer-worker.mjs"), { workerData: { dbPath, mode, ms, tag: "W" } });
28
+ const workerDone = new Promise(res => worker.once("message", res));
29
+ // Main thread connection.
30
+ const opts = mode === "ctor" ? { timeout: ms } : {};
31
+ const d = new DatabaseSync(dbPath, opts);
32
+ if (mode === "pragma") d.exec(`PRAGMA busy_timeout = ${ms}`);
33
+ let ok = 0, locks = 0;
34
+ const end = Date.now() + 3000;
35
+ while (Date.now() < end) {
36
+ try { d.prepare("UPDATE t SET v=? WHERE id=?").run(`M-${ok}`, ok % 200); ok++; }
37
+ catch (e) { if (/locked|busy/i.test(e.message)) locks++; else throw e; }
38
+ }
39
+ d.close();
40
+ const w = await workerDone;
41
+ await worker.terminate();
42
+ console.log(` mode=${mode.padEnd(7)} timeout=${ms}ms → main: ${ok} ok/${locks} locked | worker: ${w.ok} ok/${w.locks} locked`);
43
+ return locks + w.locks;
44
+ }
45
+
46
+ console.log(`\ncross-thread two-writer test (node ${process.version})\n`);
47
+ await run("none", 0);
48
+ await run("pragma", 5000);
49
+ await run("ctor", 5000);
50
+ try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {}
51
+ console.log("");
@@ -0,0 +1,14 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { workerData, parentPort } from "node:worker_threads";
3
+ const { dbPath, mode, ms, tag } = workerData;
4
+ const opts = mode === "ctor" ? { timeout: ms } : {};
5
+ const d = new DatabaseSync(dbPath, opts);
6
+ if (mode === "pragma") d.exec(`PRAGMA busy_timeout = ${ms}`);
7
+ let ok = 0, locks = 0;
8
+ const end = Date.now() + 3000;
9
+ while (Date.now() < end) {
10
+ try { d.prepare("UPDATE t SET v=? WHERE id=?").run(`${tag}-${ok}`, ok % 200); ok++; }
11
+ catch (e) { if (/locked|busy/i.test(e.message)) locks++; else throw e; }
12
+ }
13
+ d.close();
14
+ parentPort.postMessage({ ok, locks });