fix: 修复在watch模式下会重复注册定时器的问题
This commit is contained in:
parent
13384ab772
commit
89fe961434
|
|
@ -0,0 +1,45 @@
|
||||||
|
type TimerType = 'timeout' | 'interval' | 'immediate';
|
||||||
|
interface TimerRecord {
|
||||||
|
id: NodeJS.Timeout | NodeJS.Immediate;
|
||||||
|
type: TimerType;
|
||||||
|
createdAt: number;
|
||||||
|
}
|
||||||
|
declare class GlobalTimerManager {
|
||||||
|
private timers;
|
||||||
|
private isHooked;
|
||||||
|
private readonly original;
|
||||||
|
/**
|
||||||
|
* 开始拦截全局定时器
|
||||||
|
*/
|
||||||
|
hook(): void;
|
||||||
|
/**
|
||||||
|
* 恢复原始的全局定时器函数
|
||||||
|
*/
|
||||||
|
unhook(): void;
|
||||||
|
/**
|
||||||
|
* 清除所有被追踪的定时器
|
||||||
|
*/
|
||||||
|
clearAll(): number;
|
||||||
|
/**
|
||||||
|
* 按类型清除定时器
|
||||||
|
*/
|
||||||
|
clearByType(type: TimerType): number;
|
||||||
|
/**
|
||||||
|
* 获取当前活跃的定时器数量
|
||||||
|
*/
|
||||||
|
getActiveCount(): number;
|
||||||
|
/**
|
||||||
|
* 获取定时器统计信息
|
||||||
|
*/
|
||||||
|
getStats(): Record<TimerType, number>;
|
||||||
|
/**
|
||||||
|
* 获取所有定时器的详细信息(用于调试)
|
||||||
|
*/
|
||||||
|
getTimers(): TimerRecord[];
|
||||||
|
}
|
||||||
|
export declare const timerManager: GlobalTimerManager;
|
||||||
|
export declare const hookTimers: () => void;
|
||||||
|
export declare const unhookTimers: () => void;
|
||||||
|
export declare const clearAllTimers: () => number;
|
||||||
|
export declare const getTimerStats: () => Record<TimerType, number>;
|
||||||
|
export {};
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
"use strict";
|
||||||
|
// timer-manager.ts
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.getTimerStats = exports.clearAllTimers = exports.unhookTimers = exports.hookTimers = exports.timerManager = void 0;
|
||||||
|
class GlobalTimerManager {
|
||||||
|
timers = new Map();
|
||||||
|
isHooked = false;
|
||||||
|
// 保存原始函数
|
||||||
|
original = {
|
||||||
|
setTimeout: global.setTimeout,
|
||||||
|
setInterval: global.setInterval,
|
||||||
|
setImmediate: global.setImmediate, clearTimeout: global.clearTimeout,
|
||||||
|
clearInterval: global.clearInterval,
|
||||||
|
clearImmediate: global.clearImmediate,
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 开始拦截全局定时器
|
||||||
|
*/
|
||||||
|
hook() {
|
||||||
|
if (this.isHooked) {
|
||||||
|
console.warn('[TimerManager] Already hooked');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const self = this;
|
||||||
|
// Hook setTimeout
|
||||||
|
global.setTimeout = function (callback, ms, ...args) {
|
||||||
|
const id = self.original.setTimeout((...callbackArgs) => {
|
||||||
|
self.timers.delete(id); // 执行完后移除
|
||||||
|
callback(...callbackArgs);
|
||||||
|
}, ms, ...args);
|
||||||
|
self.timers.set(id, {
|
||||||
|
id,
|
||||||
|
type: 'timeout',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
});
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
// Hook setInterval
|
||||||
|
global.setInterval = function (callback, ms, ...args) {
|
||||||
|
const id = self.original.setInterval(callback, ms, ...args);
|
||||||
|
self.timers.set(id, {
|
||||||
|
id,
|
||||||
|
type: 'interval',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
});
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
// Hook setImmediate
|
||||||
|
global.setImmediate = function (callback, ...args) {
|
||||||
|
const id = self.original.setImmediate((...callbackArgs) => {
|
||||||
|
self.timers.delete(id); // 执行完后移除
|
||||||
|
callback(...callbackArgs);
|
||||||
|
}, ...args);
|
||||||
|
self.timers.set(id, {
|
||||||
|
id,
|
||||||
|
type: 'immediate',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
});
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
// Hook clear方法(确保从追踪中移除)
|
||||||
|
global.clearTimeout = ((id) => {
|
||||||
|
self.timers.delete(id);
|
||||||
|
return self.original.clearTimeout(id);
|
||||||
|
});
|
||||||
|
global.clearInterval = ((id) => {
|
||||||
|
self.timers.delete(id);
|
||||||
|
return self.original.clearInterval(id);
|
||||||
|
});
|
||||||
|
global.clearImmediate = ((id) => {
|
||||||
|
self.timers.delete(id);
|
||||||
|
return self.original.clearImmediate(id);
|
||||||
|
});
|
||||||
|
this.isHooked = true;
|
||||||
|
console.log('[TimerManager] Hooked global timer functions');
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 恢复原始的全局定时器函数
|
||||||
|
*/
|
||||||
|
unhook() {
|
||||||
|
if (!this.isHooked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
global.setTimeout = this.original.setTimeout;
|
||||||
|
global.setInterval = this.original.setInterval;
|
||||||
|
global.setImmediate = this.original.setImmediate;
|
||||||
|
global.clearTimeout = this.original.clearTimeout;
|
||||||
|
global.clearInterval = this.original.clearInterval;
|
||||||
|
global.clearImmediate = this.original.clearImmediate;
|
||||||
|
this.isHooked = false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 清除所有被追踪的定时器
|
||||||
|
*/
|
||||||
|
clearAll() {
|
||||||
|
const count = this.timers.size;
|
||||||
|
this.timers.forEach((record) => {
|
||||||
|
if (record.type === 'immediate') {
|
||||||
|
this.original.clearImmediate.call(null, record.id);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.original.clearTimeout.call(null, record.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.timers.clear();
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 按类型清除定时器
|
||||||
|
*/
|
||||||
|
clearByType(type) {
|
||||||
|
let count = 0;
|
||||||
|
this.timers.forEach((record, id) => {
|
||||||
|
if (record.type === type) {
|
||||||
|
if (type === 'immediate') {
|
||||||
|
this.original.clearImmediate.call(null, id);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.original.clearTimeout.call(null, id);
|
||||||
|
}
|
||||||
|
this.timers.delete(id);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取当前活跃的定时器数量
|
||||||
|
*/
|
||||||
|
getActiveCount() {
|
||||||
|
return this.timers.size;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取定时器统计信息
|
||||||
|
*/
|
||||||
|
getStats() {
|
||||||
|
const stats = {
|
||||||
|
timeout: 0,
|
||||||
|
interval: 0,
|
||||||
|
immediate: 0,
|
||||||
|
};
|
||||||
|
this.timers.forEach((record) => {
|
||||||
|
stats[record.type]++;
|
||||||
|
});
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取所有定时器的详细信息(用于调试)
|
||||||
|
*/
|
||||||
|
getTimers() {
|
||||||
|
return Array.from(this.timers.values());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.timerManager = new GlobalTimerManager();
|
||||||
|
const hookTimers = () => exports.timerManager.hook();
|
||||||
|
exports.hookTimers = hookTimers;
|
||||||
|
const unhookTimers = () => exports.timerManager.unhook();
|
||||||
|
exports.unhookTimers = unhookTimers;
|
||||||
|
const clearAllTimers = () => exports.timerManager.clearAll();
|
||||||
|
exports.clearAllTimers = clearAllTimers;
|
||||||
|
const getTimerStats = () => exports.timerManager.getStats();
|
||||||
|
exports.getTimerStats = getTimerStats;
|
||||||
|
|
@ -9,6 +9,7 @@ const dayjs_1 = tslib_1.__importDefault(require("dayjs"));
|
||||||
const fs_1 = tslib_1.__importDefault(require("fs"));
|
const fs_1 = tslib_1.__importDefault(require("fs"));
|
||||||
const polyfill_1 = require("./polyfill");
|
const polyfill_1 = require("./polyfill");
|
||||||
const tsc_alias_1 = require("tsc-alias");
|
const tsc_alias_1 = require("tsc-alias");
|
||||||
|
const timer_manager_1 = require("./timer-manager");
|
||||||
// 创建事件发射器
|
// 创建事件发射器
|
||||||
const createEventEmitter = () => {
|
const createEventEmitter = () => {
|
||||||
const listeners = new Map();
|
const listeners = new Map();
|
||||||
|
|
@ -228,6 +229,7 @@ const createServerManager = (projectPath, eventEmitter, config) => {
|
||||||
const simpleConnector = require(path_1.default.join(projectPath, "lib/config/connector")).default;
|
const simpleConnector = require(path_1.default.join(projectPath, "lib/config/connector")).default;
|
||||||
console.warn("----> Starting service......");
|
console.warn("----> Starting service......");
|
||||||
try {
|
try {
|
||||||
|
(0, timer_manager_1.clearAllTimers)();
|
||||||
// 这里注意要在require之前,因为require会触发编译
|
// 这里注意要在require之前,因为require会触发编译
|
||||||
const { startup } = require('./start');
|
const { startup } = require('./start');
|
||||||
shutdown = await startup(pwd, simpleConnector).then((shutdown) => {
|
shutdown = await startup(pwd, simpleConnector).then((shutdown) => {
|
||||||
|
|
@ -606,6 +608,8 @@ const watch = async (projectPath, config) => {
|
||||||
// 初始化polyfill
|
// 初始化polyfill
|
||||||
polyfillLoader();
|
polyfillLoader();
|
||||||
realConfig.polyfill.console.enable && (0, polyfill_1.polyfillConsole)("watch", enableTrace, realConfig.polyfill?.console?.formatter);
|
realConfig.polyfill.console.enable && (0, polyfill_1.polyfillConsole)("watch", enableTrace, realConfig.polyfill?.console?.formatter);
|
||||||
|
// 监听定时器相关的API,确保定时器在重启后不会重复触发
|
||||||
|
(0, timer_manager_1.hookTimers)();
|
||||||
// 初始编译检查
|
// 初始编译检查
|
||||||
const initialCompile = () => {
|
const initialCompile = () => {
|
||||||
const serverConfigFile = path_1.default.join(projectPath, "lib/configuration/server.js");
|
const serverConfigFile = path_1.default.join(projectPath, "lib/configuration/server.js");
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,211 @@
|
||||||
|
// timer-manager.ts
|
||||||
|
|
||||||
|
type TimerType = 'timeout' | 'interval' | 'immediate';
|
||||||
|
|
||||||
|
interface TimerRecord {
|
||||||
|
id: NodeJS.Timeout | NodeJS.Immediate;
|
||||||
|
type: TimerType;
|
||||||
|
createdAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class GlobalTimerManager {
|
||||||
|
private timers = new Map<NodeJS.Timeout | NodeJS.Immediate, TimerRecord>();
|
||||||
|
private isHooked = false;
|
||||||
|
|
||||||
|
// 保存原始函数
|
||||||
|
private readonly original = {
|
||||||
|
setTimeout: global.setTimeout,
|
||||||
|
setInterval: global.setInterval,
|
||||||
|
setImmediate: global.setImmediate,clearTimeout: global.clearTimeout,
|
||||||
|
clearInterval: global.clearInterval,
|
||||||
|
clearImmediate: global.clearImmediate,};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始拦截全局定时器
|
||||||
|
*/
|
||||||
|
hook(): void {
|
||||||
|
if (this.isHooked) {
|
||||||
|
console.warn('[TimerManager] Already hooked');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
// Hook setTimeout
|
||||||
|
global.setTimeout = function (
|
||||||
|
callback: (...args: any[]) => void,
|
||||||
|
ms?: number,
|
||||||
|
...args: any[]
|
||||||
|
): any {
|
||||||
|
const id = self.original.setTimeout(
|
||||||
|
(...callbackArgs: any[]) => {
|
||||||
|
self.timers.delete(id); // 执行完后移除
|
||||||
|
callback(...callbackArgs);
|
||||||
|
},
|
||||||
|
ms,
|
||||||
|
...args
|
||||||
|
);
|
||||||
|
|
||||||
|
self.timers.set(id, {
|
||||||
|
id,
|
||||||
|
type: 'timeout',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return id;
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
// Hook setInterval
|
||||||
|
global.setInterval = function (
|
||||||
|
callback: (...args: any[]) => void,
|
||||||
|
ms?: number,
|
||||||
|
...args: any[]
|
||||||
|
): any {
|
||||||
|
const id = self.original.setInterval(callback, ms, ...args);
|
||||||
|
|
||||||
|
self.timers.set(id, {
|
||||||
|
id,
|
||||||
|
type: 'interval',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return id;
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
// Hook setImmediate
|
||||||
|
global.setImmediate = function (
|
||||||
|
callback: (...args: any[]) => void,
|
||||||
|
...args: any[]
|
||||||
|
): any {
|
||||||
|
const id = self.original.setImmediate(
|
||||||
|
(...callbackArgs: any[]) => {
|
||||||
|
self.timers.delete(id); // 执行完后移除
|
||||||
|
callback(...callbackArgs);
|
||||||
|
},
|
||||||
|
...args
|
||||||
|
);
|
||||||
|
|
||||||
|
self.timers.set(id, {
|
||||||
|
id,
|
||||||
|
type: 'immediate',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return id;
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
// Hook clear方法(确保从追踪中移除)
|
||||||
|
global.clearTimeout = ((id: any) => {
|
||||||
|
self.timers.delete(id);
|
||||||
|
return self.original.clearTimeout(id);
|
||||||
|
}) as any;
|
||||||
|
|
||||||
|
global.clearInterval = ((id: any) => {
|
||||||
|
self.timers.delete(id);
|
||||||
|
return self.original.clearInterval(id);
|
||||||
|
}) as any;
|
||||||
|
|
||||||
|
global.clearImmediate = ((id: any) => {
|
||||||
|
self.timers.delete(id);
|
||||||
|
return self.original.clearImmediate(id);
|
||||||
|
}) as any;
|
||||||
|
|
||||||
|
this.isHooked = true;
|
||||||
|
console.log('[TimerManager] Hooked global timer functions');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复原始的全局定时器函数
|
||||||
|
*/
|
||||||
|
unhook(): void {
|
||||||
|
if (!this.isHooked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global.setTimeout = this.original.setTimeout;
|
||||||
|
global.setInterval = this.original.setInterval;
|
||||||
|
global.setImmediate = this.original.setImmediate;
|
||||||
|
global.clearTimeout = this.original.clearTimeout;
|
||||||
|
global.clearInterval = this.original.clearInterval;
|
||||||
|
global.clearImmediate = this.original.clearImmediate;
|
||||||
|
|
||||||
|
this.isHooked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除所有被追踪的定时器
|
||||||
|
*/
|
||||||
|
clearAll(): number {
|
||||||
|
const count = this.timers.size;
|
||||||
|
|
||||||
|
this.timers.forEach((record) => {
|
||||||
|
if (record.type === 'immediate') {
|
||||||
|
this.original.clearImmediate.call(null, record.id as NodeJS.Immediate);
|
||||||
|
} else {
|
||||||
|
this.original.clearTimeout.call(null, record.id as NodeJS.Timeout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.timers.clear();
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按类型清除定时器
|
||||||
|
*/
|
||||||
|
clearByType(type: TimerType): number {
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
this.timers.forEach((record, id) => {
|
||||||
|
if (record.type === type) {
|
||||||
|
if (type === 'immediate') {
|
||||||
|
this.original.clearImmediate.call(null, id as NodeJS.Immediate);
|
||||||
|
} else {
|
||||||
|
this.original.clearTimeout.call(null, id as NodeJS.Timeout);
|
||||||
|
}
|
||||||
|
this.timers.delete(id);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前活跃的定时器数量
|
||||||
|
*/
|
||||||
|
getActiveCount(): number {
|
||||||
|
return this.timers.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取定时器统计信息
|
||||||
|
*/
|
||||||
|
getStats(): Record<TimerType, number> {
|
||||||
|
const stats: Record<TimerType, number> = {
|
||||||
|
timeout: 0,
|
||||||
|
interval: 0,
|
||||||
|
immediate: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.timers.forEach((record) => {
|
||||||
|
stats[record.type]++;
|
||||||
|
});
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有定时器的详细信息(用于调试)
|
||||||
|
*/
|
||||||
|
getTimers(): TimerRecord[] {
|
||||||
|
return Array.from(this.timers.values());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const timerManager = new GlobalTimerManager();
|
||||||
|
|
||||||
|
export const hookTimers = () => timerManager.hook();
|
||||||
|
export const unhookTimers = () => timerManager.unhook();
|
||||||
|
export const clearAllTimers = () => timerManager.clearAll();
|
||||||
|
export const getTimerStats = () => timerManager.getStats();
|
||||||
|
|
@ -7,6 +7,7 @@ import fs from "fs";
|
||||||
import { AsyncContext } from "oak-domain/lib/store/AsyncRowStore";
|
import { AsyncContext } from "oak-domain/lib/store/AsyncRowStore";
|
||||||
import { LogFormatter, LogFormatterProp, polyfillConsole } from "./polyfill";
|
import { LogFormatter, LogFormatterProp, polyfillConsole } from "./polyfill";
|
||||||
import { prepareSingleFileReplaceTscAliasPaths, SingleFileReplacer } from 'tsc-alias';
|
import { prepareSingleFileReplaceTscAliasPaths, SingleFileReplacer } from 'tsc-alias';
|
||||||
|
import { clearAllTimers, hookTimers } from "./timer-manager";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 工作流程
|
* 工作流程
|
||||||
|
|
@ -399,6 +400,7 @@ const createServerManager = (
|
||||||
console.warn("----> Starting service......");
|
console.warn("----> Starting service......");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
clearAllTimers();
|
||||||
// 这里注意要在require之前,因为require会触发编译
|
// 这里注意要在require之前,因为require会触发编译
|
||||||
const { startup } = require('./start') as {
|
const { startup } = require('./start') as {
|
||||||
startup: (pwd: string, connector: any) => Promise<() => Promise<void>>;
|
startup: (pwd: string, connector: any) => Promise<() => Promise<void>>;
|
||||||
|
|
@ -879,6 +881,9 @@ export const watch = async (
|
||||||
polyfillLoader();
|
polyfillLoader();
|
||||||
realConfig.polyfill.console.enable && polyfillConsole("watch", enableTrace, realConfig.polyfill?.console?.formatter);
|
realConfig.polyfill.console.enable && polyfillConsole("watch", enableTrace, realConfig.polyfill?.console?.formatter);
|
||||||
|
|
||||||
|
// 监听定时器相关的API,确保定时器在重启后不会重复触发
|
||||||
|
hookTimers()
|
||||||
|
|
||||||
// 初始编译检查
|
// 初始编译检查
|
||||||
const initialCompile = () => {
|
const initialCompile = () => {
|
||||||
const serverConfigFile = pathLib.join(projectPath, "lib/configuration/server.js");
|
const serverConfigFile = pathLib.join(projectPath, "lib/configuration/server.js");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue