@yolo-croket-dev/core 1.7.25-slowquery.12 → 1.7.25-slowquery.2

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.
@@ -44,7 +44,6 @@ const rxjs_1 = require("rxjs");
44
44
  const operators_1 = require("rxjs/operators");
45
45
  const msa_logger_1 = __importStar(require("@yolo-croket-dev/msa-logger"));
46
46
  const amqplib_1 = __importDefault(require("amqplib"));
47
- const abortable_promise_1 = require("@xuchaoqian/abortable-promise");
48
47
  // import Logger from '@yolo-croket-dev/logger';
49
48
  const functions_1 = require("../functions");
50
49
  const exceptions_1 = require("../exceptions");
@@ -180,15 +179,7 @@ let RPCInterceptor = class RPCInterceptor {
180
179
  const arg = args[0];
181
180
  const rmq = args[1];
182
181
  // 메시지에서 AbortController 가져오기
183
- // const abortController = (rmq as any).args[0].abortController;
184
- const abortController = new AbortController();
185
- const handler = context.getHandler();
186
- console.log('args: ', args);
187
- console.log('context: ', context);
188
- const abortablePromise = new abortable_promise_1.AbortablePromise((...args) => handler(...args), abortController.signal);
189
- context.handler = abortablePromise;
190
- console.log('handler: ', handler);
191
- console.log('context: ', context);
182
+ const abortController = rmq.args[0].abortController;
192
183
  // const messageId = (rmq as any).messageId;
193
184
  // if (!abortController) {
194
185
  // // AbortController가 없으면 기본 처리
@@ -359,11 +350,23 @@ let RPCInterceptor = class RPCInterceptor {
359
350
  return next
360
351
  .handle()
361
352
  .pipe(
353
+ // AbortController 신호 감지
354
+ (0, operators_1.takeUntil)(new rxjs_1.Observable(subscriber => {
355
+ const abortHandler = () => {
356
+ console.log('RPC Interceptor abortHandler called');
357
+ subscriber.next();
358
+ subscriber.complete();
359
+ };
360
+ abortController.signal.addEventListener('abort', abortHandler, { once: true });
361
+ // 정리 함수
362
+ return () => {
363
+ abortController.signal.removeEventListener('abort', abortHandler);
364
+ };
365
+ })),
362
366
  // timeout(1000 * 60 * 29), // 30분 타임아웃
363
- (0, operators_1.timeout)(1000 * 3), // 30분 타임아웃
367
+ (0, operators_1.timeout)(1000 * 30), // 30분 타임아웃
364
368
  (0, operators_1.tap)({
365
369
  next: async (val) => {
366
- console.log('RPC Interceptor next called');
367
370
  val.isError = false;
368
371
  // const duration = Date.now() - now;
369
372
  // logger.debug(`<= (success:${traceId}): ${val.message}`, {
@@ -391,6 +394,7 @@ let RPCInterceptor = class RPCInterceptor {
391
394
  if (isTimeoutError) {
392
395
  if (abortController) {
393
396
  abortController.abort();
397
+ throw new Error('Message aborted due to timeout');
394
398
  }
395
399
  msaLogger.error('[타임 아웃] 메시지 재전송 처리', { traceId: execInterceptor.traceId }, {
396
400
  error: {
@@ -1,3 +1,5 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
1
3
  import { RmqOptions, ServerRMQ } from '@nestjs/microservices';
2
4
  import { Channel } from 'amqp-connection-manager';
3
5
  import { ConsumeMessage } from 'amqplib';
@@ -12,9 +14,14 @@ export declare class GracefulServerRMQ extends ServerRMQ {
12
14
  protected consumerTag: string | null;
13
15
  protected readonly waitingEndingHandlersTimeoutMs: number;
14
16
  protected readonly waitingEndingHandlersIntervalMs: number;
17
+ protected messageAbortControllers: Map<string, AbortController>;
18
+ protected messageTimeouts: Map<string, NodeJS.Timeout>;
15
19
  constructor(options: GracefulRmqOptions);
16
20
  setupChannel(channel: Channel, callback: () => void): Promise<void>;
17
21
  protected waitingHandlers(): Promise<void>;
18
22
  handleMessage(message: ConsumeMessage, channel: Channel): Promise<void>;
23
+ private cleanupMessage;
24
+ abortMessage(messageId: string): void;
25
+ abortAllMessages(): void;
19
26
  close(): Promise<void>;
20
27
  }
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GracefulServerRMQ = void 0;
4
4
  const microservices_1 = require("@nestjs/microservices");
5
5
  const node_util_1 = require("node:util");
6
- const abortable_promise_1 = require("@xuchaoqian/abortable-promise");
7
6
  const sleep = (0, node_util_1.promisify)(setTimeout);
8
7
  const RMQ_DEFAULT_WAITING_ENDING_HANDLERS_TIMEOUT_MS = 5000;
9
8
  const RMQ_DEFAULT_WAITING_ENDING_HANDLERS_INTERVAL_MS = 500;
@@ -16,6 +15,10 @@ class GracefulServerRMQ extends microservices_1.ServerRMQ {
16
15
  this.runningMessages = 0;
17
16
  this.closing = false;
18
17
  this.consumerTag = null;
18
+ // 메시지별 AbortController 관리
19
+ this.messageAbortControllers = new Map();
20
+ // eslint-disable-next-line no-undef
21
+ this.messageTimeouts = new Map();
19
22
  this.waitingEndingHandlersTimeoutMs = this.getOptionsProp(this.options, 'waitingEndingHandlersTimeoutMs')
20
23
  || RMQ_DEFAULT_WAITING_ENDING_HANDLERS_TIMEOUT_MS;
21
24
  this.waitingEndingHandlersIntervalMs = this.getOptionsProp(this.options, 'waitingEndingHandlersIntervalMs')
@@ -42,12 +45,14 @@ class GracefulServerRMQ extends microservices_1.ServerRMQ {
42
45
  const messageId = message.properties.messageId || message.properties.correlationId || `msg_${Date.now()}_${Math.random()}`;
43
46
  // AbortController 생성
44
47
  const abortController = new AbortController();
48
+ this.messageAbortControllers.set(messageId, abortController);
45
49
  // 타임아웃 설정
46
- setTimeout(() => {
50
+ const timeoutId = setTimeout(() => {
47
51
  console.log(`Message timeout: ${messageId}`);
48
- abortController.abort(); // Abort with default reason
52
+ abortController.abort();
53
+ this.cleanupMessage(messageId);
49
54
  }, RMQ_DEFAULT_MESSAGE_TIMEOUT_MS);
50
- console.log('timeout 설정');
55
+ this.messageTimeouts.set(messageId, timeoutId);
51
56
  this.runningMessages += 1;
52
57
  try {
53
58
  // AbortController를 메시지에 추가
@@ -58,23 +63,26 @@ class GracefulServerRMQ extends microservices_1.ServerRMQ {
58
63
  // abort 상태 확인
59
64
  if (abortController.signal.aborted) {
60
65
  console.log(`Message already aborted: ${messageId}`);
66
+ this.cleanupMessage(messageId);
61
67
  this.runningMessages -= 1;
62
68
  return;
63
69
  }
64
- const abortablePromise = new abortable_promise_1.AbortablePromise((resolve, reject) => {
65
- try {
66
- super.handleMessage(message, channel);
67
- }
68
- catch (err) {
69
- reject(err);
70
- }
71
- }, abortController.signal);
72
- abortablePromise.then((val) => {
73
- console.log('handleMessage success', val);
74
- }).catch((err) => {
75
- console.log('handleMessage error', err);
76
- throw err;
70
+ // Promise.race를 사용하여 abort와 handleMessage 경쟁
71
+ const handleMessagePromise = super.handleMessage(message, channel);
72
+ const abortPromise = new Promise((_, reject) => {
73
+ // 1초마다 abort 상태를 확인
74
+ const interval = setInterval(() => {
75
+ if (abortController.signal.aborted) {
76
+ reject(new Error(`Message aborted: ${messageId}`));
77
+ clearInterval(interval);
78
+ }
79
+ }, 1000);
77
80
  });
81
+ // 둘 중 하나가 먼저 완료되면 그 결과를 반환
82
+ await Promise.race([
83
+ handleMessagePromise,
84
+ abortPromise,
85
+ ]);
78
86
  }
79
87
  catch (error) {
80
88
  if (error.message.includes('Message aborted')) {
@@ -86,11 +94,39 @@ class GracefulServerRMQ extends microservices_1.ServerRMQ {
86
94
  }
87
95
  }
88
96
  finally {
97
+ this.cleanupMessage(messageId);
89
98
  this.runningMessages -= 1;
90
99
  }
91
100
  }
101
+ cleanupMessage(messageId) {
102
+ // 타임아웃 정리
103
+ const timeoutId = this.messageTimeouts.get(messageId);
104
+ if (timeoutId) {
105
+ clearTimeout(timeoutId);
106
+ this.messageTimeouts.delete(messageId);
107
+ }
108
+ // AbortController 정리
109
+ this.messageAbortControllers.delete(messageId);
110
+ }
111
+ // 특정 메시지 강제 중단
112
+ abortMessage(messageId) {
113
+ const abortController = this.messageAbortControllers.get(messageId);
114
+ if (abortController) {
115
+ abortController.abort();
116
+ this.cleanupMessage(messageId);
117
+ }
118
+ }
119
+ // 모든 실행 중인 메시지 중단
120
+ abortAllMessages() {
121
+ for (const [messageId, abortController,] of this.messageAbortControllers) {
122
+ abortController.abort();
123
+ this.cleanupMessage(messageId);
124
+ }
125
+ }
92
126
  async close() {
93
127
  this.closing = true;
128
+ // 모든 실행 중인 메시지 중단
129
+ this.abortAllMessages();
94
130
  if (this.channel) {
95
131
  await this.channel.removeSetup(undefined, (channel) => channel.cancel(this.consumerTag));
96
132
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yolo-croket-dev/core",
3
- "version": "1.7.25-slowquery.12",
3
+ "version": "1.7.25-slowquery.2",
4
4
  "author": "Yolo Co., Ltd.",
5
5
  "description": "Yolo commonization module",
6
6
  "main": "index.js",
@@ -15,7 +15,6 @@
15
15
  "license": "UNLICENSED",
16
16
  "dependencies": {
17
17
  "@nestjs/common": "^8.2.3",
18
- "@xuchaoqian/abortable-promise": "^1.11.2",
19
18
  "@yolo-croket-dev/logger": "^1.0.15",
20
19
  "@yolo-croket-dev/msa-logger": "^3.0.4",
21
20
  "@yolo-croket-dev/redis": "^1.0.23",