@yolo-croket-dev/core 1.7.25-slowquery.0 → 1.7.25-slowquery.10
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.
|
@@ -179,7 +179,7 @@ let RPCInterceptor = class RPCInterceptor {
|
|
|
179
179
|
const arg = args[0];
|
|
180
180
|
const rmq = args[1];
|
|
181
181
|
// 메시지에서 AbortController 가져오기
|
|
182
|
-
const abortController = rmq.abortController;
|
|
182
|
+
const abortController = rmq.args[0].abortController;
|
|
183
183
|
// const messageId = (rmq as any).messageId;
|
|
184
184
|
// if (!abortController) {
|
|
185
185
|
// // AbortController가 없으면 기본 처리
|
|
@@ -349,23 +349,25 @@ let RPCInterceptor = class RPCInterceptor {
|
|
|
349
349
|
// eslint-disable-next-line consistent-return
|
|
350
350
|
return next
|
|
351
351
|
.handle()
|
|
352
|
-
.pipe(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
//
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
|
|
352
|
+
.pipe((0, operators_1.switchMap)((val) => {
|
|
353
|
+
console.log('switchMap called', val);
|
|
354
|
+
if (abortController === null || abortController === void 0 ? void 0 : abortController.signal.aborted) {
|
|
355
|
+
console.log('RPC Interceptor: Service execution aborted');
|
|
356
|
+
return (0, rxjs_1.throwError)(() => new Error('Service execution aborted'));
|
|
357
|
+
}
|
|
358
|
+
// // AbortController를 컨텍스트에 주입
|
|
359
|
+
// const request = context.switchToRpc().getData();
|
|
360
|
+
// if (request && typeof request === 'object') {
|
|
361
|
+
// (request as any).abortController = abortController;
|
|
362
|
+
// (request as any).abortSignal = abortController?.signal;
|
|
363
|
+
// }
|
|
364
|
+
return (0, rxjs_1.of)(val);
|
|
365
|
+
}),
|
|
365
366
|
// timeout(1000 * 60 * 29), // 30분 타임아웃
|
|
366
|
-
(0, operators_1.timeout)(1000 *
|
|
367
|
+
(0, operators_1.timeout)(1000 * 3), // 30분 타임아웃
|
|
367
368
|
(0, operators_1.tap)({
|
|
368
369
|
next: async (val) => {
|
|
370
|
+
console.log('RPC Interceptor next called');
|
|
369
371
|
val.isError = false;
|
|
370
372
|
// const duration = Date.now() - now;
|
|
371
373
|
// logger.debug(`<= (success:${traceId}): ${val.message}`, {
|
|
@@ -391,7 +393,8 @@ let RPCInterceptor = class RPCInterceptor {
|
|
|
391
393
|
// timeout 에러 처리
|
|
392
394
|
const isTimeoutError = err instanceof Error && err.name === 'TimeoutError';
|
|
393
395
|
if (isTimeoutError) {
|
|
394
|
-
if (abortController
|
|
396
|
+
if (abortController) {
|
|
397
|
+
abortController.abort();
|
|
395
398
|
throw new Error('Message aborted due to timeout');
|
|
396
399
|
}
|
|
397
400
|
msaLogger.error('[타임 아웃] 메시지 재전송 처리', { traceId: execInterceptor.traceId }, {
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
1
|
import { RmqOptions, ServerRMQ } from '@nestjs/microservices';
|
|
4
2
|
import { Channel } from 'amqp-connection-manager';
|
|
5
3
|
import { ConsumeMessage } from 'amqplib';
|
|
@@ -14,14 +12,9 @@ export declare class GracefulServerRMQ extends ServerRMQ {
|
|
|
14
12
|
protected consumerTag: string | null;
|
|
15
13
|
protected readonly waitingEndingHandlersTimeoutMs: number;
|
|
16
14
|
protected readonly waitingEndingHandlersIntervalMs: number;
|
|
17
|
-
protected messageAbortControllers: Map<string, AbortController>;
|
|
18
|
-
protected messageTimeouts: Map<string, NodeJS.Timeout>;
|
|
19
15
|
constructor(options: GracefulRmqOptions);
|
|
20
16
|
setupChannel(channel: Channel, callback: () => void): Promise<void>;
|
|
21
17
|
protected waitingHandlers(): Promise<void>;
|
|
22
18
|
handleMessage(message: ConsumeMessage, channel: Channel): Promise<void>;
|
|
23
|
-
private cleanupMessage;
|
|
24
|
-
abortMessage(messageId: string): void;
|
|
25
|
-
abortAllMessages(): void;
|
|
26
19
|
close(): Promise<void>;
|
|
27
20
|
}
|
|
@@ -3,6 +3,7 @@ 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");
|
|
6
7
|
const sleep = (0, node_util_1.promisify)(setTimeout);
|
|
7
8
|
const RMQ_DEFAULT_WAITING_ENDING_HANDLERS_TIMEOUT_MS = 5000;
|
|
8
9
|
const RMQ_DEFAULT_WAITING_ENDING_HANDLERS_INTERVAL_MS = 500;
|
|
@@ -15,10 +16,6 @@ class GracefulServerRMQ extends microservices_1.ServerRMQ {
|
|
|
15
16
|
this.runningMessages = 0;
|
|
16
17
|
this.closing = false;
|
|
17
18
|
this.consumerTag = null;
|
|
18
|
-
// 메시지별 AbortController 관리
|
|
19
|
-
this.messageAbortControllers = new Map();
|
|
20
|
-
// eslint-disable-next-line no-undef
|
|
21
|
-
this.messageTimeouts = new Map();
|
|
22
19
|
this.waitingEndingHandlersTimeoutMs = this.getOptionsProp(this.options, 'waitingEndingHandlersTimeoutMs')
|
|
23
20
|
|| RMQ_DEFAULT_WAITING_ENDING_HANDLERS_TIMEOUT_MS;
|
|
24
21
|
this.waitingEndingHandlersIntervalMs = this.getOptionsProp(this.options, 'waitingEndingHandlersIntervalMs')
|
|
@@ -45,56 +42,50 @@ class GracefulServerRMQ extends microservices_1.ServerRMQ {
|
|
|
45
42
|
const messageId = message.properties.messageId || message.properties.correlationId || `msg_${Date.now()}_${Math.random()}`;
|
|
46
43
|
// AbortController 생성
|
|
47
44
|
const abortController = new AbortController();
|
|
48
|
-
this.messageAbortControllers.set(messageId, abortController);
|
|
49
45
|
// 타임아웃 설정
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
console.log(`Message timeout: ${messageId}`);
|
|
48
|
+
abortController.abort(); // Abort with default reason
|
|
53
49
|
}, RMQ_DEFAULT_MESSAGE_TIMEOUT_MS);
|
|
54
|
-
|
|
50
|
+
console.log('timeout 설정');
|
|
55
51
|
this.runningMessages += 1;
|
|
56
52
|
try {
|
|
57
53
|
// AbortController를 메시지에 추가
|
|
58
54
|
// eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any
|
|
59
55
|
message.abortController = abortController;
|
|
60
|
-
// eslint-disable-next-line no-param-reassign
|
|
56
|
+
// eslint-disable-next-line no-param-reassign
|
|
61
57
|
message.messageId = messageId;
|
|
62
|
-
|
|
58
|
+
// abort 상태 확인
|
|
59
|
+
if (abortController.signal.aborted) {
|
|
60
|
+
console.log(`Message already aborted: ${messageId}`);
|
|
61
|
+
this.runningMessages -= 1;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
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
|
+
await abortablePromise;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error.message.includes('Message aborted')) {
|
|
76
|
+
console.log(`Message processing aborted: ${messageId}`);
|
|
77
|
+
// abort로 인한 에러는 정상적으로 처리된 것으로 간주
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
63
82
|
}
|
|
64
83
|
finally {
|
|
65
|
-
this.cleanupMessage(messageId);
|
|
66
84
|
this.runningMessages -= 1;
|
|
67
85
|
}
|
|
68
86
|
}
|
|
69
|
-
cleanupMessage(messageId) {
|
|
70
|
-
// 타임아웃 정리
|
|
71
|
-
const timeoutId = this.messageTimeouts.get(messageId);
|
|
72
|
-
if (timeoutId) {
|
|
73
|
-
clearTimeout(timeoutId);
|
|
74
|
-
this.messageTimeouts.delete(messageId);
|
|
75
|
-
}
|
|
76
|
-
// AbortController 정리
|
|
77
|
-
this.messageAbortControllers.delete(messageId);
|
|
78
|
-
}
|
|
79
|
-
// 특정 메시지 강제 중단
|
|
80
|
-
abortMessage(messageId) {
|
|
81
|
-
const abortController = this.messageAbortControllers.get(messageId);
|
|
82
|
-
if (abortController) {
|
|
83
|
-
abortController.abort();
|
|
84
|
-
this.cleanupMessage(messageId);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
// 모든 실행 중인 메시지 중단
|
|
88
|
-
abortAllMessages() {
|
|
89
|
-
for (const [messageId, abortController,] of this.messageAbortControllers) {
|
|
90
|
-
abortController.abort();
|
|
91
|
-
this.cleanupMessage(messageId);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
87
|
async close() {
|
|
95
88
|
this.closing = true;
|
|
96
|
-
// 모든 실행 중인 메시지 중단
|
|
97
|
-
this.abortAllMessages();
|
|
98
89
|
if (this.channel) {
|
|
99
90
|
await this.channel.removeSetup(undefined, (channel) => channel.cancel(this.consumerTag));
|
|
100
91
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yolo-croket-dev/core",
|
|
3
|
-
"version": "1.7.25-slowquery.
|
|
3
|
+
"version": "1.7.25-slowquery.10",
|
|
4
4
|
"author": "Yolo Co., Ltd.",
|
|
5
5
|
"description": "Yolo commonization module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"license": "UNLICENSED",
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@nestjs/common": "^8.2.3",
|
|
18
|
+
"@xuchaoqian/abortable-promise": "^1.11.2",
|
|
18
19
|
"@yolo-croket-dev/logger": "^1.0.15",
|
|
19
20
|
"@yolo-croket-dev/msa-logger": "^3.0.4",
|
|
20
21
|
"@yolo-croket-dev/redis": "^1.0.23",
|