mirror of
https://github.com/ONLYOFFICE/server.git
synced 2026-04-07 14:04:35 +08:00
[bug] Add watchWithFallback to support fs.watch fallback on NFS; For bug 75439
This commit is contained in:
@ -99,15 +99,21 @@ async function saveConfig(ctx, config) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle configuration file change events
|
||||
* @param {string} eventType - Type of file system event (change, rename)
|
||||
* @param {string} filename - Name of the file that triggered the event
|
||||
* Supports both fs.watch (eventType, filename) and fs.watchFile (current, previous) callbacks
|
||||
*/
|
||||
function handleConfigFileChange(eventType, filename) {
|
||||
function handleConfigFileChange(eventTypeOrCurrent, filenameOrPrevious) {
|
||||
try {
|
||||
if (configFileName === filename) {
|
||||
let shouldReload = false;
|
||||
|
||||
if (typeof eventTypeOrCurrent === 'object' && eventTypeOrCurrent.isFile) {
|
||||
shouldReload = eventTypeOrCurrent.mtime !== filenameOrPrevious.mtime;
|
||||
operationContext.global.logger.info(`handleConfigFileChange reloaded=${shouldReload} watchFile: ${configFileName}`);
|
||||
} else {
|
||||
shouldReload = configFileName === filenameOrPrevious;
|
||||
operationContext.global.logger.info(`handleConfigFileChange reloaded=${shouldReload} watch ${eventTypeOrCurrent}: ${filenameOrPrevious}`);
|
||||
}
|
||||
if (shouldReload) {
|
||||
nodeCache.del(configFileName);
|
||||
operationContext.global.logger.info(`handleConfigFileChange configuration ${eventType}: ${configFileName}`);
|
||||
}
|
||||
} catch (err) {
|
||||
operationContext.global.logger.error(`handleConfigFileChange error: ${err.message}`);
|
||||
@ -117,21 +123,13 @@ function handleConfigFileChange(eventType, filename) {
|
||||
/**
|
||||
* Initialize the configuration directory watcher
|
||||
*/
|
||||
function initRuntimeConfigWatcher(ctx) {
|
||||
async function initRuntimeConfigWatcher(ctx) {
|
||||
if (!configFilePath) {
|
||||
ctx.logger.info(`runtimeConfig.filePath is not specified`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const configDir = path.dirname(configFilePath);
|
||||
const watcher = fsWatch.watch(configDir, handleConfigFileChange);
|
||||
watcher.on('error', (err) => {
|
||||
ctx.logger.warn(`initRuntimeConfigWatcher error: ${err.message}`);
|
||||
});
|
||||
ctx.logger.info(`watching for runtime config changes in: ${configDir}`);
|
||||
} catch (watchErr) {
|
||||
ctx.logger.warn(`initRuntimeConfigWatcher error: ${watchErr.message}`);
|
||||
}
|
||||
const configDir = path.dirname(configFilePath);
|
||||
await utils.watchWithFallback(ctx, configDir, configFilePath, handleConfigFileChange);
|
||||
}
|
||||
module.exports = {
|
||||
initRuntimeConfigWatcher,
|
||||
|
||||
@ -40,6 +40,7 @@ const { buffer } = require('node:stream/consumers');
|
||||
const { Transform } = require('stream');
|
||||
var config = require('config');
|
||||
var fs = require('fs');
|
||||
const fsPromises = require('node:fs/promises');
|
||||
var path = require('path');
|
||||
const crypto = require('crypto');
|
||||
var url = require('url');
|
||||
@ -1361,3 +1362,66 @@ exports.isObject = isObject;
|
||||
exports.deepMergeObjects = deepMergeObjects;
|
||||
exports.NodeCache = NodeCache;//todo via require
|
||||
|
||||
//like suggestion in https://github.com/paulmillr/chokidar/issues/242#issuecomment-76205459
|
||||
const UNSAFE_MAGIC = new Set([
|
||||
0x6969, // NFS
|
||||
0xFF534D42, // CIFS/SMB1
|
||||
0xFE534D42, // SMB2+
|
||||
0x517B, // legacy SMB
|
||||
0x65735546, // FUSE
|
||||
0x794C7630, // overlayfs
|
||||
0x00C36400, // CephFS
|
||||
0x73757245, // Coda
|
||||
0x6B414653 // AFS
|
||||
]);
|
||||
|
||||
/**
|
||||
* Gets the file system type for the given path
|
||||
* @param {operationContext} ctx - Operation context
|
||||
* @param {string} path - Path to check
|
||||
* @returns {Promise<number>} File system type
|
||||
*/
|
||||
async function getFsType(ctx, path) {
|
||||
try {
|
||||
const statfs = await fsPromises.statfs(path);
|
||||
const fsType = Number(statfs.type);
|
||||
ctx.logger.info(`getFsType fs type=${fsType} ${path}`);
|
||||
return fsType;
|
||||
} catch (err) {
|
||||
ctx.logger.info(`getFsType error: ${path}: ${err.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* File watcher with native events fallback to polling
|
||||
* @param {operationContext} ctx - Operation context
|
||||
* @param {string} dirPath - Directory path to watch
|
||||
* @param {string} filePath - File path to watch
|
||||
* @param {Function} listener - Change event callback
|
||||
* @param {Object} opts - Options
|
||||
* @returns {Promise<fs.FSWatcher|fs.StatWatcher>} Watcher instance
|
||||
*/
|
||||
exports.watchWithFallback = async function watchWithFallback(ctx, dirPath, filePath, listener, opts = {}) {
|
||||
const fsType = await getFsType(ctx, dirPath);
|
||||
if (null === fsType || UNSAFE_MAGIC.has(fsType)) {
|
||||
ctx.logger.info(`watchWithFallback fs type=${fsType} unsupport watch. fallback to watchFile ${filePath}`);
|
||||
return fs.watchFile(filePath, opts, listener);
|
||||
}
|
||||
|
||||
//Try native watch
|
||||
try {
|
||||
const watcher = fs.watch(dirPath, opts, listener);
|
||||
watcher.on('error', (err) => {
|
||||
watcher.close();
|
||||
ctx.logger.info(`watchWithFallback error ${dirPath} fallback to watchFile ${filePath}: ${err.message}`);
|
||||
fs.watchFile(filePath, opts, listener);
|
||||
});
|
||||
ctx.logger.info(`watchWithFallback watch: ${dirPath}`);
|
||||
return watcher;
|
||||
} catch (err) {
|
||||
ctx.logger.info(`watchWithFallback error ${dirPath} fallback to watchFile ${filePath}: ${err.message}`);
|
||||
return fs.watchFile(filePath, opts, listener);
|
||||
}
|
||||
}
|
||||
@ -4011,7 +4011,9 @@ exports.install = function(server, callbackFunction) {
|
||||
});
|
||||
|
||||
//Initialize watch here to avoid circular import with operationContext
|
||||
runtimeConfigManager.initRuntimeConfigWatcher(operationContext.global);
|
||||
runtimeConfigManager.initRuntimeConfigWatcher(operationContext.global).catch(err => {
|
||||
operationContext.global.logger.warn('initRuntimeConfigWatcher error: %s', err.stack);
|
||||
});
|
||||
void aiProxyHandler.getPluginSettings(operationContext.global);
|
||||
};
|
||||
exports.setLicenseInfo = async function(globalCtx, data, original) {
|
||||
|
||||
Reference in New Issue
Block a user