Prevent multiple board save operation to happen simultaneously

This commit is contained in:
lovasoa 2021-02-07 19:54:14 +01:00
parent de9f9725e9
commit 70ceaac242
No known key found for this signature in database
GPG key ID: AC8DB8E033B44AB8
3 changed files with 41 additions and 15 deletions

15
package-lock.json generated
View file

@ -272,6 +272,21 @@
"tslib": "^2.0.1"
}
},
"async-mutex": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.0.tgz",
"integrity": "sha512-6VIpUM7s37EMXvnO3TvujgaS6gx4yJby13BhxovMYSap7nrbS0gJ1UzGcjD+HElNSdTz/+IlAIqj7H48N0ZlyQ==",
"requires": {
"tslib": "^2.1.0"
},
"dependencies": {
"tslib": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A=="
}
}
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",

View file

@ -9,6 +9,7 @@
"license": "AGPL-3.0-or-later",
"dependencies": {
"accept-language-parser": "^1.5.0",
"async-mutex": "^0.3.0",
"handlebars": "^4.7.6",
"polyfill-library": "^3.101.0",
"serve-static": "^1.14.1",

View file

@ -28,7 +28,8 @@
var fs = require("./fs_promises.js"),
log = require("./log.js").log,
path = require("path"),
config = require("./configuration.js");
config = require("./configuration.js"),
Mutex = require("async-mutex").Mutex;
/**
* Represents a board.
@ -48,6 +49,7 @@ class BoardData {
);
this.lastSaveDate = Date.now();
this.users = new Set();
this.saveMutex = new Mutex();
}
/** Adds data to the board
@ -125,8 +127,7 @@ class BoardData {
.map(([_, elem]) => elem);
}
/** Delays the triggering of auto-save by SAVE_INTERVAL seconds
*/
/** Delays the triggering of auto-save by SAVE_INTERVAL seconds */
delaySave() {
if (this.saveTimeoutId !== undefined) clearTimeout(this.saveTimeoutId);
this.saveTimeoutId = setTimeout(this.save.bind(this), config.SAVE_INTERVAL);
@ -134,13 +135,17 @@ class BoardData {
setTimeout(this.save.bind(this), 0);
}
/** Saves the data in the board to a file.
* @param {string} [file=this.file] - Path to the file where the board data will be saved.
*/
async save(file) {
/** Saves the data in the board to a file. */
async save() {
// The mutex prevents multiple save operation to happen simultaneously
this.saveMutex.runExclusive(this._unsafe_save.bind(this));
}
/** Save the board to disk without preventing multiple simultaneaous saves. Use save() instead */
async _unsafe_save() {
this.lastSaveDate = Date.now();
this.clean();
if (!file) file = this.file;
var file = this.file;
var tmp_file = backupFileName(file);
var board_txt = JSON.stringify(this.board);
if (board_txt === "{}") {
@ -156,7 +161,7 @@ class BoardData {
}
} else {
try {
await fs.promises.writeFile(tmp_file, board_txt);
await fs.promises.writeFile(tmp_file, board_txt, { flag: "wx" });
await fs.promises.rename(tmp_file, file);
log("saved board", {
name: this.name,
@ -227,14 +232,19 @@ class BoardData {
try {
data = await fs.promises.readFile(boardData.file);
boardData.board = JSON.parse(data);
for (id in boardData.board) boardData.validate(boardData.board[id]);
for (const id in boardData.board) boardData.validate(boardData.board[id]);
log("disk load", { board: boardData.name });
} catch (e) {
log("empty board creation", {
board: boardData.name,
// If the file doesn't exist, this is not an error
error: e.code !== "ENOENT" && e.toString(),
});
// If the file doesn't exist, this is not an error
if (e.code === "ENOENT") {
log("empty board creation", { board: boardData.name });
} else {
log("board load error", {
board: name,
error: e.toString(),
stack: e.stack,
});
}
boardData.board = {};
if (data) {
// There was an error loading the board, but some data was still read