mirror of
https://github.com/zadam/trilium.git
synced 2025-02-22 05:56:03 +08:00
added basic CLS support with re-entrant transactions
This commit is contained in:
parent
b10b0048f3
commit
0ec909fd7a
13 changed files with 115 additions and 24 deletions
36
package-lock.json
generated
36
package-lock.json
generated
|
@ -395,6 +395,14 @@
|
|||
"resolved": "https://registry.npmjs.org/async-each-series/-/async-each-series-1.1.0.tgz",
|
||||
"integrity": "sha1-9C/YFV048hpbjqB8KOBj7RcAsTg="
|
||||
},
|
||||
"async-hook-jl": {
|
||||
"version": "1.7.6",
|
||||
"resolved": "https://registry.npmjs.org/async-hook-jl/-/async-hook-jl-1.7.6.tgz",
|
||||
"integrity": "sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==",
|
||||
"requires": {
|
||||
"stack-chain": "1.3.7"
|
||||
}
|
||||
},
|
||||
"async-limiter": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
|
||||
|
@ -1888,6 +1896,16 @@
|
|||
"resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz",
|
||||
"integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE="
|
||||
},
|
||||
"cls-hooked": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cls-hooked/-/cls-hooked-4.2.2.tgz",
|
||||
"integrity": "sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==",
|
||||
"requires": {
|
||||
"async-hook-jl": "1.7.6",
|
||||
"emitter-listener": "1.1.1",
|
||||
"semver": "5.4.1"
|
||||
}
|
||||
},
|
||||
"co": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||
|
@ -3593,6 +3611,14 @@
|
|||
"integrity": "sha1-H71tl779crim+SHcONIkE9L2/d8=",
|
||||
"dev": true
|
||||
},
|
||||
"emitter-listener": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.1.tgz",
|
||||
"integrity": "sha1-6Lu+gkS8jg0LTvcc0UKUx/JBx+w=",
|
||||
"requires": {
|
||||
"shimmer": "1.2.0"
|
||||
}
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
|
@ -9677,6 +9703,11 @@
|
|||
"rechoir": "0.6.2"
|
||||
}
|
||||
},
|
||||
"shimmer": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz",
|
||||
"integrity": "sha512-xTCx2vohXC2EWWDqY/zb4+5Mu28D+HYNSOuFzsyRDRvI/e1ICb69afwaUwfjr+25ZXldbOLyp+iDUZHq8UnTag=="
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||
|
@ -10580,6 +10611,11 @@
|
|||
"tweetnacl": "0.14.5"
|
||||
}
|
||||
},
|
||||
"stack-chain": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz",
|
||||
"integrity": "sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU="
|
||||
},
|
||||
"stat-mode": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.2.tgz",
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"async-mutex": "^0.1.3",
|
||||
"axios": "^0.17.1",
|
||||
"body-parser": "~1.18.2",
|
||||
"cls-hooked": "^4.2.2",
|
||||
"cookie-parser": "~1.4.3",
|
||||
"debug": "~3.1.0",
|
||||
"devtron": "^1.4.0",
|
||||
|
|
12
src/app.js
12
src/app.js
|
@ -9,6 +9,7 @@ const session = require('express-session');
|
|||
const FileStore = require('session-file-store')(session);
|
||||
const os = require('os');
|
||||
const sessionSecret = require('./services/session_secret');
|
||||
const cls = require('./services/cls');
|
||||
|
||||
const app = express();
|
||||
|
||||
|
@ -23,6 +24,17 @@ app.use((req, res, next) => {
|
|||
next();
|
||||
});
|
||||
|
||||
app.use((req, res, next) => {
|
||||
cls.namespace.bindEmitter(req);
|
||||
cls.namespace.bindEmitter(res);
|
||||
|
||||
cls.init(() => {
|
||||
cls.namespace.set("Hi");
|
||||
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
app.use(bodyParser.json({limit: '50mb'}));
|
||||
app.use(bodyParser.urlencoded({extended: false}));
|
||||
app.use(cookieParser());
|
||||
|
|
|
@ -108,7 +108,11 @@ function updateNoteFromInputs(note) {
|
|||
}
|
||||
|
||||
async function saveNoteToServer(note) {
|
||||
await server.put('notes/' + note.noteId, note);
|
||||
const dto = Object.assign({}, note);
|
||||
delete dto.treeCache;
|
||||
delete dto.hideInAutocomplete;
|
||||
|
||||
await server.put('notes/' + dto.noteId, dto);
|
||||
|
||||
isNoteChanged = false;
|
||||
|
||||
|
|
|
@ -55,7 +55,9 @@ router.put('/:noteId', auth.checkApiAuth, wrap(async (req, res, next) => {
|
|||
const sourceId = req.headers.source_id;
|
||||
const dataKey = protected_session.getDataKey(req);
|
||||
|
||||
await notes.updateNote(noteId, note, dataKey, sourceId);
|
||||
await sql.doInTransaction(async () => {
|
||||
await notes.updateNote(noteId, note, dataKey, sourceId);
|
||||
});
|
||||
|
||||
res.send({});
|
||||
}));
|
||||
|
|
|
@ -7,6 +7,7 @@ const dataDir = require('./data_dir');
|
|||
const log = require('./log');
|
||||
const sql = require('./sql');
|
||||
const sync_mutex = require('./sync_mutex');
|
||||
const cls = require('./cls');
|
||||
|
||||
async function regularBackup() {
|
||||
const now = new Date();
|
||||
|
@ -64,10 +65,10 @@ if (!fs.existsSync(dataDir.BACKUP_DIR)) {
|
|||
}
|
||||
|
||||
sql.dbReady.then(() => {
|
||||
setInterval(regularBackup, 60 * 60 * 1000);
|
||||
setInterval(cls.wrap(regularBackup), 60 * 60 * 1000);
|
||||
|
||||
// kickoff backup immediately
|
||||
setTimeout(regularBackup, 1000);
|
||||
setTimeout(cls.wrap(regularBackup), 1000);
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
|
|
16
src/services/cls.js
Normal file
16
src/services/cls.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
const clsHooked = require('cls-hooked');
|
||||
const namespace = clsHooked.createNamespace("trilium");
|
||||
|
||||
async function init(callback) {
|
||||
return await namespace.runAndReturn(callback);
|
||||
}
|
||||
|
||||
function wrap(callback) {
|
||||
return async () => await init(callback);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init,
|
||||
wrap,
|
||||
namespace
|
||||
};
|
|
@ -5,6 +5,7 @@ const log = require('./log');
|
|||
const messaging = require('./messaging');
|
||||
const sync_mutex = require('./sync_mutex');
|
||||
const utils = require('./utils');
|
||||
const cls = require('./cls');
|
||||
|
||||
async function runCheck(query, errorText, errorList) {
|
||||
utils.assertArguments(query, errorText, errorList);
|
||||
|
@ -268,10 +269,10 @@ async function runChecks() {
|
|||
}
|
||||
|
||||
sql.dbReady.then(() => {
|
||||
setInterval(runChecks, 60 * 60 * 1000);
|
||||
setInterval(cls.wrap(runChecks), 60 * 60 * 1000);
|
||||
|
||||
// kickoff backup immediately
|
||||
setTimeout(runChecks, 10000);
|
||||
setTimeout(cls.wrap(runChecks), 10000);
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -262,16 +262,16 @@ async function loadFile(noteId, newNote, dataKey) {
|
|||
await protected_session.decryptNote(dataKey, oldNote);
|
||||
}
|
||||
|
||||
newNote.detail.content = oldNote.content;
|
||||
newNote.content = oldNote.content;
|
||||
}
|
||||
|
||||
async function updateNote(noteId, newNote, dataKey, sourceId) {
|
||||
if (newNote.detail.type === 'file') {
|
||||
if (newNote.type === 'file') {
|
||||
await loadFile(noteId, newNote, dataKey);
|
||||
}
|
||||
|
||||
if (newNote.detail.isProtected) {
|
||||
await protected_session.encryptNote(dataKey, newNote.detail);
|
||||
if (newNote.isProtected) {
|
||||
await protected_session.encryptNote(dataKey, newNote);
|
||||
}
|
||||
|
||||
const labelsMap = await labels.getNoteLabelMap(noteId);
|
||||
|
@ -287,7 +287,7 @@ async function updateNote(noteId, newNote, dataKey, sourceId) {
|
|||
"SELECT noteRevisionId FROM note_revisions WHERE noteId = ? AND dateModifiedTo >= ?", [noteId, revisionCutoff]);
|
||||
|
||||
await sql.doInTransaction(async () => {
|
||||
const msSinceDateCreated = now.getTime() - utils.parseDateTime(newNote.detail.dateCreated).getTime();
|
||||
const msSinceDateCreated = now.getTime() - utils.parseDateTime(newNote.dateCreated).getTime();
|
||||
|
||||
if (labelsMap.disable_versioning !== 'true'
|
||||
&& !existingnoteRevisionId
|
||||
|
@ -296,14 +296,14 @@ async function updateNote(noteId, newNote, dataKey, sourceId) {
|
|||
await saveNoteRevision(noteId, dataKey, sourceId, nowStr);
|
||||
}
|
||||
|
||||
await saveNoteImages(noteId, newNote.detail.content, sourceId);
|
||||
await saveNoteImages(noteId, newNote.content, sourceId);
|
||||
|
||||
await protectNoteRevisions(noteId, dataKey, newNote.detail.isProtected);
|
||||
await protectNoteRevisions(noteId, dataKey, newNote.isProtected);
|
||||
|
||||
await sql.execute("UPDATE notes SET title = ?, content = ?, isProtected = ?, dateModified = ? WHERE noteId = ?", [
|
||||
newNote.detail.title,
|
||||
newNote.detail.content,
|
||||
newNote.detail.isProtected,
|
||||
newNote.title,
|
||||
newNote.content,
|
||||
newNote.isProtected,
|
||||
nowStr,
|
||||
noteId]);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const script = require('./script');
|
||||
const Repository = require('./repository');
|
||||
const cls = require('./cls');
|
||||
|
||||
const repo = new Repository();
|
||||
|
||||
|
@ -20,8 +21,8 @@ async function runNotesWithLabel(runAttrValue) {
|
|||
}
|
||||
}
|
||||
|
||||
setTimeout(() => runNotesWithLabel('backend_startup'), 10 * 1000);
|
||||
setTimeout(cls.wrap(() => runNotesWithLabel('backend_startup')), 10 * 1000);
|
||||
|
||||
setInterval(() => runNotesWithLabel('hourly'), 3600 * 1000);
|
||||
setInterval(cls.wrap(() => runNotesWithLabel('hourly')), 3600 * 1000);
|
||||
|
||||
setInterval(() => runNotesWithLabel('daily'), 24 * 3600 * 1000);
|
||||
setInterval(cls.wrap(() => runNotesWithLabel('daily'), 24 * 3600 * 1000));
|
|
@ -1,6 +1,7 @@
|
|||
const utils = require('./utils');
|
||||
const log = require('./log');
|
||||
const sql = require('./sql');
|
||||
const cls = require('./cls');
|
||||
|
||||
async function saveSourceId(sourceId) {
|
||||
await sql.doInTransaction(async () => {
|
||||
|
@ -41,7 +42,7 @@ function isLocalSourceId(srcId) {
|
|||
const currentSourceId = createSourceId();
|
||||
|
||||
// this will also refresh source IDs
|
||||
sql.dbReady.then(() => saveSourceId(currentSourceId));
|
||||
sql.dbReady.then(cls.wrap(() => saveSourceId(currentSourceId)));
|
||||
|
||||
function getCurrentSourceId() {
|
||||
return currentSourceId;
|
||||
|
|
|
@ -6,6 +6,7 @@ const fs = require('fs');
|
|||
const sqlite = require('sqlite');
|
||||
const app_info = require('./app_info');
|
||||
const resource_dir = require('./resource_dir');
|
||||
const cls = require('./cls');
|
||||
|
||||
async function createConnection() {
|
||||
return await sqlite.open(dataDir.DOCUMENT_PATH, {Promise});
|
||||
|
@ -15,7 +16,7 @@ const dbConnected = createConnection();
|
|||
|
||||
let dbReadyResolve = null;
|
||||
const dbReady = new Promise((resolve, reject) => {
|
||||
dbConnected.then(async db => {
|
||||
dbConnected.then(cls.wrap(async db => {
|
||||
await execute("PRAGMA foreign_keys = ON");
|
||||
|
||||
dbReadyResolve = () => {
|
||||
|
@ -65,7 +66,7 @@ const dbReady = new Promise((resolve, reject) => {
|
|||
|
||||
resolve(db);
|
||||
}
|
||||
})
|
||||
}))
|
||||
.catch(e => {
|
||||
console.log("Error connecting to DB.", e);
|
||||
process.exit(1);
|
||||
|
@ -191,6 +192,15 @@ let transactionActive = false;
|
|||
let transactionPromise = null;
|
||||
|
||||
async function doInTransaction(func) {
|
||||
if (cls.namespace.get('isInTransaction')) {
|
||||
console.log("Transaction already active");
|
||||
|
||||
return await func();
|
||||
}
|
||||
else {
|
||||
console.log("Starting new transaction");
|
||||
}
|
||||
|
||||
while (transactionActive) {
|
||||
await transactionPromise;
|
||||
}
|
||||
|
@ -201,6 +211,8 @@ async function doInTransaction(func) {
|
|||
transactionActive = true;
|
||||
transactionPromise = new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
cls.namespace.set('isInTransaction', true);
|
||||
|
||||
await beginTransaction();
|
||||
|
||||
ret = await func();
|
||||
|
@ -219,6 +231,9 @@ async function doInTransaction(func) {
|
|||
|
||||
reject(e);
|
||||
}
|
||||
finally {
|
||||
cls.namespace.set('isInTransaction', false);
|
||||
}
|
||||
});
|
||||
|
||||
if (transactionActive) {
|
||||
|
|
|
@ -15,6 +15,7 @@ const app_info = require('./app_info');
|
|||
const messaging = require('./messaging');
|
||||
const sync_setup = require('./sync_setup');
|
||||
const sync_mutex = require('./sync_mutex');
|
||||
const cls = require('./cls');
|
||||
|
||||
let proxyToggle = true;
|
||||
let syncServerCertificate = null;
|
||||
|
@ -347,10 +348,10 @@ sql.dbReady.then(() => {
|
|||
syncServerCertificate = fs.readFileSync(sync_setup.SYNC_CERT_PATH);
|
||||
}
|
||||
|
||||
setInterval(sync, 60000);
|
||||
setInterval(cls.wrap(sync), 60000);
|
||||
|
||||
// kickoff initial sync immediately
|
||||
setTimeout(sync, 1000);
|
||||
setTimeout(cls.wrap(sync), 1000);
|
||||
}
|
||||
else {
|
||||
log.info("Sync server not configured, sync timer not running.")
|
||||
|
|
Loading…
Reference in a new issue