diff --git a/packages/egg/src/lib/application.ts b/packages/egg/src/lib/application.ts index 54727e0f34..71b83f7825 100644 --- a/packages/egg/src/lib/application.ts +++ b/packages/egg/src/lib/application.ts @@ -193,16 +193,17 @@ export class Application extends EggApplicationCore { } /** - * save routers to `run/router.json` + * save routers to `${rundir}/router.json` * @private */ dumpConfig(): void { super.dumpConfig(); // dump routers to router.json - const rundir = this.config.rundir; + const rundir = this.getRuntimeRundir(); const FULLPATH = this.loader.FileLoader.FULLPATH; try { + fs.mkdirSync(rundir, { recursive: true }); const dumpRouterFile = path.join(rundir, 'router.json'); const routers = []; for (const layer of this.router.stack) { diff --git a/packages/egg/src/lib/egg.ts b/packages/egg/src/lib/egg.ts index a27e479da7..8ecce9af91 100644 --- a/packages/egg/src/lib/egg.ts +++ b/packages/egg/src/lib/egg.ts @@ -38,6 +38,8 @@ import { convertObject, createTransparentProxy } from './core/utils.ts'; import type { EggApplicationLoader } from './loader/index.ts'; import type { EggAppConfig } from './types.ts'; +const DEFAULT_WORKER_START_TIMEOUT = 10 * 60 * 1000; + export interface EggApplicationCoreOptions extends Omit { mode?: 'cluster' | 'single'; clusterPort?: number; @@ -561,15 +563,13 @@ export class EggApplicationCore extends EggCore { } /** - * save app.config to `run/${type}_config.json` + * save app.config to `${rundir}/${type}_config.json` * @private */ dumpConfig(): void { - const rundir = this.config.rundir; + const rundir = this.getRuntimeRundir(); try { - if (!fs.existsSync(rundir)) { - fs.mkdirSync(rundir); - } + fs.mkdirSync(rundir, { recursive: true }); // get dumped object const { config, meta } = this.dumpConfigToObject(); @@ -589,7 +589,8 @@ export class EggApplicationCore extends EggCore { dumpTiming(): void { try { const items = this.timing.toJSON(); - const rundir = this.config.rundir; + const rundir = this.getRuntimeRundir(); + fs.mkdirSync(rundir, { recursive: true }); const dumpFile = path.join(rundir, `${this.type}_timing_${process.pid}.json`); fs.writeFileSync(dumpFile, CircularJSON.stringify(items, null, 2)); this.coreLogger.info(this.timing.toString()); @@ -641,27 +642,41 @@ export class EggApplicationCore extends EggCore { } #setupTimeoutTimer(): void { + const workerStartTimeout = this.getWorkerStartTimeout(); const startTimeoutTimer = setTimeout(() => { this.coreLogger.error(this.timing.toString()); - this.coreLogger.error(`${this.type} still doesn't ready after ${this.config.workerStartTimeout} ms.`); + this.coreLogger.error(`${this.type} still doesn't ready after ${workerStartTimeout} ms.`); // log unfinished const items = this.timing.toJSON(); for (const item of items) { if (item.end) continue; this.coreLogger.error(`unfinished timing item: ${CircularJSON.stringify(item)}`); } - this.coreLogger.error( - '[egg][setupTimeoutTimer] check run/%s_timing_%s.json for more details.', - this.type, - process.pid, - ); + const dumpTimingFile = path.join(this.getRuntimeRundir(), `${this.type}_timing_${process.pid}.json`); + this.coreLogger.error('[egg][setupTimeoutTimer] check %s for more details.', dumpTimingFile); this.emit('startTimeout'); this.dumpConfig(); this.dumpTiming(); - }, this.config.workerStartTimeout); + }, workerStartTimeout); this.ready(() => clearTimeout(startTimeoutTimer)); } + protected getRuntimeRundir(): string { + const rundir = this.config.rundir; + if (typeof rundir === 'string' && rundir.length > 0) { + return rundir; + } + return path.join(this.baseDir, 'run'); + } + + private getWorkerStartTimeout(): number { + const workerStartTimeout = this.config.workerStartTimeout; + if (typeof workerStartTimeout === 'number' && Number.isFinite(workerStartTimeout) && workerStartTimeout > 0) { + return workerStartTimeout; + } + return DEFAULT_WORKER_START_TIMEOUT; + } + get config() { return super.config as EggAppConfig; } diff --git a/packages/egg/test/egg.test.ts b/packages/egg/test/egg.test.ts index 9d4f7ecda2..e99d4c3876 100644 --- a/packages/egg/test/egg.test.ts +++ b/packages/egg/test/egg.test.ts @@ -263,6 +263,53 @@ describe.sequential('test/egg.test.ts', () => { }); }); + describe('runtime diagnostics fallback config', () => { + const baseDir = getFilepath('apps/dumpconfig'); + const runDir = path.join(baseDir, 'run'); + let app: MockApplication; + + beforeAll(async () => { + app = createApp('apps/dumpconfig'); + await app.ready(); + }); + + afterAll(() => app.close()); + + it('should dump config and timing to baseDir/run when rundir is missing', () => { + fs.rmSync(runDir, { recursive: true, force: true }); + const originalRundir = app.config.rundir; + Reflect.set(app.config, 'rundir', undefined); + try { + app.dumpConfig(); + app.dumpTiming(); + } finally { + Reflect.set(app.config, 'rundir', originalRundir); + } + + assertFile(path.join(runDir, 'application_config.json')); + assertFile(path.join(runDir, `application_timing_${process.pid}.json`)); + assertFile(path.join(runDir, 'router.json')); + }); + + it('should use the default worker start timeout when config is missing or invalid', () => { + const originalWorkerStartTimeout = app.config.workerStartTimeout; + try { + Reflect.set(app.config, 'workerStartTimeout', undefined); + assert.equal((app as any).getWorkerStartTimeout(), 10 * 60 * 1000); + Reflect.set(app.config, 'workerStartTimeout', 0); + assert.equal((app as any).getWorkerStartTimeout(), 10 * 60 * 1000); + Reflect.set(app.config, 'workerStartTimeout', -1); + assert.equal((app as any).getWorkerStartTimeout(), 10 * 60 * 1000); + Reflect.set(app.config, 'workerStartTimeout', Number.POSITIVE_INFINITY); + assert.equal((app as any).getWorkerStartTimeout(), 10 * 60 * 1000); + Reflect.set(app.config, 'workerStartTimeout', 1); + assert.equal((app as any).getWorkerStartTimeout(), 1); + } finally { + Reflect.set(app.config, 'workerStartTimeout', originalWorkerStartTimeout); + } + }); + }); + describe('custom config from env', () => { let app: MockApplication; let baseDir: string;