mirror of
https://github.com/zotero/zotero.git
synced 2026-10-10 03:28:10 +00:00
Fix full-text indexing failing silently after periodic vacuum or backup
Some checks failed
CI / Build, Upload, Test (push) Has been cancelled
Some checks failed
CI / Build, Upload, Test (push) Has been cancelled
vacuum() and the APFS-cloning offline backup path both close and reopen the SQLite connection, which drops all ATTACHed databases -- including the in-memory "indexing" alias used for the fulltextWords scratch table set up in Fulltext.init(). Once the connection is reopened, indexing queries fail with "no such table: indexing.fulltextWords", and indexItems()'s ignoreErrors path routes the error to logError(), so indexing silently stops working for the rest of the session. Add an onConnect() hook on Zotero.DBConnection for per-connection state that doesn't persist across reopens, and use it from Fulltext.init() to re-attach the indexing DB on each reconnect. Regression sources: -67288047f3("Use APFS cloning for file copies on macOS") flips online idle backups to offline-with-clone on APFS, so every idle backup interval (24h default) closes and reopens the connection. Affects Mac users on APFS. -b27c4cb023("Enable SQLite WAL mode and add periodic VACUUM INTO") adds the vacuum path, which closes and reopens on the first idle period in a session that passes the freelist/time gates. Affects all users with sufficient DB churn, roughly once per 14 days. https://forums.zotero.org/discussion/131576/debug-id-d848621212-indexing-of-pdfs-fail-zotero-9-0-3 https://forums.zotero.org/discussion/131718/possible-bug-regression-report-search-unusable-on-macos-zotero-9-0-3-with-large-library (cherry picked from commit70c4cadab1)
This commit is contained in:
parent
6881089b1b
commit
74a3617e7e
4 changed files with 58 additions and 5 deletions
|
|
@ -105,7 +105,8 @@ Zotero.DBConnection = function (dbNameOrPath) {
|
|||
};
|
||||
this._dbIsCorrupt = null
|
||||
this._onlineBackupInProgress = false;
|
||||
|
||||
this._onConnectCallbacks = [];
|
||||
|
||||
this._transactionPromise = null;
|
||||
|
||||
if (dbNameOrPath == 'zotero') {
|
||||
|
|
@ -987,6 +988,21 @@ Zotero.DBConnection.prototype.closeDatabase = async function (permanent) {
|
|||
};
|
||||
|
||||
|
||||
/**
|
||||
* Register a callback to run after the SQLite connection is opened, including after it has
|
||||
* been closed and reopened (e.g., after vacuum()). Use this for per-connection state that
|
||||
* doesn't persist across reopens, such as ATTACHed databases or temp tables.
|
||||
*
|
||||
* The callback is not invoked for the connection that's already open at registration time --
|
||||
* the caller is responsible for any initial setup.
|
||||
*
|
||||
* @param {Function} callback - Async function called with no arguments after each (re)open
|
||||
*/
|
||||
Zotero.DBConnection.prototype.onConnect = function (callback) {
|
||||
this._onConnectCallbacks.push(callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
|
|
@ -1302,7 +1318,16 @@ Zotero.DBConnection.prototype._getConnectionAsync = async function () {
|
|||
idleService.addIdleObserver(this, 300);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
for (let callback of this._onConnectCallbacks) {
|
||||
try {
|
||||
await callback();
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
return this._connection;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -61,9 +61,15 @@ Zotero.Fulltext = Zotero.FullText = new function () {
|
|||
var _syncLibraryVersion = 0;
|
||||
|
||||
this.init = async function () {
|
||||
await Zotero.DB.queryAsync("ATTACH ':memory:' AS 'indexing'");
|
||||
await Zotero.DB.queryAsync('CREATE TABLE indexing.fulltextWords (word NOT NULL)');
|
||||
|
||||
let setUpIndexingDB = async () => {
|
||||
await Zotero.DB.queryAsync("ATTACH ':memory:' AS 'indexing'");
|
||||
await Zotero.DB.queryAsync('CREATE TABLE indexing.fulltextWords (word NOT NULL)');
|
||||
};
|
||||
await setUpIndexingDB();
|
||||
// ATTACHed databases don't survive a connection reopen (e.g., after vacuum), so
|
||||
// re-run the setup on every reconnect
|
||||
Zotero.DB.onConnect(setUpIndexingDB);
|
||||
|
||||
let pdfConverterFileName = "pdftotext";
|
||||
let pdfInfoFileName = "pdfinfo";
|
||||
|
||||
|
|
|
|||
|
|
@ -453,4 +453,16 @@ describe("Zotero.DB", function () {
|
|||
assert.isTrue(await IOUtils.exists(bakFile));
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onConnect()", function () {
|
||||
it("should run registered callbacks after the connection is reopened", async function () {
|
||||
let count = 0;
|
||||
Zotero.DB.onConnect(async () => {
|
||||
count++;
|
||||
});
|
||||
await Zotero.DB.closeDatabase();
|
||||
await Zotero.DB.valueQueryAsync("SELECT 1");
|
||||
assert.equal(count, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -62,6 +62,16 @@ describe("Zotero.FullText", function () {
|
|||
);
|
||||
});
|
||||
|
||||
it("should still work after the DB connection is reopened", async function () {
|
||||
var item = await importFileAttachment('test.txt');
|
||||
await Zotero.DB.vacuum({ force: true });
|
||||
await Zotero.Fulltext.indexItems([item.id]);
|
||||
assert.equal(
|
||||
((await Zotero.Fulltext.getIndexedState(item))),
|
||||
Zotero.Fulltext.INDEX_STATE_INDEXED
|
||||
);
|
||||
});
|
||||
|
||||
describe("Indexing with HiddenBrowser", () => {
|
||||
it("should index attachment as its attachmentContentType when supported", async function () {
|
||||
// Firefox would normally load this as text/x-shellscript, but we detect text/plain
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue