mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-23 08:56:17 -04:00
resolve conflict
This commit is contained in:
commit
96520a3f31
13 changed files with 458 additions and 394 deletions
|
@ -7,12 +7,14 @@
|
|||
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
|
||||
process.on('unhandledRejection', (err) => { throw err; });
|
||||
|
||||
const npm = require('ep_etherpad-lite/node_modules/npm');
|
||||
const util = require('util');
|
||||
|
||||
if (process.argv.length !== 2) throw new Error('Use: node bin/checkAllPads.js');
|
||||
|
||||
// load and initialize NPM
|
||||
const npm = require('ep_etherpad-lite/node_modules/npm');
|
||||
npm.load({}, async () => {
|
||||
try {
|
||||
(async () => {
|
||||
await util.promisify(npm.load)({});
|
||||
|
||||
// initialize the database
|
||||
require('ep_etherpad-lite/node/utils/Settings');
|
||||
const db = require('ep_etherpad-lite/node/db/DB');
|
||||
|
@ -30,7 +32,7 @@ npm.load({}, async () => {
|
|||
const pad = await padManager.getPad(padId);
|
||||
|
||||
// check if the pad has a pool
|
||||
if (pad.pool === undefined) {
|
||||
if (pad.pool == null) {
|
||||
console.error(`[${pad.id}] Missing attribute pool`);
|
||||
continue;
|
||||
}
|
||||
|
@ -66,13 +68,13 @@ npm.load({}, async () => {
|
|||
}
|
||||
|
||||
// check if there is a atext in the keyRevisions
|
||||
if (revisions[keyRev].meta === undefined || revisions[keyRev].meta.atext === undefined) {
|
||||
let {meta: {atext} = {}} = revisions[keyRev];
|
||||
if (atext == null) {
|
||||
console.error(`[${pad.id}] Missing atext in revision ${keyRev}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const apool = pad.pool;
|
||||
let atext = revisions[keyRev].meta.atext;
|
||||
for (let rev = keyRev + 1; rev <= keyRev + 100 && rev <= head; rev++) {
|
||||
try {
|
||||
const cs = revisions[rev].changeset;
|
||||
|
@ -88,8 +90,4 @@ npm.load({}, async () => {
|
|||
throw new Error('No revisions tested');
|
||||
}
|
||||
console.log(`Finished: Tested ${revTestedCount} revisions`);
|
||||
} catch (err) {
|
||||
console.trace(err);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -7,16 +7,18 @@
|
|||
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
|
||||
process.on('unhandledRejection', (err) => { throw err; });
|
||||
|
||||
const npm = require('ep_etherpad-lite/node_modules/npm');
|
||||
const util = require('util');
|
||||
|
||||
if (process.argv.length !== 3) throw new Error('Use: node bin/checkPad.js $PADID');
|
||||
|
||||
// get the padID
|
||||
const padId = process.argv[2];
|
||||
let checkRevisionCount = 0;
|
||||
|
||||
// load and initialize NPM;
|
||||
const npm = require('ep_etherpad-lite/node_modules/npm');
|
||||
npm.load({}, async () => {
|
||||
try {
|
||||
(async () => {
|
||||
await util.promisify(npm.load)({});
|
||||
|
||||
// initialize database
|
||||
require('ep_etherpad-lite/node/utils/Settings');
|
||||
const db = require('ep_etherpad-lite/node/db/DB');
|
||||
|
@ -59,18 +61,16 @@ npm.load({}, async () => {
|
|||
}
|
||||
|
||||
// check if the pad has a pool
|
||||
if (pad.pool === undefined) throw new Error('Attribute pool is missing');
|
||||
if (pad.pool == null) throw new Error('Attribute pool is missing');
|
||||
|
||||
// check if there is an atext in the keyRevisions
|
||||
if (revisions[keyRev] === undefined ||
|
||||
revisions[keyRev].meta === undefined ||
|
||||
revisions[keyRev].meta.atext === undefined) {
|
||||
let {meta: {atext} = {}} = revisions[keyRev] || {};
|
||||
if (atext == null) {
|
||||
console.error(`No atext in key revision ${keyRev}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const apool = pad.pool;
|
||||
let atext = revisions[keyRev].meta.atext;
|
||||
|
||||
for (let rev = keyRev + 1; rev <= keyRev + 100 && rev <= head; rev++) {
|
||||
checkRevisionCount++;
|
||||
|
@ -84,8 +84,4 @@ npm.load({}, async () => {
|
|||
}
|
||||
console.log(`Finished: Checked ${checkRevisionCount} revisions`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.trace(err);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -12,12 +12,14 @@ if (process.argv.length !== 3) throw new Error('Use: node bin/checkPadDeltas.js
|
|||
// get the padID
|
||||
const padId = process.argv[2];
|
||||
|
||||
// load and initialize NPM;
|
||||
const expect = require('../tests/frontend/lib/expect');
|
||||
const diff = require('ep_etherpad-lite/node_modules/diff');
|
||||
const npm = require('ep_etherpad-lite/node_modules/npm');
|
||||
const util = require('util');
|
||||
|
||||
(async () => {
|
||||
await util.promisify(npm.load)({});
|
||||
|
||||
npm.load({}, async () => {
|
||||
// initialize database
|
||||
require('ep_etherpad-lite/node/utils/Settings');
|
||||
const db = require('ep_etherpad-lite/node/db/DB');
|
||||
|
@ -54,10 +56,8 @@ npm.load({}, async () => {
|
|||
// console.log('Fetching', revNum)
|
||||
const revision = await db.get(`pad:${padId}:revs:${revNum}`);
|
||||
// check if there is a atext in the keyRevisions
|
||||
if (~keyRevisions.indexOf(revNum) &&
|
||||
(revision === undefined ||
|
||||
revision.meta === undefined ||
|
||||
revision.meta.atext === undefined)) {
|
||||
const {meta: {atext: revAtext} = {}} = revision || {};
|
||||
if (~keyRevisions.indexOf(revNum) && revAtext == null) {
|
||||
console.error(`No atext in key revision ${revNum}`);
|
||||
continue;
|
||||
}
|
||||
|
@ -104,4 +104,4 @@ npm.load({}, async () => {
|
|||
}
|
||||
}));
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -16,11 +16,11 @@ if (process.argv.length !== 3) throw new Error('Use: node extractPadData.js $PAD
|
|||
const padId = process.argv[2];
|
||||
|
||||
const npm = require('ep_etherpad-lite/node_modules/npm');
|
||||
const util = require('util');
|
||||
|
||||
npm.load({}, async (err) => {
|
||||
if (err) throw err;
|
||||
(async () => {
|
||||
await util.promisify(npm.load)({});
|
||||
|
||||
try {
|
||||
// initialize database
|
||||
require('ep_etherpad-lite/node/utils/Settings');
|
||||
const db = require('ep_etherpad-lite/node/db/DB');
|
||||
|
@ -29,7 +29,6 @@ npm.load({}, async (err) => {
|
|||
// load extra modules
|
||||
const dirtyDB = require('ep_etherpad-lite/node_modules/dirty');
|
||||
const padManager = require('ep_etherpad-lite/node/db/PadManager');
|
||||
const util = require('util');
|
||||
|
||||
// initialize output database
|
||||
const dirty = dirtyDB(`${padId}.db`);
|
||||
|
@ -67,8 +66,4 @@ npm.load({}, async (err) => {
|
|||
}
|
||||
|
||||
console.log('finished');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
|
||||
process.on('unhandledRejection', (err) => { throw err; });
|
||||
|
||||
const npm = require('ep_etherpad-lite/node_modules/npm');
|
||||
const util = require('util');
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
const log = (str) => {
|
||||
|
@ -43,10 +46,10 @@ const unescape = (val) => {
|
|||
return val;
|
||||
};
|
||||
|
||||
(async () => {
|
||||
await util.promisify(npm.load)({});
|
||||
|
||||
require('ep_etherpad-lite/node_modules/npm').load({}, (er, npm) => {
|
||||
const fs = require('fs');
|
||||
|
||||
const ueberDB = require('ep_etherpad-lite/node_modules/ueberdb2');
|
||||
const settings = require('ep_etherpad-lite/node/utils/Settings');
|
||||
const log4js = require('ep_etherpad-lite/node_modules/log4js');
|
||||
|
@ -68,11 +71,7 @@ require('ep_etherpad-lite/node_modules/npm').load({}, (er, npm) => {
|
|||
if (!sqlFile) throw new Error('Use: node importSqlFile.js $SQLFILE');
|
||||
|
||||
log('initializing db');
|
||||
db.init((err) => {
|
||||
// there was an error while initializing the database, output it and stop
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
await util.promisify(db.init.bind(db))();
|
||||
log('done');
|
||||
|
||||
log('open output file...');
|
||||
|
@ -101,9 +100,6 @@ require('ep_etherpad-lite/node_modules/npm').load({}, (er, npm) => {
|
|||
process.stdout.write('done. waiting for db to finish transaction. ' +
|
||||
'depended on dbms this may take some time..\n');
|
||||
|
||||
db.close(() => {
|
||||
await util.promisify(db.close.bind(db))();
|
||||
log(`finished, imported ${keyNo} keys.`);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -4,9 +4,12 @@
|
|||
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
|
||||
process.on('unhandledRejection', (err) => { throw err; });
|
||||
|
||||
const npm = require('ep_etherpad-lite/node_modules/npm');
|
||||
const util = require('util');
|
||||
|
||||
require('ep_etherpad-lite/node_modules/npm').load({}, async (er, npm) => {
|
||||
(async () => {
|
||||
await util.promisify(npm.load)({});
|
||||
|
||||
process.chdir(`${npm.root}/..`);
|
||||
|
||||
// This script requires that you have modified your settings.json file
|
||||
|
@ -56,4 +59,4 @@ require('ep_etherpad-lite/node_modules/npm').load({}, async (er, npm) => {
|
|||
|
||||
await util.promisify(db.close.bind(db))();
|
||||
console.log('Finished.');
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -13,7 +13,6 @@ if (process.argv.length !== 4 && process.argv.length !== 5) {
|
|||
throw new Error('Use: node bin/repairPad.js $PADID $REV [$NEWPADID]');
|
||||
}
|
||||
|
||||
const async = require('ep_etherpad-lite/node_modules/async');
|
||||
const npm = require('ep_etherpad-lite/node_modules/npm');
|
||||
const util = require('util');
|
||||
|
||||
|
@ -21,50 +20,33 @@ const padId = process.argv[2];
|
|||
const newRevHead = process.argv[3];
|
||||
const newPadId = process.argv[4] || `${padId}-rebuilt`;
|
||||
|
||||
let db, oldPad, newPad;
|
||||
let Pad, PadManager;
|
||||
(async () => {
|
||||
await util.promisify(npm.load)({});
|
||||
|
||||
async.series([
|
||||
(callback) => npm.load({}, callback),
|
||||
(callback) => {
|
||||
// Get a handle into the database
|
||||
db = require('ep_etherpad-lite/node/db/DB');
|
||||
db.init(callback);
|
||||
},
|
||||
(callback) => {
|
||||
Pad = require('ep_etherpad-lite/node/db/Pad').Pad;
|
||||
PadManager = require('ep_etherpad-lite/node/db/PadManager');
|
||||
// Get references to the original pad and to a newly created pad
|
||||
// HACK: This is a standalone script, so we want to write everything
|
||||
// out to the database immediately. The only problem with this is
|
||||
// that a driver (like the mysql driver) can hardcode these values.
|
||||
db.db.db.settings = {cache: 0, writeInterval: 0, json: true};
|
||||
const db = require('ep_etherpad-lite/node/db/DB');
|
||||
await db.init();
|
||||
|
||||
const PadManager = require('ep_etherpad-lite/node/db/PadManager');
|
||||
const Pad = require('ep_etherpad-lite/node/db/Pad').Pad;
|
||||
// Validate the newPadId if specified and that a pad with that ID does
|
||||
// not already exist to avoid overwriting it.
|
||||
if (!PadManager.isValidPadId(newPadId)) {
|
||||
throw new Error('Cannot create a pad with that id as it is invalid');
|
||||
}
|
||||
PadManager.doesPadExists(newPadId, (err, exists) => {
|
||||
const exists = await PadManager.doesPadExist(newPadId);
|
||||
if (exists) throw new Error('Cannot create a pad with that id as it already exists');
|
||||
});
|
||||
PadManager.getPad(padId, (err, pad) => {
|
||||
oldPad = pad;
|
||||
newPad = new Pad(newPadId);
|
||||
callback();
|
||||
});
|
||||
},
|
||||
(callback) => {
|
||||
|
||||
const oldPad = await PadManager.getPad(padId);
|
||||
const newPad = new Pad(newPadId);
|
||||
|
||||
// Clone all Chat revisions
|
||||
const chatHead = oldPad.chatHead;
|
||||
for (let i = 0, curHeadNum = 0; i <= chatHead; i++) {
|
||||
db.db.get(`pad:${padId}:chat:${i}`, (err, chat) => {
|
||||
db.db.set(`pad:${newPadId}:chat:${curHeadNum++}`, chat);
|
||||
console.log(`Created: Chat Revision: pad:${newPadId}:chat:${curHeadNum}`);
|
||||
});
|
||||
}
|
||||
callback();
|
||||
},
|
||||
(callback) => {
|
||||
await Promise.all([...Array(chatHead + 1).keys()].map(async (i) => {
|
||||
const chat = await db.get(`pad:${padId}:chat:${i}`);
|
||||
await db.set(`pad:${newPadId}:chat:${i}`, chat);
|
||||
console.log(`Created: Chat Revision: pad:${newPadId}:chat:${i}`);
|
||||
}));
|
||||
|
||||
// Rebuild Pad from revisions up to and including the new revision head
|
||||
const AuthorManager = require('ep_etherpad-lite/node/db/AuthorManager');
|
||||
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
|
||||
|
@ -73,23 +55,18 @@ async.series([
|
|||
// and, AFAICT, cannot be recreated any other way
|
||||
newPad.pool.numToAttrib = oldPad.pool.numToAttrib;
|
||||
for (let curRevNum = 0; curRevNum <= newRevHead; curRevNum++) {
|
||||
db.db.get(`pad:${padId}:revs:${curRevNum}`, (err, rev) => {
|
||||
if (rev.meta) {
|
||||
throw new Error('The specified revision number could not be found.');
|
||||
}
|
||||
const rev = await db.get(`pad:${padId}:revs:${curRevNum}`);
|
||||
if (!rev || !rev.meta) throw new Error('The specified revision number could not be found.');
|
||||
const newRevNum = ++newPad.head;
|
||||
const newRevId = `pad:${newPad.id}:revs:${newRevNum}`;
|
||||
db.db.set(newRevId, rev);
|
||||
AuthorManager.addPad(rev.meta.author, newPad.id);
|
||||
await Promise.all([
|
||||
db.set(newRevId, rev),
|
||||
AuthorManager.addPad(rev.meta.author, newPad.id),
|
||||
]);
|
||||
newPad.atext = Changeset.applyToAText(rev.changeset, newPad.atext, newPad.pool);
|
||||
console.log(`Created: Revision: pad:${newPad.id}:revs:${newRevNum}`);
|
||||
if (newRevNum === newRevHead) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
(callback) => {
|
||||
|
||||
// Add saved revisions up to the new revision head
|
||||
console.log(newPad.head);
|
||||
const newSavedRevisions = [];
|
||||
|
@ -100,16 +77,13 @@ async.series([
|
|||
}
|
||||
}
|
||||
newPad.savedRevisions = newSavedRevisions;
|
||||
callback();
|
||||
},
|
||||
(callback) => {
|
||||
|
||||
// Save the source pad
|
||||
db.db.set(`pad:${newPadId}`, newPad, (err) => {
|
||||
await db.set(`pad:${newPadId}`, newPad);
|
||||
|
||||
console.log(`Created: Source Pad: pad:${newPadId}`);
|
||||
util.callbackify(newPad.saveToDatabase.bind(newPad))(callback);
|
||||
});
|
||||
},
|
||||
], (err) => {
|
||||
if (err) throw err;
|
||||
await newPad.saveToDatabase();
|
||||
|
||||
await db.shutdown();
|
||||
console.info('finished');
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -18,8 +18,10 @@ const padId = process.argv[2];
|
|||
let valueCount = 0;
|
||||
|
||||
const npm = require('ep_etherpad-lite/node_modules/npm');
|
||||
npm.load({}, async (err) => {
|
||||
if (err) throw err;
|
||||
const util = require('util');
|
||||
|
||||
(async () => {
|
||||
await util.promisify(npm.load)({});
|
||||
|
||||
// intialize database
|
||||
require('ep_etherpad-lite/node/utils/Settings');
|
||||
|
@ -56,4 +58,4 @@ npm.load({}, async (err) => {
|
|||
}
|
||||
|
||||
console.info(`Finished: Replaced ${valueCount} values in the database`);
|
||||
});
|
||||
})();
|
||||
|
|
8
package-lock.json
generated
8
package-lock.json
generated
|
@ -870,7 +870,8 @@
|
|||
"tinycon": "0.0.1",
|
||||
"ueberdb2": "^1.2.5",
|
||||
"underscore": "1.8.3",
|
||||
"unorm": "1.4.1"
|
||||
"unorm": "1.4.1",
|
||||
"wtfnode": "^0.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apidevtools/json-schema-ref-parser": {
|
||||
|
@ -10577,6 +10578,11 @@
|
|||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
|
||||
},
|
||||
"wtfnode": {
|
||||
"version": "0.8.4",
|
||||
"resolved": "https://registry.npmjs.org/wtfnode/-/wtfnode-0.8.4.tgz",
|
||||
"integrity": "sha512-64GEKtMt/MUBuAm+8kHqP74ojjafzu00aT0JKsmkIwYmjRQ/odO0yhbzKLm+Z9v1gMla+8dwITRKzTAlHsB+Og=="
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
|
|
|
@ -27,6 +27,10 @@
|
|||
const log4js = require('log4js');
|
||||
log4js.replaceConsole();
|
||||
|
||||
// wtfnode should be loaded after log4js.replaceConsole() so that it uses log4js for logging, and it
|
||||
// should be above everything else so that it can hook in before resources are used.
|
||||
const wtfnode = require('wtfnode');
|
||||
|
||||
/*
|
||||
* early check for version compatibility before calling
|
||||
* any modules that require newer versions of NodeJS
|
||||
|
@ -44,31 +48,81 @@ const plugins = require('../static/js/pluginfw/plugins');
|
|||
const settings = require('./utils/Settings');
|
||||
const util = require('util');
|
||||
|
||||
let started = false;
|
||||
let stopped = false;
|
||||
const State = {
|
||||
INITIAL: 1,
|
||||
STARTING: 2,
|
||||
RUNNING: 3,
|
||||
STOPPING: 4,
|
||||
STOPPED: 5,
|
||||
EXITING: 6,
|
||||
WAITING_FOR_EXIT: 7,
|
||||
};
|
||||
|
||||
let state = State.INITIAL;
|
||||
|
||||
const removeSignalListener = (signal, listener) => {
|
||||
console.debug(`Removing ${signal} listener because it might interfere with shutdown tasks. ` +
|
||||
`Function code:\n${listener.toString()}\n` +
|
||||
`Current stack:\n${(new Error()).stack.split('\n').slice(1).join('\n')}`);
|
||||
process.off(signal, listener);
|
||||
};
|
||||
|
||||
const runningCallbacks = [];
|
||||
exports.start = async () => {
|
||||
if (started) return express.server;
|
||||
started = true;
|
||||
if (stopped) throw new Error('restart not supported');
|
||||
switch (state) {
|
||||
case State.INITIAL:
|
||||
break;
|
||||
case State.STARTING:
|
||||
await new Promise((resolve) => runningCallbacks.push(resolve));
|
||||
// fall through
|
||||
case State.RUNNING:
|
||||
return express.server;
|
||||
case State.STOPPING:
|
||||
case State.STOPPED:
|
||||
case State.EXITING:
|
||||
case State.WAITING_FOR_EXIT:
|
||||
throw new Error('restart not supported');
|
||||
default:
|
||||
throw new Error(`unknown State: ${state.toString()}`);
|
||||
}
|
||||
console.log('Starting Etherpad...');
|
||||
state = State.STARTING;
|
||||
|
||||
// Check if Etherpad version is up-to-date
|
||||
UpdateCheck.check();
|
||||
|
||||
// start up stats counting system
|
||||
const stats = require('./stats');
|
||||
let startDurations = {};
|
||||
const startDurations = {};
|
||||
stats.gauge('startDurations', () => startDurations);
|
||||
stats.gauge('memoryUsage', () => process.memoryUsage().rss);
|
||||
stats.gauge('memoryUsageHeap', () => process.memoryUsage().heapUsed);
|
||||
|
||||
// Performance stats gauges
|
||||
// We use gauges because a reload might replace the value
|
||||
process.on('uncaughtException', exports.exit);
|
||||
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
|
||||
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
|
||||
process.on('unhandledRejection', (err) => { throw err; });
|
||||
|
||||
for (const signal of ['SIGINT', 'SIGTERM']) {
|
||||
// Forcibly remove other signal listeners to prevent them from terminating node before we are
|
||||
// done cleaning up. See https://github.com/andywer/threads.js/pull/329 for an example of a
|
||||
// problematic listener. This means that exports.exit is solely responsible for performing all
|
||||
// necessary cleanup tasks.
|
||||
for (const listener of process.listeners(signal)) {
|
||||
removeSignalListener(signal, listener);
|
||||
}
|
||||
process.on(signal, exports.exit);
|
||||
// Prevent signal listeners from being added in the future.
|
||||
process.on('newListener', (event, listener) => {
|
||||
if (event !== signal) return;
|
||||
removeSignalListener(signal, listener);
|
||||
});
|
||||
}
|
||||
|
||||
const preNpmLoad = Date.now();
|
||||
await util.promisify(npm.load)();
|
||||
startDurations.npmLoad = Date.now() - preNpmLoad;
|
||||
|
||||
try {
|
||||
const preDbInit = Date.now();
|
||||
await db.init();
|
||||
startDurations.dbInit = Date.now() - preDbInit;
|
||||
|
@ -85,70 +139,104 @@ exports.start = async () => {
|
|||
await hooks.aCallAll('loadSettings', {settings});
|
||||
startDurations.loadSettings = Date.now() - preLoadSettings;
|
||||
|
||||
const preCreateServer = Date.now();
|
||||
await hooks.aCallAll('createServer');
|
||||
startDurations.createSettings = Date.now() - preCreateServer;
|
||||
} catch (e) {
|
||||
console.error(`exception thrown: ${e.message}`);
|
||||
if (e.stack) console.log(e.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.on('uncaughtException', exports.exit);
|
||||
|
||||
/*
|
||||
* Connect graceful shutdown with sigint and uncaught exception
|
||||
*
|
||||
* Until Etherpad 1.7.5, process.on('SIGTERM') and process.on('SIGINT') were
|
||||
* not hooked up under Windows, because old nodejs versions did not support
|
||||
* them.
|
||||
*
|
||||
* According to nodejs 6.x documentation, it is now safe to do so. This
|
||||
* allows to gracefully close the DB connection when hitting CTRL+C under
|
||||
* Windows, for example.
|
||||
*
|
||||
* Source: https://nodejs.org/docs/latest-v6.x/api/process.html#process_signal_events
|
||||
*
|
||||
* - SIGTERM is not supported on Windows, it can be listened on.
|
||||
* - SIGINT from the terminal is supported on all platforms, and can usually
|
||||
* be generated with <Ctrl>+C (though this may be configurable). It is not
|
||||
* generated when terminal raw mode is enabled.
|
||||
*/
|
||||
process.on('SIGINT', exports.exit);
|
||||
|
||||
// When running as PID1 (e.g. in docker container) allow graceful shutdown on SIGTERM c.f. #3265.
|
||||
// Pass undefined to exports.exit because this is not an abnormal termination.
|
||||
process.on('SIGTERM', () => exports.exit());
|
||||
console.log('Etherpad is running');
|
||||
state = State.RUNNING;
|
||||
while (runningCallbacks.length > 0) setImmediate(runningCallbacks.pop());
|
||||
|
||||
// Return the HTTP server to make it easier to write tests.
|
||||
return express.server;
|
||||
};
|
||||
|
||||
const stoppedCallbacks = [];
|
||||
exports.stop = async () => {
|
||||
if (stopped) return;
|
||||
stopped = true;
|
||||
switch (state) {
|
||||
case State.STARTING:
|
||||
await exports.start();
|
||||
// Don't fall through to State.RUNNING in case another caller is also waiting for startup.
|
||||
return await exports.stop();
|
||||
case State.RUNNING:
|
||||
break;
|
||||
case State.STOPPING:
|
||||
await new Promise((resolve) => stoppedCallbacks.push(resolve));
|
||||
// fall through
|
||||
case State.INITIAL:
|
||||
case State.STOPPED:
|
||||
case State.EXITING:
|
||||
case State.WAITING_FOR_EXIT:
|
||||
return;
|
||||
default:
|
||||
throw new Error(`unknown State: ${state.toString()}`);
|
||||
}
|
||||
console.log('Stopping Etherpad...');
|
||||
await new Promise(async (resolve, reject) => {
|
||||
const id = setTimeout(() => reject(new Error('Timed out waiting for shutdown tasks')), 3000);
|
||||
await hooks.aCallAll('shutdown');
|
||||
clearTimeout(id);
|
||||
resolve();
|
||||
});
|
||||
state = State.STOPPING;
|
||||
let timeout = null;
|
||||
await Promise.race([
|
||||
hooks.aCallAll('shutdown'),
|
||||
new Promise((resolve, reject) => {
|
||||
timeout = setTimeout(() => reject(new Error('Timed out waiting for shutdown tasks')), 3000);
|
||||
}),
|
||||
]);
|
||||
clearTimeout(timeout);
|
||||
console.log('Etherpad stopped');
|
||||
state = State.STOPPED;
|
||||
while (stoppedCallbacks.length > 0) setImmediate(stoppedCallbacks.pop());
|
||||
};
|
||||
|
||||
exports.exit = async (err) => {
|
||||
let exitCode = 0;
|
||||
if (err) {
|
||||
exitCode = 1;
|
||||
console.error(err.stack ? err.stack : err);
|
||||
const exitCallbacks = [];
|
||||
let exitCalled = false;
|
||||
exports.exit = async (err = null) => {
|
||||
/* eslint-disable no-process-exit */
|
||||
if (err === 'SIGTERM') {
|
||||
// Termination from SIGTERM is not treated as an abnormal termination.
|
||||
console.log('Received SIGTERM signal');
|
||||
err = null;
|
||||
} else if (err != null) {
|
||||
console.error(err.stack || err.toString());
|
||||
process.exitCode = 1;
|
||||
if (exitCalled) {
|
||||
console.error('Error occurred while waiting to exit. Forcing an immediate unclean exit...');
|
||||
process.exit(1);
|
||||
}
|
||||
try {
|
||||
}
|
||||
exitCalled = true;
|
||||
switch (state) {
|
||||
case State.STARTING:
|
||||
case State.RUNNING:
|
||||
case State.STOPPING:
|
||||
await exports.stop();
|
||||
} catch (err) {
|
||||
exitCode = 1;
|
||||
console.error(err.stack ? err.stack : err);
|
||||
// Don't fall through to State.STOPPED in case another caller is also waiting for stop().
|
||||
// Don't pass err to exports.exit() because this err has already been processed. (If err is
|
||||
// passed again to exit() then exit() will think that a second error occurred while exiting.)
|
||||
return await exports.exit();
|
||||
case State.INITIAL:
|
||||
case State.STOPPED:
|
||||
break;
|
||||
case State.EXITING:
|
||||
await new Promise((resolve) => exitCallbacks.push(resolve));
|
||||
// fall through
|
||||
case State.WAITING_FOR_EXIT:
|
||||
return;
|
||||
default:
|
||||
throw new Error(`unknown State: ${state.toString()}`);
|
||||
}
|
||||
process.exit(exitCode);
|
||||
console.log('Exiting...');
|
||||
state = State.EXITING;
|
||||
while (exitCallbacks.length > 0) setImmediate(exitCallbacks.pop());
|
||||
// Node.js should exit on its own without further action. Add a timeout to force Node.js to exit
|
||||
// just in case something failed to get cleaned up during the shutdown hook. unref() is called on
|
||||
// the timeout so that the timeout itself does not prevent Node.js from exiting.
|
||||
setTimeout(() => {
|
||||
console.error('Something that should have been cleaned up during the shutdown hook (such as ' +
|
||||
'a timer, worker thread, or open connection) is preventing Node.js from exiting');
|
||||
wtfnode.dump();
|
||||
console.error('Forcing an unclean exit...');
|
||||
process.exit(1);
|
||||
}, 5000).unref();
|
||||
console.log('Waiting for Node.js to exit...');
|
||||
state = State.WAITING_FOR_EXIT;
|
||||
/* eslint-enable no-process-exit */
|
||||
};
|
||||
|
||||
if (require.main === module) exports.start();
|
||||
|
|
5
src/package-lock.json
generated
5
src/package-lock.json
generated
|
@ -8697,6 +8697,11 @@
|
|||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
|
||||
"integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA=="
|
||||
},
|
||||
"wtfnode": {
|
||||
"version": "0.8.4",
|
||||
"resolved": "https://registry.npmjs.org/wtfnode/-/wtfnode-0.8.4.tgz",
|
||||
"integrity": "sha512-64GEKtMt/MUBuAm+8kHqP74ojjafzu00aT0JKsmkIwYmjRQ/odO0yhbzKLm+Z9v1gMla+8dwITRKzTAlHsB+Og=="
|
||||
},
|
||||
"xml2js": {
|
||||
"version": "0.4.23",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
||||
|
|
|
@ -72,7 +72,8 @@
|
|||
"tinycon": "0.0.1",
|
||||
"ueberdb2": "^1.2.5",
|
||||
"underscore": "1.8.3",
|
||||
"unorm": "1.4.1"
|
||||
"unorm": "1.4.1",
|
||||
"wtfnode": "^0.8.4"
|
||||
},
|
||||
"bin": {
|
||||
"etherpad-lite": "node/server.js"
|
||||
|
|
|
@ -50,10 +50,10 @@ exports.init = async function () {
|
|||
|
||||
after(async function () {
|
||||
webaccess.authnFailureDelayMs = backups.authnFailureDelayMs;
|
||||
await server.stop();
|
||||
// Note: This does not unset settings that were added.
|
||||
Object.assign(settings, backups.settings);
|
||||
log4js.setGlobalLogLevel(logLevel);
|
||||
await server.exit();
|
||||
});
|
||||
|
||||
return exports.agent;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue