Skip to content

Commit eba1dad

Browse files
committed
Retry loading better-sqlite3 before failing database open
Loading the native better-sqlite3 binary can fail transiently with "The paging file is too small for this operation to complete" under Windows virtual memory pressure (MAILSPRING-CLIENT-9). The require() was previously a top-level import, so this failure was an unhandled exception unrelated to the database file, yet it was indistinguishable from a corrupt database and left the app stuck without explanation. Retry the module load a few times before giving up, and if it still fails, report a clear, actionable error instead of treating it as an unrecoverable database error (which would wipe and resync local data for a problem that has nothing to do with the database itself). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DzBiH9WKXBG4BU6QGUxqTM
1 parent 427c402 commit eba1dad

1 file changed

Lines changed: 43 additions & 2 deletions

File tree

‎app/src/flux/stores/database-store.ts‎

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import path from 'path';
33
import createDebug from 'debug';
44
import childProcess, { ChildProcess } from 'child_process';
55
import { LRUCache } from 'lru-cache';
6-
import Sqlite3 from 'better-sqlite3';
6+
import type Sqlite3 from 'better-sqlite3';
77

88
import { ExponentialBackoffScheduler } from '../../backoff-schedulers';
99
import { Model } from '../models/model';
1010
import MailspringStore from '../../global/mailspring-store';
1111
import * as Utils from '../models/utils';
1212
import Query from '../models/query';
13+
import { localized } from '../../intl';
1314

1415
const debug = createDebug('app:RxDB');
1516
const debugVerbose = createDebug('app:RxDB:all');
@@ -48,9 +49,49 @@ function handleUnrecoverableDatabaseError(
4849
});
4950
}
5051

52+
const SQLITE_MODULE_LOAD_RETRIES = 3;
53+
const SQLITE_MODULE_LOAD_RETRY_DELAY = 1000;
54+
55+
// Loading the native better-sqlite3 binary can fail transiently under
56+
// Windows virtual memory pressure ("The paging file is too small for this
57+
// operation to complete", MAILSPRING-CLIENT-9) even though the database file
58+
// itself is fine. Retry a few times before giving up, rather than routing
59+
// the failure through handleUnrecoverableDatabaseError, which would wipe and
60+
// resync the local database for a problem that has nothing to do with it.
61+
async function requireSqlite3(): Promise<typeof import('better-sqlite3')> {
62+
for (let attempt = 1; ; attempt++) {
63+
try {
64+
return require('better-sqlite3');
65+
} catch (err) {
66+
if (attempt >= SQLITE_MODULE_LOAD_RETRIES) {
67+
throw err;
68+
}
69+
await Promise.delay(SQLITE_MODULE_LOAD_RETRY_DELAY);
70+
}
71+
}
72+
}
73+
74+
function handleUnavailableSqliteModule(err: Error) {
75+
AppEnv.errorLogger.reportError(err);
76+
AppEnv.showErrorDialog(
77+
localized(
78+
'Mailspring could not load its database engine. This usually happens when your computer is critically low on virtual memory (paging file / swap space). Please free up disk space or increase your paging file size, then restart your computer and try again.'
79+
),
80+
{ detail: err.toString() }
81+
);
82+
}
83+
5184
async function openDatabase(dbPath: string) {
85+
let SqliteCtor: typeof import('better-sqlite3');
86+
try {
87+
SqliteCtor = await requireSqlite3();
88+
} catch (err) {
89+
handleUnavailableSqliteModule(err);
90+
return null;
91+
}
92+
5293
try {
53-
const db = new Sqlite3(dbPath, { readonly: true, timeout: 10000 }) as Sqlite3.Database;
94+
const db = new SqliteCtor(dbPath, { readonly: true, timeout: 10000 }) as Sqlite3.Database;
5495

5596
// https://www.sqlite.org/wal.html
5697
// WAL provides more concurrency as readers do not block writers and a writer

0 commit comments

Comments
 (0)