Files
Yajbir Singh f1b860b25c
Some checks failed
check / markdownlint (push) Has been cancelled
check / spellchecker (push) Has been cancelled
updated
2025-12-11 19:03:17 +05:30

196 lines
8.3 KiB
JavaScript

/*
* (c) Copyright Ascensio System SIA 2010-2024
*
* This program is a free software product. You can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License (AGPL)
* version 3 as published by the Free Software Foundation. In accordance with
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
* that Ascensio System SIA expressly excludes the warranty of non-infringement
* of any third-party rights.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
*
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
* street, Riga, Latvia, EU, LV-1050.
*
* The interactive user interfaces in modified source and object code versions
* of the Program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU AGPL version 3.
*
* Pursuant to Section 7(b) of the License you must retain the original Product
* logo when distributing the program. Pursuant to Section 7(e) we decline to
* grant you any rights under trademark law for use of our trademarks.
*
* All the Product's GUI elements, including illustrations and icon sets, as
* well as technical writing content are licensed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
*
*/
'use strict';
const config = require('config');
const configCoAuthoring = config.get('services.CoAuthoring');
const co = require('co');
const pubsubService = require('./pubsubRabbitMQ');
const commonDefines = require('./../../Common/sources/commondefines');
const constants = require('./../../Common/sources/constants');
const utils = require('./../../Common/sources/utils');
const storage = require('./../../Common/sources/storage/storage-base');
const queueService = require('./../../Common/sources/taskqueueRabbitMQ');
const operationContext = require('./../../Common/sources/operationContext');
const sqlBase = require('./databaseConnectors/baseConnector');
const docsCoServer = require('./DocsCoServer');
const taskResult = require('./taskresult');
const cfgEditorDataStorage = config.get('services.CoAuthoring.server.editorDataStorage');
const cfgEditorStatStorage = config.get('services.CoAuthoring.server.editorStatStorage');
const editorStatStorage = require('./' + (cfgEditorStatStorage || cfgEditorDataStorage));
const cfgForgottenFiles = config.get('services.CoAuthoring.server.forgottenfiles');
const cfgTableResult = config.get('services.CoAuthoring.sql.tableResult');
const cfgRedisPrefix = configCoAuthoring.get('redis.prefix');
const redisKeyShutdown = cfgRedisPrefix + constants.REDIS_KEY_SHUTDOWN;
const WAIT_TIMEOUT = 30000;
const LOOP_TIMEOUT = 1000;
const EXEC_TIMEOUT = WAIT_TIMEOUT + utils.getConvertionTimeout(undefined);
const addSqlParam = sqlBase.addSqlParameter;
function updateDoc(ctx, docId, status, callback) {
return new Promise((resolve, reject) => {
const values = [];
const p1 = addSqlParam(status, values);
const p2 = addSqlParam(callback, values);
const p3 = addSqlParam(ctx.tenant, values);
const p4 = addSqlParam(docId, values);
const sqlCommand = `UPDATE ${cfgTableResult} SET status=${p1},callback=${p2} WHERE tenant=${p3} AND id=${p4};`;
sqlBase.sqlQuery(
ctx,
sqlCommand,
(error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
},
undefined,
undefined,
values
);
});
}
function shutdown() {
return co(function* () {
let res = true;
const ctx = new operationContext.Context();
try {
const editorStat = editorStatStorage.EditorStat ? new editorStatStorage.EditorStat() : new editorStatStorage();
ctx.logger.debug('shutdown start:' + EXEC_TIMEOUT);
//redisKeyShutdown is not a simple counter, so it doesn't get decremented by a build that started before Shutdown started
//reset redisKeyShutdown just in case the previous run didn't finish yield editorData.cleanupShutdown(redisKeyShutdown);
const queue = new queueService();
yield queue.initPromise(true, false, false, false, false, false);
const pubsub = new pubsubService();
yield pubsub.initPromise();
//inner ping to update presence
ctx.logger.debug('shutdown pubsub shutdown message');
yield pubsub.publish(JSON.stringify({type: commonDefines.c_oPublishType.shutdown, ctx, status: true}));
ctx.logger.debug('shutdown start wait pubsub deliver');
yield utils.sleep(LOOP_TIMEOUT);
const documentsWithChanges = yield sqlBase.getDocumentsWithChanges(ctx);
ctx.logger.debug('shutdown docs with changes count = %s', documentsWithChanges.length);
const docsWithEmptyForgotten = [];
const docsWithOutOfDateForgotten = [];
for (let i = 0; i < documentsWithChanges.length; ++i) {
const tenant = documentsWithChanges[i].tenant;
const docId = documentsWithChanges[i].id;
ctx.setTenant(tenant);
const forgotten = yield storage.listObjects(ctx, docId, cfgForgottenFiles);
if (forgotten.length > 0) {
const selectRes = yield taskResult.select(ctx, docId);
if (selectRes.length > 0) {
const row = selectRes[0];
if (commonDefines.FileStatus.SaveVersion !== row.status && commonDefines.FileStatus.UpdateVersion !== row.status) {
docsWithOutOfDateForgotten.push([tenant, docId]);
}
}
} else {
docsWithEmptyForgotten.push([tenant, docId]);
}
}
ctx.initDefault();
ctx.logger.debug('shutdown docs with changes and empty forgotten count = %s', docsWithEmptyForgotten.length);
ctx.logger.debug('shutdown docs with changes and out of date forgotten count = %s', docsWithOutOfDateForgotten.length);
const docsToConvert = docsWithEmptyForgotten.concat(docsWithOutOfDateForgotten);
for (let i = 0; i < docsToConvert.length; ++i) {
const tenant = docsToConvert[i][0];
const docId = docsToConvert[i][1];
//todo refactor. group tenants?
ctx.setTenant(tenant);
yield ctx.initTenantCache();
yield updateDoc(ctx, docId, commonDefines.FileStatus.Ok, '');
yield editorStat.addShutdown(redisKeyShutdown, docId);
ctx.logger.debug('shutdown createSaveTimerPromise %s', docId);
yield docsCoServer.createSaveTimer(ctx, docId, null, null, null, queue, true);
}
ctx.initDefault();
//sleep because of bugs in createSaveTimerPromise
yield utils.sleep(LOOP_TIMEOUT);
const startTime = new Date().getTime();
while (true) {
const remainingFiles = yield editorStat.getShutdownCount(redisKeyShutdown);
ctx.logger.debug('shutdown remaining files:%d', remainingFiles);
const curTime = new Date().getTime() - startTime;
if (curTime >= EXEC_TIMEOUT || remainingFiles <= 0) {
if (curTime >= EXEC_TIMEOUT) {
ctx.logger.debug('shutdown timeout');
}
break;
}
yield utils.sleep(LOOP_TIMEOUT);
}
let countInForgotten = 0;
for (let i = 0; i < docsToConvert.length; ++i) {
const tenant = docsToConvert[i][0];
const docId = docsToConvert[i][1];
ctx.setTenant(tenant);
const forgotten = yield storage.listObjects(ctx, docId, cfgForgottenFiles);
if (forgotten.length > 0) {
countInForgotten++;
} else {
ctx.logger.warn('shutdown missing in forgotten:%s', docId);
}
}
ctx.initDefault();
ctx.logger.debug('shutdown docs placed in forgotten:%d', countInForgotten);
ctx.logger.debug('shutdown docs with unknown status:%d', docsToConvert.length - countInForgotten);
//todo needs to check queues, because there may be long conversions running before Shutdown
//clean up
yield editorStat.cleanupShutdown(redisKeyShutdown);
yield pubsub.close();
yield queue.close();
ctx.logger.debug('shutdown end');
} catch (e) {
res = false;
ctx.logger.error('shutdown error:\r\n%s', e.stack);
}
process.exit(0);
return res;
});
}
exports.shutdown = shutdown;
shutdown();