mirror of
https://github.com/ONLYOFFICE/server.git
synced 2026-04-07 14:04:35 +08:00
Merge pull request 'feature/runtime-config-3' (#46) from feature/runtime-config-3 into develop
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/server/pulls/46
This commit is contained in:
98
Common/sources/moduleReloader.js
Normal file
98
Common/sources/moduleReloader.js
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* (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 fs = require('fs');
|
||||
|
||||
/**
|
||||
* Reloads an NPM module by clearing it from require.cache and re-requiring it
|
||||
* @param {string} moduleName - Name of the module to reload
|
||||
* @returns {Object} The freshly loaded module
|
||||
*/
|
||||
function reloadNpmModule(moduleName) {
|
||||
try {
|
||||
const moduleId = require.resolve(moduleName);
|
||||
delete require.cache[moduleId];
|
||||
return require(moduleName);
|
||||
} catch (error) {
|
||||
console.error(`Failed to reload module ${moduleName}:`, error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requires config module with runtime configuration support
|
||||
* @param {Object} opt_additionalConfig - Additional configuration to merge
|
||||
* @returns {Object} config module
|
||||
*/
|
||||
function requireConfigWithRuntime(opt_additionalConfig) {
|
||||
let config = require('config');
|
||||
try {
|
||||
const configFilePath = config.get('runtimeConfig.filePath');
|
||||
if (configFilePath) {
|
||||
// Update NODE_CONFIG with runtime configuration
|
||||
if (configFilePath) {
|
||||
const configData = fs.readFileSync(configFilePath, 'utf8');
|
||||
|
||||
let curNodeConfig;
|
||||
if (process.env['NODE_CONFIG']) {
|
||||
curNodeConfig = JSON.parse(process.env['NODE_CONFIG']);
|
||||
} else {
|
||||
curNodeConfig = {};
|
||||
}
|
||||
|
||||
const fileConfig = JSON.parse(configData);
|
||||
|
||||
// Merge configurations: NODE_CONFIG -> runtime -> additional
|
||||
curNodeConfig = config.util.extendDeep(curNodeConfig, fileConfig);
|
||||
if (opt_additionalConfig) {
|
||||
curNodeConfig = config.util.extendDeep(curNodeConfig, opt_additionalConfig);
|
||||
}
|
||||
|
||||
process.env['NODE_CONFIG'] = JSON.stringify(curNodeConfig);
|
||||
}
|
||||
|
||||
config = reloadNpmModule('config');
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
console.error('Failed to load runtime config: %s', err.stack);
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
reloadNpmModule,
|
||||
requireConfigWithRuntime
|
||||
};
|
||||
@ -39,10 +39,6 @@ var ms = require('ms');
|
||||
var taskResult = require('./taskresult');
|
||||
var docsCoServer = require('./DocsCoServer');
|
||||
var canvasService = require('./canvasservice');
|
||||
var storage = require('./../../Common/sources/storage/storage-base');
|
||||
var utils = require('./../../Common/sources/utils');
|
||||
var logger = require('./../../Common/sources/logger');
|
||||
var constants = require('./../../Common/sources/constants');
|
||||
var commondefines = require('./../../Common/sources/commondefines');
|
||||
var queueService = require('./../../Common/sources/taskqueueRabbitMQ');
|
||||
var operationContext = require('./../../Common/sources/operationContext');
|
||||
@ -53,7 +49,7 @@ var cfgExpFilesCron = config.get('services.CoAuthoring.expire.filesCron');
|
||||
var cfgExpDocumentsCron = config.get('services.CoAuthoring.expire.documentsCron');
|
||||
var cfgExpFiles = config.get('services.CoAuthoring.expire.files');
|
||||
var cfgExpFilesRemovedAtOnce = config.get('services.CoAuthoring.expire.filesremovedatonce');
|
||||
var cfgForceSaveStep = ms(config.get('services.CoAuthoring.autoAssembly.step'));
|
||||
var cfgForceSaveStep = config.get('services.CoAuthoring.autoAssembly.step');
|
||||
|
||||
function getCronStep(cronTime){
|
||||
let cronJob = new cron.CronJob(cronTime, function(){});
|
||||
@ -66,21 +62,41 @@ let expDocumentsStep = getCronStep(cfgExpDocumentsCron);
|
||||
var checkFileExpire = function(expireSeconds) {
|
||||
return co(function* () {
|
||||
let ctx = new operationContext.Context();
|
||||
let currentExpFilesStep = expFilesStep;
|
||||
try {
|
||||
ctx.logger.info('checkFileExpire start');
|
||||
yield ctx.initTenantCache();
|
||||
const expFiles = ctx.getCfg('services.CoAuthoring.expire.files', cfgExpFiles);
|
||||
const expFilesRemovedAtOnce = ctx.getCfg('services.CoAuthoring.expire.filesremovedatonce', cfgExpFilesRemovedAtOnce);
|
||||
const currentFilesCron = ctx.getCfg('services.CoAuthoring.expire.filesCron', cfgExpFilesCron);
|
||||
currentExpFilesStep = getCronStep(currentFilesCron);
|
||||
|
||||
let removedCount = 0;
|
||||
var expired;
|
||||
var currentRemovedCount;
|
||||
do {
|
||||
currentRemovedCount = 0;
|
||||
expired = yield taskResult.getExpired(ctx, cfgExpFilesRemovedAtOnce, expireSeconds ?? cfgExpFiles);
|
||||
expired = yield taskResult.getExpired(ctx, expFilesRemovedAtOnce, expireSeconds ?? expFiles);
|
||||
|
||||
expired.sort((a, b) => a.tenant.localeCompare(b.tenant));
|
||||
let currentTenant = null;
|
||||
|
||||
for (var i = 0; i < expired.length; ++i) {
|
||||
let tenant = expired[i].tenant;
|
||||
let docId = expired[i].id;
|
||||
let shardKey = sqlBase.DocumentAdditional.prototype.getShardKey(expired[i].additional);
|
||||
let wopiSrc = sqlBase.DocumentAdditional.prototype.getWopiSrc(expired[i].additional);
|
||||
ctx.init(tenant, docId, ctx.userId, shardKey, wopiSrc);
|
||||
yield ctx.initTenantCache();
|
||||
|
||||
if (currentTenant !== tenant) {
|
||||
ctx.init(tenant, docId, ctx.userId, shardKey, wopiSrc);
|
||||
yield ctx.initTenantCache();
|
||||
currentTenant = tenant;
|
||||
} else {
|
||||
ctx.setDocId(docId);
|
||||
ctx.setShardKey(shardKey);
|
||||
ctx.setWopiSrc(wopiSrc);
|
||||
}
|
||||
|
||||
//todo tenant
|
||||
//check that no one is in the document
|
||||
let editorsCount = yield docsCoServer.getEditorsCountPromise(ctx, docId);
|
||||
@ -99,7 +115,7 @@ var checkFileExpire = function(expireSeconds) {
|
||||
} catch (e) {
|
||||
ctx.logger.error('checkFileExpire error: %s', e.stack);
|
||||
} finally {
|
||||
setTimeout(checkFileExpire, expFilesStep);
|
||||
setTimeout(checkFileExpire, currentExpFilesStep);
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -108,21 +124,34 @@ var checkDocumentExpire = function() {
|
||||
var queue = null;
|
||||
var removedCount = 0;
|
||||
var startSaveCount = 0;
|
||||
let currentExpDocumentsStep = expDocumentsStep;
|
||||
let ctx = new operationContext.Context();
|
||||
try {
|
||||
ctx.logger.info('checkDocumentExpire start');
|
||||
yield ctx.initTenantCache();
|
||||
const currentDocumentsCron = ctx.getCfg('services.CoAuthoring.expire.documentsCron', cfgExpDocumentsCron);
|
||||
currentExpDocumentsStep = getCronStep(currentDocumentsCron);
|
||||
var now = (new Date()).getTime();
|
||||
let expiredKeys = yield docsCoServer.editorData.getDocumentPresenceExpired(now);
|
||||
if (expiredKeys.length > 0) {
|
||||
queue = new queueService();
|
||||
yield queue.initPromise(true, false, false, false, false, false);
|
||||
|
||||
expiredKeys.sort((a, b) => a[0].localeCompare(b[0]));
|
||||
let currentTenant = null;
|
||||
|
||||
for (var i = 0; i < expiredKeys.length; ++i) {
|
||||
let tenant = expiredKeys[i][0];
|
||||
let docId = expiredKeys[i][1];
|
||||
if (docId) {
|
||||
ctx.init(tenant, docId, ctx.userId);
|
||||
yield ctx.initTenantCache();
|
||||
if (currentTenant !== tenant) {
|
||||
ctx.init(tenant, docId, ctx.userId);
|
||||
yield ctx.initTenantCache();
|
||||
currentTenant = tenant;
|
||||
} else {
|
||||
ctx.setDocId(docId);
|
||||
}
|
||||
|
||||
var hasChanges = yield docsCoServer.hasChanges(ctx, docId);
|
||||
if (hasChanges) {
|
||||
//todo opt_initShardKey from getDocumentPresenceExpired data or from db
|
||||
@ -147,7 +176,8 @@ var checkDocumentExpire = function() {
|
||||
} catch (e) {
|
||||
ctx.logger.error('checkDocumentExpire error: %s', e.stack);
|
||||
}
|
||||
setTimeout(checkDocumentExpire, expDocumentsStep);
|
||||
|
||||
setTimeout(checkDocumentExpire, currentExpDocumentsStep);
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -155,9 +185,12 @@ let forceSaveTimeout = function() {
|
||||
return co(function* () {
|
||||
let queue = null;
|
||||
let pubsub = null;
|
||||
let currentForceSaveStep = cfgForceSaveStep;
|
||||
let ctx = new operationContext.Context();
|
||||
try {
|
||||
ctx.logger.info('forceSaveTimeout start');
|
||||
yield ctx.initTenantCache();
|
||||
currentForceSaveStep = ctx.getCfg('services.CoAuthoring.autoAssembly.step', cfgForceSaveStep);
|
||||
let now = (new Date()).getTime();
|
||||
let expiredKeys = yield docsCoServer.editorData.getForceSaveTimer(now);
|
||||
if (expiredKeys.length > 0) {
|
||||
@ -167,14 +200,24 @@ let forceSaveTimeout = function() {
|
||||
pubsub = new pubsubService();
|
||||
yield pubsub.initPromise();
|
||||
|
||||
expiredKeys.sort((a, b) => a[0].localeCompare(b[0]));
|
||||
|
||||
let actions = [];
|
||||
let currentTenant = null;
|
||||
|
||||
for (let i = 0; i < expiredKeys.length; ++i) {
|
||||
let tenant = expiredKeys[i][0];
|
||||
let docId = expiredKeys[i][1];
|
||||
if (docId) {
|
||||
ctx.init(tenant, docId, ctx.userId);
|
||||
yield ctx.initTenantCache();
|
||||
//todo opt_initShardKey from ForceSave data or from db
|
||||
if (currentTenant !== tenant) {
|
||||
ctx.init(tenant, docId, ctx.userId);
|
||||
yield ctx.initTenantCache();
|
||||
//todo opt_initShardKey from ForceSave data or from db
|
||||
currentTenant = tenant;
|
||||
} else {
|
||||
ctx.setDocId(docId);
|
||||
}
|
||||
|
||||
actions.push(docsCoServer.startForceSave(ctx, docId, commondefines.c_oAscForceSaveTypes.Timeout,
|
||||
undefined, undefined, undefined, undefined,
|
||||
undefined, undefined, undefined, undefined, queue, pubsub, undefined, true));
|
||||
@ -196,17 +239,18 @@ let forceSaveTimeout = function() {
|
||||
yield pubsub.close();
|
||||
}
|
||||
} catch (e) {
|
||||
ctx.logger.error('checkDocumentExpire error: %s', e.stack);
|
||||
ctx.logger.error('forceSaveTimeout cleanup error: %s', e.stack);
|
||||
}
|
||||
setTimeout(forceSaveTimeout, cfgForceSaveStep);
|
||||
setTimeout(forceSaveTimeout, ms(currentForceSaveStep));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.startGC = function() {
|
||||
//runtime config is read on start
|
||||
setTimeout(checkDocumentExpire, expDocumentsStep);
|
||||
setTimeout(checkFileExpire, expFilesStep);
|
||||
setTimeout(forceSaveTimeout, cfgForceSaveStep);
|
||||
setTimeout(forceSaveTimeout, ms(cfgForceSaveStep));
|
||||
};
|
||||
exports.getCronStep = getCronStep;
|
||||
exports.checkFileExpire = checkFileExpire;
|
||||
|
||||
@ -32,7 +32,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const config = require('config');
|
||||
const moduleReloader = require('./../../Common/sources/moduleReloader');
|
||||
const config = moduleReloader.requireConfigWithRuntime();
|
||||
//process.env.NODE_ENV = config.get('services.CoAuthoring.server.mode');
|
||||
const logger = require('./../../Common/sources/logger');
|
||||
const co = require('co');
|
||||
|
||||
@ -35,12 +35,13 @@
|
||||
const cluster = require('cluster');
|
||||
const logger = require('./../../Common/sources/logger');
|
||||
const operationContext = require('./../../Common/sources/operationContext');
|
||||
const moduleReloader = require('./../../Common/sources/moduleReloader');
|
||||
const config = moduleReloader.requireConfigWithRuntime();
|
||||
|
||||
if (cluster.isMaster) {
|
||||
const fs = require('fs');
|
||||
const co = require('co');
|
||||
const os = require('os');
|
||||
const config = require('config');
|
||||
const license = require('./../../Common/sources/license');
|
||||
|
||||
const cfgLicenseFile = config.get('license.license_file');
|
||||
|
||||
Reference in New Issue
Block a user