@yolo-croket-dev/core 1.7.22 → 1.7.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -365,20 +365,33 @@ let RPCInterceptor = class RPCInterceptor {
365
365
  // eslint-disable-next-line consistent-return
366
366
  (0, operators_1.catchError)(async (err) => {
367
367
  // timeout 에러 처리
368
- msaLogger.error('Timeout occurred, requeuing message', { traceId: execInterceptor.traceId }, {
369
- error: {
370
- message: err.message,
371
- stack: err.stack,
372
- name: err.name,
373
- response: err.response,
374
- },
375
- });
376
368
  const isTimeoutError = err instanceof Error && err.name === 'TimeoutError';
377
- if (isTimeoutError && !execInterceptor.isMessageAck) {
378
- channel.nack(message, false, true);
379
- execInterceptor.isMessageAck = true;
380
- message.properties.replyTo = undefined; /** replyTo가 존재하면 sendToQueue가 호출되므로, 삭제해주기 */
381
- msaLogger.debug('TimeoutError occurred, message requeued called', { traceId: execInterceptor.traceId });
369
+ if (isTimeoutError) {
370
+ msaLogger.error('[타임 아웃] 메시지 재전송 처리', { traceId: execInterceptor.traceId }, {
371
+ error: {
372
+ message: err.message,
373
+ stack: err.stack,
374
+ name: err.name,
375
+ response: err.response,
376
+ },
377
+ });
378
+ if (!execInterceptor.isMessageAck) {
379
+ channel.nack(message, false, true);
380
+ execInterceptor.isMessageAck = true;
381
+ message.properties.replyTo = undefined; /** replyTo가 존재하면 sendToQueue가 호출되므로, 삭제해주기 */
382
+ msaLogger.debug('TimeoutError occurred, message requeued called', { traceId: execInterceptor.traceId });
383
+ return new rxjs_1.Observable();
384
+ }
385
+ }
386
+ else {
387
+ msaLogger.error('에러 응답 전송 처리', { traceId: execInterceptor.traceId }, {
388
+ error: {
389
+ message: err.message,
390
+ stack: err.stack,
391
+ name: err.name,
392
+ response: err.response,
393
+ },
394
+ });
382
395
  return new rxjs_1.Observable();
383
396
  }
384
397
  }), (0, operators_1.finalize)(() => {
@@ -1,2 +1,7 @@
1
- export declare class GracefulShutdownModule {
1
+ import { OnModuleDestroy } from '@nestjs/common';
2
+ import { GracefulShutdownService } from './graceful-shutdown.service';
3
+ export declare class GracefulShutdownModule implements OnModuleDestroy {
4
+ private readonly gracefulShutdownService;
5
+ constructor(gracefulShutdownService: GracefulShutdownService);
6
+ onModuleDestroy(): Promise<void>;
2
7
  }
@@ -5,11 +5,25 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
6
  return c > 3 && r && Object.defineProperty(target, key, r), r;
7
7
  };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
8
11
  Object.defineProperty(exports, "__esModule", { value: true });
9
12
  exports.GracefulShutdownModule = void 0;
10
13
  const common_1 = require("@nestjs/common");
11
14
  const graceful_shutdown_service_1 = require("./graceful-shutdown.service");
12
15
  let GracefulShutdownModule = class GracefulShutdownModule {
16
+ constructor(gracefulShutdownService) {
17
+ this.gracefulShutdownService = gracefulShutdownService;
18
+ }
19
+ async onModuleDestroy() {
20
+ // GracefulShutdownService가 완료될 때까지 대기
21
+ while (this.gracefulShutdownService.getActiveInterceptorsCount() > 0) {
22
+ await new Promise(resolve => {
23
+ setTimeout(() => resolve(), 100);
24
+ });
25
+ }
26
+ }
13
27
  };
14
28
  exports.GracefulShutdownModule = GracefulShutdownModule;
15
29
  exports.GracefulShutdownModule = GracefulShutdownModule = __decorate([
@@ -17,5 +31,6 @@ exports.GracefulShutdownModule = GracefulShutdownModule = __decorate([
17
31
  (0, common_1.Module)({
18
32
  providers: [graceful_shutdown_service_1.GracefulShutdownService],
19
33
  exports: [graceful_shutdown_service_1.GracefulShutdownService],
20
- })
34
+ }),
35
+ __metadata("design:paramtypes", [graceful_shutdown_service_1.GracefulShutdownService])
21
36
  ], GracefulShutdownModule);
@@ -9,10 +9,15 @@ export declare class GracefulShutdownService implements OnApplicationShutdown, O
9
9
  private activeInterceptors;
10
10
  isApplicationExit: boolean;
11
11
  private readonly HEALTHY_FILE_PATH;
12
+ private shutdownSignal?;
13
+ private isShutdownInProgress;
12
14
  onApplicationBootstrap(): void;
13
15
  pushActiveInterceptors(execInterceptor: GracefulExecInterceptor): void;
14
16
  popActiveInterceptors(execInterceptor: GracefulExecInterceptor): void;
17
+ getActiveInterceptorsCount(): number;
18
+ getShutdownStatus(): boolean;
15
19
  onApplicationShutdown(signal?: string): Promise<void>;
16
20
  onModuleDestroy(): Promise<void>;
21
+ private waitForActiveInterceptors;
17
22
  }
18
23
  export {};
@@ -39,6 +39,7 @@ let GracefulShutdownService = class GracefulShutdownService {
39
39
  this.activeInterceptors = [];
40
40
  this.isApplicationExit = false;
41
41
  this.HEALTHY_FILE_PATH = '/tmp/healthy';
42
+ this.isShutdownInProgress = false;
42
43
  }
43
44
  onApplicationBootstrap() {
44
45
  this.activeInterceptors = [];
@@ -54,6 +55,7 @@ let GracefulShutdownService = class GracefulShutdownService {
54
55
  // Health check 용 파일 생성
55
56
  (0, fs_1.writeFileSync)(this.HEALTHY_FILE_PATH, (new Date().toISOString()));
56
57
  const isHealthyFileExist = (0, fs_1.existsSync)(this.HEALTHY_FILE_PATH);
58
+ // eslint-disable-next-line max-len
57
59
  msaLogger.debug(`GracefulShutdownService.onApplicationBootstrap isExecLocal=${this.isExecLocal}`, { traceId: 'ON_APPLICATION_BOOTSTRAP' }, { isHealthyFileExist });
58
60
  }
59
61
  pushActiveInterceptors(execInterceptor) {
@@ -63,35 +65,76 @@ let GracefulShutdownService = class GracefulShutdownService {
63
65
  // eslint-disable-next-line max-len
64
66
  this.activeInterceptors = this.activeInterceptors.filter((item) => !(item.method === execInterceptor.method && item.traceId === execInterceptor.traceId && item.correlationId === execInterceptor.correlationId));
65
67
  }
68
+ getActiveInterceptorsCount() {
69
+ return this.activeInterceptors.length;
70
+ }
71
+ getShutdownStatus() {
72
+ return this.isShutdownInProgress;
73
+ }
66
74
  async onApplicationShutdown(signal) {
75
+ if (this.isShutdownInProgress) {
76
+ msaLogger.debug(`GracefulShutdownService.onApplicationShutdown already in progress, signal=${signal}`, { traceId: 'ON_APPLICATION_SHUTDOWN' });
77
+ return;
78
+ }
79
+ this.isShutdownInProgress = true;
80
+ this.shutdownSignal = signal;
81
+ this.isApplicationExit = true;
67
82
  msaLogger.debug(`GracefulShutdownService.onApplicationShutdown signal=${signal}`, { traceId: 'ON_APPLICATION_SHUTDOWN' });
83
+ // onApplicationShutdown에서 모든 activeInterceptors 완료를 기다림
84
+ // 이렇게 하면 onModuleDestroy가 호출되기 전에 모든 이벤트가 처리됨
85
+ await this.waitForActiveInterceptors();
86
+ msaLogger.info('GracefulShutdownService.onApplicationShutdown: all interceptors finished, shutting down', { traceId: 'ON_APPLICATION_SHUTDOWN' });
87
+ // 모든 이벤트 처리 완료 후 종료
68
88
  process.exit(0);
69
89
  }
70
90
  async onModuleDestroy() {
71
- this.isApplicationExit = true;
72
91
  msaLogger.debug('GracefulShutdownService.onModuleDestroy', { traceId: 'ON_MODULE_DESTROY' });
92
+ // onModuleDestroy에서는 단순히 플래그만 설정
93
+ // 실제 대기는 onApplicationShutdown에서 처리됨
94
+ this.isApplicationExit = true;
95
+ }
96
+ async waitForActiveInterceptors() {
73
97
  if (this.isExecLocal) {
74
- msaLogger.debug('GracefulShutdownService.onModuleDestroy isExecLocal=true, process exit', { traceId: 'ON_MODULE_DESTROY' });
98
+ msaLogger.debug('GracefulShutdownService.waitForActiveInterceptors isExecLocal=true', { traceId: 'WAIT_FOR_INTERCEPTORS' });
75
99
  return;
76
100
  }
77
101
  // eslint-disable-next-line max-len
78
- msaLogger.debug('GracefulShutdownService.onModuleDestroy activeInterceptors', { traceId: 'ON_MODULE_DESTROY' }, { activeInterceptors: this.activeInterceptors });
102
+ msaLogger.debug('GracefulShutdownService.waitForActiveInterceptors activeInterceptors', { traceId: 'WAIT_FOR_INTERCEPTORS' }, { activeInterceptors: this.activeInterceptors });
79
103
  let prevExecInterceptorCount = 0;
80
104
  let count = 0;
81
- while (this.activeInterceptors.length) {
105
+ const maxWaitTime = 1000 * 60 * 10; // 최대 10분 대기
106
+ const startTime = Date.now();
107
+ while (this.activeInterceptors.length > 0) {
82
108
  count += 1;
109
+ // 최대 대기 시간 체크
110
+ if (Date.now() - startTime > maxWaitTime) {
111
+ // eslint-disable-next-line max-len
112
+ msaLogger.warn('GracefulShutdownService.waitForActiveInterceptors: max wait time exceeded, forcing shutdown', { traceId: 'WAIT_FOR_INTERCEPTORS' }, {
113
+ activeInterceptors: this.activeInterceptors,
114
+ count,
115
+ maxWaitTime,
116
+ });
117
+ break;
118
+ }
83
119
  if ((prevExecInterceptorCount !== this.activeInterceptors.length) || (count % 100 === 0)) {
84
120
  prevExecInterceptorCount = this.activeInterceptors.length;
85
121
  // eslint-disable-next-line max-len
86
- msaLogger.debug('GracefulShutdownService.onModuleDestroy: waiting for interceptors to finish', { traceId: 'ON_MODULE_DESTROY' }, {
122
+ msaLogger.debug('GracefulShutdownService.waitForActiveInterceptors: waiting for interceptors to finish', { traceId: 'WAIT_FOR_INTERCEPTORS' }, {
87
123
  activeInterceptors: this.activeInterceptors,
88
124
  count,
125
+ remainingCount: this.activeInterceptors.length,
126
+ elapsedTime: Date.now() - startTime,
89
127
  });
90
128
  }
91
129
  await new Promise((resolve) => {
92
130
  setTimeout(resolve, 1000);
93
131
  });
94
132
  }
133
+ msaLogger.info('GracefulShutdownService.waitForActiveInterceptors: all interceptors finished', { traceId: 'WAIT_FOR_INTERCEPTORS' }, {
134
+ finalCount: count,
135
+ shutdownSignal: this.shutdownSignal,
136
+ totalElapsedTime: Date.now() - startTime,
137
+ });
95
138
  }
96
139
  };
97
140
  exports.GracefulShutdownService = GracefulShutdownService;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yolo-croket-dev/core",
3
- "version": "1.7.22",
3
+ "version": "1.7.24",
4
4
  "author": "Yolo Co., Ltd.",
5
5
  "description": "Yolo commonization module",
6
6
  "main": "index.js",