فهرست منبع

Добавлен модуль AsyncExit для выполненния cleanup-процедур перед выходом из приложения

Book Pauk 3 سال پیش
والد
کامیت
4852c7aec3
5فایلهای تغییر یافته به همراه179 افزوده شده و 73 حذف شده
  1. 13 4
      server/core/AppLogger.js
  2. 111 0
      server/core/AsyncExit.js
  3. 0 1
      server/core/LibSharedStorage/MegaStorage.js
  4. 43 63
      server/core/Logger.js
  5. 12 5
      server/index.js

+ 13 - 4
server/core/AppLogger.js

@@ -7,10 +7,14 @@ let instance = null;
 class AppLogger {
     constructor() {
         if (!instance) {
+            this.inited = false;
+            this.logFileName = '';
+            this.errLogFileName = '';
+            this.fatalLogFileName = '';
+
             instance = this;
         }
 
-        this.inited = false;
         return instance;
     }
 
@@ -22,11 +26,16 @@ class AppLogger {
 
         if (config.loggingEnabled) {
             await fs.ensureDir(config.logDir);
+
+            this.logFileName = `${config.logDir}/${config.name}.log`;
+            this.errLogFileName = `${config.logDir}/${config.name}.err.log`;
+            this.fatalLogFileName = `${config.logDir}/${config.name}.fatal.log`;
+
             loggerParams = [
                 {log: 'ConsoleLog'},
-                {log: 'FileLog', fileName: `${config.logDir}/${config.name}.log`},
-                {log: 'FileLog', fileName: `${config.logDir}/${config.name}.err.log`, exclude: [LM_OK, LM_INFO, LM_TOTAL]},
-                {log: 'FileLog', fileName: `${config.logDir}/${config.name}.fatal.log`, exclude: [LM_OK, LM_INFO, LM_WARN, LM_ERR, LM_TOTAL]},//LM_FATAL only
+                {log: 'FileLog', fileName: this.logFileName},
+                {log: 'FileLog', fileName: this.errLogFileName, exclude: [LM_OK, LM_INFO, LM_TOTAL]},
+                {log: 'FileLog', fileName: this.fatalLogFileName, exclude: [LM_OK, LM_INFO, LM_WARN, LM_ERR, LM_TOTAL]},//LM_FATAL only
             ];
         }
 

+ 111 - 0
server/core/AsyncExit.js

@@ -0,0 +1,111 @@
+let instance = null;
+
+const defaultTimeout = 15*1000;//15 sec
+const exitSignals = ['SIGINT', 'SIGTERM', 'SIGBREAK', 'SIGHUP', 'uncaughtException'];
+
+//singleton
+class AsyncExit {
+    constructor() {
+        if (!instance) {
+            this.onSignalCallbacks = new Map();
+            this.callbacks = new Map();
+            this.afterCallbacks = new Map();
+            this.exitTimeout = defaultTimeout;
+            this.inited = false;
+            instance = this;
+        }
+
+        return instance;
+    }
+
+    init(signals = null, codeOnSignal = 2) {
+        if (this.inited)
+            throw new Error('AsyncExit: initialized already');
+
+        if (!signals)
+            signals = exitSignals;
+
+        const runSingalCallbacks = async(signal) => {
+            for (const signalCallback of this.onSignalCallbacks.keys()) {
+                try {
+                    await signalCallback(signal);
+                } catch(e) {
+                    console.error(e);
+                }
+            }
+        };
+
+        for (const signal of signals) {
+            process.once(signal, async() => {
+                await runSingalCallbacks(signal);
+                this.exit(codeOnSignal);
+            });
+        }
+
+        this.inited = true;
+    }
+
+    onSignal(signalCallback) {
+        if (!this.onSignalCallbacks.has(signalCallback)) {
+            this.onSignalCallbacks.set(signalCallback, true);
+        }
+    }
+
+    add(exitCallback) {
+        if (!this.callbacks.has(exitCallback)) {
+            this.callbacks.set(exitCallback, true);
+        }
+    }
+
+    addAfter(exitCallback) {
+        if (!this.afterCallbacks.has(exitCallback)) {
+            this.afterCallbacks.set(exitCallback, true);
+        }
+    }
+
+    remove(exitCallback) {
+        if (this.callbacks.has(exitCallback)) {
+            this.callbacks.delete(exitCallback);
+        }
+        if (this.afterCallbacks.has(exitCallback)) {
+            this.afterCallbacks.delete(exitCallback);
+        }
+    }
+
+    setExitTimeout(timeout) {
+        this.exitTimeout = timeout;
+    }
+
+    exit(code = 0) {
+        if (this.exiting)
+            return;
+
+        this.exiting = true;
+
+        const timer = setTimeout(() => { process.exit(code); }, this.exitTimeout);
+
+        (async() => {
+            for (const exitCallback of this.callbacks.keys()) {
+                try {
+                    await exitCallback();
+                } catch(e) {
+                    console.error(e);
+                }
+            }
+
+            for (const exitCallback of this.afterCallbacks.keys()) {
+                try {
+                    await exitCallback();
+                } catch(e) {
+                    console.error(e);
+                }
+            }
+
+            clearTimeout(timer);
+            //console.log('Exited gracefully');
+            process.exit(code);
+        })();
+    }
+}
+
+module.exports = AsyncExit;

+ 0 - 1
server/core/LibSharedStorage/MegaStorage.js

@@ -25,7 +25,6 @@ class MegaStorage {
             this.debouncedSaveStats = _.debounce(() => {
                 this.saveStats().catch((e) => {
                     log(LM_ERR, `MegaStorage::saveStats ${e.message}`);
-                    //process.exit(1);
                 });
             }, 5000, {'maxWait':6000});
 

+ 43 - 63
server/core/Logger.js

@@ -2,6 +2,9 @@
   Журналирование с буферизацией вывода
 */
 const fs = require('fs-extra');
+const ayncExit = new (require('./AsyncExit'))();
+
+const sleep = (ms) => { return new Promise(resolve => setTimeout(resolve, ms)) };
 
 global.LM_OK = 0;
 global.LM_INFO = 1;
@@ -46,12 +49,13 @@ class BaseLog {
         this.outputBuffer = [];
 
         await this.flushImpl(this.data)
-            .catch(e => { console.log(e); process.exit(1); } );
+            .catch(e => { console.log(e); ayncExit.exit(1); } );
         this.flushing = false;
     }
 
     log(msgType, message) {
-        if (this.closed) { console.log(`Logger fatal error: log was closed (message to log: ${message}})`); process.exit(1); }
+        if (this.closed)
+            return;
 
         if (!this.exclude.has(msgType)) {
             this.outputBuffer.push(message);
@@ -73,7 +77,7 @@ class BaseLog {
         }
     }
 
-    close() {
+    async close() {
         if (this.closed)
             return;
 
@@ -81,12 +85,13 @@ class BaseLog {
             clearInterval(this.iid);
 
         try {
-            if (this.flushing)
-                this.flushImplSync(this.data);
-            this.flushImplSync(this.outputBuffer);
+            while (this.outputBufferLength) {
+                await this.flush();
+                await sleep(1);
+            }
         } catch(e) {
             console.log(e);
-            process.exit(1);
+            ayncExit.exit(1);
         }
         this.outputBufferLength = 0;
         this.outputBuffer = [];
@@ -103,12 +108,14 @@ class FileLog extends BaseLog {
         this.rcid = 0;
     }
 
-    close() {
+    async close() {
         if (this.closed)
             return;
-        super.close();
-        if (this.fd)
-            fs.closeSync(this.fd);
+        await super.close();
+        if (this.fd) {
+            await fs.close(this.fd);
+            this.fd = null;
+        }
         if (this.rcid)
             clearTimeout(this.rcid);
     }
@@ -151,23 +158,15 @@ class FileLog extends BaseLog {
             }, LOG_ROTATE_FILE_CHECK_INTERVAL);
         }
 
-        await fs.write(this.fd, Buffer.from(data.join('')));
-    }
-
-    flushImplSync(data) {
-        fs.writeSync(this.fd, Buffer.from(data.join('')));
+        if (this.fd)
+            await fs.write(this.fd, Buffer.from(data.join('')));
     }
-
 }
 
 class ConsoleLog extends BaseLog {
     async flushImpl(data) {
         process.stdout.write(data.join(''));
     }
-
-    flushImplSync(data) {
-        process.stdout.write(data.join(''));
-    }
 }
 
 //------------------------------------------------------------------
@@ -178,7 +177,7 @@ const factory = {
 
 class Logger {
 
-    constructor(params = null, cleanupCallback = null) {        
+    constructor(params = null) {
         this.handlers = [];
         if (params) {
             params.forEach((logParams) => {
@@ -187,12 +186,22 @@ class Logger {
                 this.handlers.push(new loggerClass(logParams));
             });
         }
-        cleanupCallback = cleanupCallback || (() => {});
-        this.cleanup(cleanupCallback);
+
+        this.closed = false;
+        ayncExit.onSignal((signal) => {
+            this.log(LM_FATAL, `Signal ${signal} received, exiting...`);
+        });
+        ayncExit.addAfter(this.close.bind(this));
+    }
+
+    formatDate(date) {
+        return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ` +
+            `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}:${date.getSeconds().toString().padStart(2, '0')}.` +
+            `${date.getMilliseconds().toString().padStart(3, '0')}`;
     }
 
     prepareMessage(msgType, message) {
-        return (new Date().toISOString()) + ` ${msgTypeToStr[msgType]}: ${message}\n`;
+        return this.formatDate(new Date()) + ` ${msgTypeToStr[msgType]}: ${message}\n`;
     }
 
     log(msgType, message) {
@@ -203,47 +212,18 @@ class Logger {
 
         const mes = this.prepareMessage(msgType, message);
 
-        for (let i = 0; i < this.handlers.length; i++)
-            this.handlers[i].log(msgType, mes);
+        if (!this.closed) {
+            for (let i = 0; i < this.handlers.length; i++)
+                this.handlers[i].log(msgType, mes);
+        } else {
+            console.log(mes);
+        }
     }
 
-    close() {
+    async close() {
         for (let i = 0; i < this.handlers.length; i++)
-            this.handlers[i].close();
-    }
-
-    cleanup(callback) {
-        // attach user callback to the process event emitter
-        // if no callback, it will still exit gracefully on Ctrl-C
-        callback = callback || (() => {});
-        process.on('cleanup', callback);
-
-        // do app specific cleaning before exiting
-        process.on('exit', () => {
-            this.close();
-            process.emit('cleanup');
-        });
-
-        // catch ctrl+c event and exit normally
-        process.on('SIGINT', () => {
-            this.log(LM_FATAL, 'Ctrl-C pressed, exiting...');
-            process.exit(2);
-        });
-
-        process.on('SIGTERM', () => {
-            this.log(LM_FATAL, 'Kill signal, exiting...');
-            process.exit(2);
-        });
-
-        //catch uncaught exceptions, trace, then exit normally
-        process.on('uncaughtException', e => {
-            try {
-                this.log(LM_FATAL, e.stack);
-            } catch (e) {
-                console.log(e.stack);
-            }
-            process.exit(99);
-        });
+            await this.handlers[i].close();
+        this.closed = true;
     }
 }
 

+ 12 - 5
server/index.js

@@ -7,6 +7,11 @@ const compression = require('compression');
 const http = require('http');
 const WebSocket = require ('ws');
 
+const ayncExit = new (require('./core/AsyncExit'))();
+ayncExit.init();
+
+let log = null;
+
 async function init() {
     //config
     const configManager = new (require('./config'))();//singleton
@@ -18,7 +23,7 @@ async function init() {
     //logger
     const appLogger = new (require('./core/AppLogger'))();//singleton
     await appLogger.init(config);
-    const log = appLogger.log;
+    log = appLogger.log;
 
     //dirs
     log(`${config.name} v${config.version}, Node.js ${process.version}`);
@@ -96,13 +101,15 @@ async function main() {
     }
 }
 
-
 (async() => {
     try {
         await init();
         await main();
     } catch (e) {
-        console.error(e);
-        process.exit(1);
+        if (log)
+            log(LM_FATAL, e);
+        else
+            console.error(e);
+        ayncExit.exit(1);
     }
-})();
+})();