@yolo-croket-dev/core 1.10.3 → 1.10.5
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.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.10.5
|
|
4
|
+
|
|
5
|
+
- 종료 중 RPC의 consumer 취소나 메시지 반환이 실패해도 기존 Pod의 오류·완료 응답으로 전파하지 않습니다.
|
|
6
|
+
- 취소 실패 후에도 메시지 반환을 시도하며 실패한 작업은 요청 traceId와 정리된 진단 정보로 한 번 기록합니다.
|
|
7
|
+
|
|
8
|
+
## 1.10.4
|
|
9
|
+
|
|
10
|
+
- 종료 중 consumer 취소나 채널 설정이 실패해도 기존 RPC 완료 대기와 최종 연결 정리를 계속합니다.
|
|
11
|
+
- 취소 실패는 민감 정보가 제거된 진단 로그로 한 번 기록합니다.
|
|
12
|
+
|
|
3
13
|
## 1.10.3
|
|
4
14
|
|
|
5
15
|
- 종료 시작 시 등록한 RMQ consumer를 먼저 중단하고 실행 중인 RPC는 완료까지 기다립니다.
|
|
@@ -148,10 +148,39 @@ let RPCInterceptor = class RPCInterceptor {
|
|
|
148
148
|
}
|
|
149
149
|
return obj;
|
|
150
150
|
}
|
|
151
|
-
async requeueMessageOnShutdown(channel, message) {
|
|
151
|
+
async requeueMessageOnShutdown(channel, message, traceId) {
|
|
152
152
|
msaLogger.debug('RPCInterceptor isApplicationExit called', { traceId: 'IS_APPLICATION_EXIT' });
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
const failures = [];
|
|
154
|
+
try {
|
|
155
|
+
await channel.cancel(message.fields.consumerTag);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
failures.push({
|
|
159
|
+
operation: 'cancel',
|
|
160
|
+
diagnostic: (0, msa_logger_1.normalizeDiagnosticError)(err),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
channel.nack(message, false, true);
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
// 닫힌 채널의 미확인 메시지는 브로커가 재전달하므로 기존 Pod에서 오류 응답을 보내지 않는다.
|
|
168
|
+
failures.push({
|
|
169
|
+
operation: 'nack',
|
|
170
|
+
diagnostic: (0, msa_logger_1.normalizeDiagnosticError)(err),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
if (failures.length) {
|
|
174
|
+
msaLogger.warn('RPCInterceptor 종료 중 메시지 재전달 처리 실패', {
|
|
175
|
+
traceId,
|
|
176
|
+
diagnosticInfo: failures[0].diagnostic.diagnosticInfo,
|
|
177
|
+
}, {
|
|
178
|
+
failures: failures.map(({ operation, diagnostic }) => ({
|
|
179
|
+
operation,
|
|
180
|
+
...diagnostic.details,
|
|
181
|
+
})),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
155
184
|
// 기존 서버에서 응답을 완료하면 재전달된 요청의 실제 응답을 클라이언트가 기다릴 수 없다.
|
|
156
185
|
return new rxjs_1.Observable();
|
|
157
186
|
}
|
|
@@ -182,7 +211,7 @@ let RPCInterceptor = class RPCInterceptor {
|
|
|
182
211
|
let abortController = new AbortController();
|
|
183
212
|
/** consume 메시지 전부 cancel */
|
|
184
213
|
if (this.gracefulShutdownService.isApplicationExit) {
|
|
185
|
-
return this.requeueMessageOnShutdown(channel, message);
|
|
214
|
+
return this.requeueMessageOnShutdown(channel, message, execInterceptor.traceId);
|
|
186
215
|
}
|
|
187
216
|
if (!staticRetryCount && reDeliveryCount > 0) {
|
|
188
217
|
msaLogger.debug('RETRY_COUNT_ZERO_OR_NO_RETRY 발생 전, redelivery 발생', { traceId: ((_b = content.data.param) === null || _b === void 0 ? void 0 : _b.traceId) || 'RETRY_COUNT_ZERO_OR_NO_RETRY' }, {
|
|
@@ -195,7 +224,7 @@ let RPCInterceptor = class RPCInterceptor {
|
|
|
195
224
|
});
|
|
196
225
|
const isRedeliveryMessage = await this.isRedeliveryMessage(message.properties.replyTo, execInterceptor.traceId);
|
|
197
226
|
if (this.gracefulShutdownService.isApplicationExit) {
|
|
198
|
-
return this.requeueMessageOnShutdown(channel, message);
|
|
227
|
+
return this.requeueMessageOnShutdown(channel, message, execInterceptor.traceId);
|
|
199
228
|
}
|
|
200
229
|
if (!isRedeliveryMessage || reDeliveryCount > 5) {
|
|
201
230
|
msaLogger.debug('isRedeliveryMessage RETRY_COUNT_ZERO_OR_NO_RETRY false', { traceId: ((_c = content.data.param) === null || _c === void 0 ? void 0 : _c.traceId) || 'RETRY_COUNT_ZERO_OR_NO_RETRY' });
|
|
@@ -368,7 +397,7 @@ let RPCInterceptor = class RPCInterceptor {
|
|
|
368
397
|
}
|
|
369
398
|
// 재전달 여부를 비동기로 확인하는 동안 종료가 시작될 수 있으므로 실행 직전에 다시 확인한다.
|
|
370
399
|
if (this.gracefulShutdownService.isApplicationExit) {
|
|
371
|
-
return this.requeueMessageOnShutdown(channel, message);
|
|
400
|
+
return this.requeueMessageOnShutdown(channel, message, execInterceptor.traceId);
|
|
372
401
|
}
|
|
373
402
|
this.gracefulShutdownService.pushActiveInterceptors(execInterceptor);
|
|
374
403
|
// eslint-disable-next-line consistent-return
|
|
@@ -1,14 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
2
25
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
27
|
};
|
|
5
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
29
|
exports.GracefulServerRMQ = void 0;
|
|
7
30
|
const microservices_1 = require("@nestjs/microservices");
|
|
31
|
+
const msa_logger_1 = __importStar(require("@yolo-croket-dev/msa-logger"));
|
|
8
32
|
const os_1 = __importDefault(require("os"));
|
|
9
33
|
const node_util_1 = require("node:util");
|
|
10
34
|
const rmq_diagnostics_function_1 = require("../../functions/rmq-diagnostics.function");
|
|
11
35
|
const sleep = (0, node_util_1.promisify)(setTimeout);
|
|
36
|
+
const msaLogger = new msa_logger_1.default(msa_logger_1.ServerTypeEnum.SERVICE);
|
|
12
37
|
const RMQ_DEFAULT_WAITING_ENDING_HANDLERS_TIMEOUT_MS = 5000;
|
|
13
38
|
const RMQ_DEFAULT_WAITING_ENDING_HANDLERS_INTERVAL_MS = 500;
|
|
14
39
|
const RMQ_UNKNOWN_CONNECTION_IDENTITY = 'unknown';
|
|
@@ -128,13 +153,25 @@ class GracefulServerRMQ extends microservices_1.ServerRMQ {
|
|
|
128
153
|
}
|
|
129
154
|
async cancelConsumer() {
|
|
130
155
|
const consumerTag = this.consumerTag;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
156
|
+
try {
|
|
157
|
+
if (this.channel) {
|
|
158
|
+
await this.channel.removeSetup(undefined, async (channel) => {
|
|
159
|
+
if (consumerTag)
|
|
160
|
+
await channel.cancel(consumerTag);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
// 취소 실패가 Nest 종료 훅을 중단하면 실행 중인 요청 완료 대기와 연결 정리가 생략된다.
|
|
166
|
+
const diagnostic = (0, msa_logger_1.normalizeDiagnosticError)(err);
|
|
167
|
+
msaLogger.warn('GracefulServerRMQ consumer 취소 실패: 기존 요청 완료 후 연결 정리', {
|
|
168
|
+
traceId: 'RMQ_SHUTDOWN_CANCEL_FAILED',
|
|
169
|
+
diagnosticInfo: diagnostic.diagnosticInfo,
|
|
170
|
+
}, diagnostic.details);
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
this.consumerTag = null;
|
|
136
174
|
}
|
|
137
|
-
this.consumerTag = null;
|
|
138
175
|
}
|
|
139
176
|
async close() {
|
|
140
177
|
var _a;
|
package/package.json
CHANGED
|
@@ -78,17 +78,35 @@ test('primitive and getter failures retain the original rejection and emit diagn
|
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
test('consumer
|
|
81
|
+
test('consumer setup removal failure is logged and native close still disposes the observer', async t => {
|
|
82
82
|
const flag = process.env.RMQ_DIAGNOSTICS_ENABLED;
|
|
83
83
|
process.env.RMQ_DIAGNOSTICS_ENABLED = 'true';
|
|
84
84
|
t.after(() => { if (flag === undefined) delete process.env.RMQ_DIAGNOSTICS_ENABLED; else process.env.RMQ_DIAGNOSTICS_ENABLED = flag; });
|
|
85
85
|
const manager = new EventEmitter();
|
|
86
86
|
t.mock.method(ServerRMQ.prototype, 'createClient', () => manager);
|
|
87
|
-
t.mock.method(ServerRMQ.prototype, 'close', () => {});
|
|
87
|
+
const close = t.mock.method(ServerRMQ.prototype, 'close', () => {});
|
|
88
|
+
const warning = t.mock.method(MsaLogger.prototype, 'warn', () => {});
|
|
88
89
|
const server = new GracefulServerRMQ({ queue: 'USER_SERVICE' });
|
|
89
90
|
server.createClient();
|
|
90
91
|
server.channel = { removeSetup: async () => { throw Error('channel closed'); } };
|
|
91
|
-
await assert.
|
|
92
|
+
await assert.doesNotReject(server.close());
|
|
93
|
+
assert.equal(close.mock.callCount(), 1);
|
|
94
|
+
assert.equal(warning.mock.callCount(), 1);
|
|
95
|
+
assert.equal(manager.listenerCount('connect'), 0);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('native server close failure retains its rejection after observer disposal', async t => {
|
|
99
|
+
const flag = process.env.RMQ_DIAGNOSTICS_ENABLED;
|
|
100
|
+
process.env.RMQ_DIAGNOSTICS_ENABLED = 'true';
|
|
101
|
+
t.after(() => { if (flag === undefined) delete process.env.RMQ_DIAGNOSTICS_ENABLED; else process.env.RMQ_DIAGNOSTICS_ENABLED = flag; });
|
|
102
|
+
const manager = new EventEmitter();
|
|
103
|
+
const failure = Error('native close failed');
|
|
104
|
+
t.mock.method(ServerRMQ.prototype, 'createClient', () => manager);
|
|
105
|
+
t.mock.method(ServerRMQ.prototype, 'close', async () => { throw failure; });
|
|
106
|
+
const server = new GracefulServerRMQ({ queue: 'USER_SERVICE' });
|
|
107
|
+
server.createClient();
|
|
108
|
+
server.channel = { removeSetup: async () => {} };
|
|
109
|
+
await assert.rejects(server.close(), err => err === failure);
|
|
92
110
|
assert.equal(manager.listenerCount('connect'), 0);
|
|
93
111
|
});
|
|
94
112
|
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
const assert = require('node:assert/strict');
|
|
2
2
|
const test = require('node:test');
|
|
3
|
+
const { createRequire } = require('node:module');
|
|
3
4
|
const { RmqContext, ServerRMQ } = require('@nestjs/microservices');
|
|
5
|
+
const { RpcProxy } = require('@nestjs/microservices/context/rpc-proxy');
|
|
6
|
+
const { RpcExceptionsHandler } = require('@nestjs/microservices/exceptions/rpc-exceptions-handler');
|
|
7
|
+
const { Logger } = require('@nestjs/common');
|
|
8
|
+
const { callModuleDestroyHook } = require('@nestjs/core/hooks/on-module-destroy.hook');
|
|
9
|
+
const { InstanceWrapper } = require('@nestjs/core/injector/instance-wrapper');
|
|
4
10
|
const { Subject } = require('rxjs');
|
|
5
|
-
const
|
|
11
|
+
const coreRequire = createRequire(require.resolve('../modules/graceful-shutdown/graceful-shutdown.rmq'));
|
|
12
|
+
const { MsaLogger } = coreRequire('@yolo-croket-dev/msa-logger');
|
|
6
13
|
const { RPCInterceptor } = require('../interceptors/rpc.interceptor');
|
|
7
14
|
const { GracefulShutdownService } = require('../modules/graceful-shutdown/graceful-shutdown.service');
|
|
15
|
+
const { GracefulShutdownModule } = require('../modules/graceful-shutdown/graceful-shutdown.module');
|
|
8
16
|
const { GracefulServerRMQ } = require('../modules/graceful-shutdown/graceful-shutdown.rmq');
|
|
9
17
|
|
|
10
18
|
function createRequest(channel, headers = {}) {
|
|
@@ -44,6 +52,60 @@ test('종료 중 받은 미실행 RPC는 즉시 재전달하고 기존 요청의
|
|
|
44
52
|
assert.equal(shutdown.getActiveInterceptorsCount(), 0);
|
|
45
53
|
});
|
|
46
54
|
|
|
55
|
+
for (const scenario of [
|
|
56
|
+
{ name: '취소 실패', cancelFails: true, nackFails: false, duringRedelivery: false },
|
|
57
|
+
{ name: '반환 실패', cancelFails: false, nackFails: true, duringRedelivery: false },
|
|
58
|
+
{ name: '취소·반환 실패', cancelFails: true, nackFails: true, duringRedelivery: false },
|
|
59
|
+
{ name: '재전달 확인 중 종료와 취소 실패', cancelFails: true, nackFails: false, duringRedelivery: true },
|
|
60
|
+
]) {
|
|
61
|
+
test(`${scenario.name} 시에도 기존 Pod는 RPC 오류나 완료 응답을 보내지 않는다`, async t => {
|
|
62
|
+
t.mock.method(MsaLogger.prototype, 'debug', () => {});
|
|
63
|
+
t.mock.method(Logger.prototype, 'error', () => {});
|
|
64
|
+
const warning = t.mock.method(MsaLogger.prototype, 'warn', () => {});
|
|
65
|
+
const shutdown = new GracefulShutdownService();
|
|
66
|
+
shutdown.isApplicationExit = !scenario.duringRedelivery;
|
|
67
|
+
const calls = [];
|
|
68
|
+
const channel = {
|
|
69
|
+
cancel: async () => {
|
|
70
|
+
calls.push('cancel');
|
|
71
|
+
if (scenario.cancelFails) throw Error('channel closed during cancel');
|
|
72
|
+
},
|
|
73
|
+
nack: (_message, multiple, requeue) => {
|
|
74
|
+
calls.push(['nack', multiple, requeue]);
|
|
75
|
+
if (scenario.nackFails) throw Error('channel closed during nack');
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
const { message, context } = createRequest(channel, scenario.duringRedelivery ? { 'x-delivery-count': 1 } : {});
|
|
79
|
+
const interceptor = new RPCInterceptor({ get: () => undefined }, shutdown);
|
|
80
|
+
if (scenario.duringRedelivery) {
|
|
81
|
+
const server = new GracefulServerRMQ({ queue: 'TEST', noAck: false });
|
|
82
|
+
shutdown.registerRmqServer(server);
|
|
83
|
+
t.mock.method(interceptor, 'isRedeliveryMessage', async () => {
|
|
84
|
+
await shutdown.onModuleDestroy();
|
|
85
|
+
return true;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const next = { handle: t.mock.fn(() => { throw Error('실행하면 안 되는 신규 요청'); }) };
|
|
89
|
+
const handler = new RpcProxy().create(() => interceptor.intercept(context, next), new RpcExceptionsHandler());
|
|
90
|
+
const response = await handler(...context.getArgs());
|
|
91
|
+
const replies = [];
|
|
92
|
+
const subscription = new ServerRMQ({ queue: 'TEST', noAck: false }).send(response, value => replies.push(value));
|
|
93
|
+
t.after(() => subscription.unsubscribe());
|
|
94
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
95
|
+
assert.deepEqual(replies, []);
|
|
96
|
+
assert.deepEqual(calls, ['cancel', ['nack', false, true]]);
|
|
97
|
+
assert.equal(next.handle.mock.callCount(), 0);
|
|
98
|
+
assert.equal(shutdown.getActiveInterceptorsCount(), 0);
|
|
99
|
+
assert.equal(warning.mock.callCount(), 1);
|
|
100
|
+
const [logMessage, logInfo, logDetails] = warning.mock.calls[0].arguments;
|
|
101
|
+
assert.match(logMessage, /종료/);
|
|
102
|
+
assert.equal(logInfo.traceId, 'shutdown-test');
|
|
103
|
+
assert.deepEqual(logDetails.failures.map(item => item.operation),
|
|
104
|
+
[scenario.cancelFails ? 'cancel' : null, scenario.nackFails ? 'nack' : null].filter(Boolean));
|
|
105
|
+
assert.equal(message.properties.replyTo, 'reply-queue');
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
47
109
|
test('종료 전에 실행한 RPC는 계속 처리하고 종료 이후 도착한 메시지만 반환한다', async t => {
|
|
48
110
|
t.mock.method(MsaLogger.prototype, 'debug', () => {});
|
|
49
111
|
const shutdown = new GracefulShutdownService();
|
|
@@ -152,6 +214,60 @@ test('수신 중단과 서버 close가 겹쳐도 consumer는 한 번만 취소
|
|
|
152
214
|
assert.deepEqual(calls, [['cancel', 'consumer-1'], ['close']]);
|
|
153
215
|
});
|
|
154
216
|
|
|
217
|
+
for (const failurePoint of ['cancel', 'setup']) {
|
|
218
|
+
test(`${failurePoint} 실패 후에도 Nest 종료 훅은 기존 RPC 완료를 기다리고 연결을 정리한다`, async t => {
|
|
219
|
+
t.mock.method(MsaLogger.prototype, 'debug', () => {});
|
|
220
|
+
t.mock.method(MsaLogger.prototype, 'info', () => {});
|
|
221
|
+
const warning = t.mock.method(MsaLogger.prototype, 'warn', () => {});
|
|
222
|
+
const shutdown = new GracefulShutdownService();
|
|
223
|
+
const server = new GracefulServerRMQ({ queue: 'TEST', noAck: false });
|
|
224
|
+
const calls = [];
|
|
225
|
+
const failure = new Error('Channel ended during consumer cancellation');
|
|
226
|
+
server.consumerTag = 'consumer-1';
|
|
227
|
+
server.channel = {
|
|
228
|
+
removeSetup: async (_setup, teardown) => {
|
|
229
|
+
if (failurePoint === 'setup') throw failure;
|
|
230
|
+
await teardown({ cancel: async () => { calls.push('cancel'); throw failure; } });
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
const active = { method: 'test.payment', traceId: 'shutdown-test', correlationId: 'active-request' };
|
|
234
|
+
shutdown.pushActiveInterceptors(active);
|
|
235
|
+
t.after(() => shutdown.popActiveInterceptors(active));
|
|
236
|
+
shutdown.registerRmqServer(server);
|
|
237
|
+
let enteredDrain;
|
|
238
|
+
const startedDrain = new Promise(resolve => { enteredDrain = resolve; });
|
|
239
|
+
const originalDrain = shutdown.waitForActiveInterceptors.bind(shutdown);
|
|
240
|
+
t.mock.method(shutdown, 'waitForActiveInterceptors', async () => {
|
|
241
|
+
calls.push('drain');
|
|
242
|
+
enteredDrain();
|
|
243
|
+
await originalDrain();
|
|
244
|
+
calls.push('finished');
|
|
245
|
+
});
|
|
246
|
+
t.mock.method(ServerRMQ.prototype, 'close', async () => { calls.push('close'); });
|
|
247
|
+
const moduleInstance = new GracefulShutdownModule(shutdown);
|
|
248
|
+
const moduleFixture = {
|
|
249
|
+
getNonAliasProviders: () => [
|
|
250
|
+
['module', new InstanceWrapper({ instance: moduleInstance, metatype: GracefulShutdownModule })],
|
|
251
|
+
['service', new InstanceWrapper({ instance: shutdown, metatype: GracefulShutdownService })],
|
|
252
|
+
],
|
|
253
|
+
controllers: new Map(), injectables: new Map(), middlewares: new Map(),
|
|
254
|
+
};
|
|
255
|
+
const destroying = callModuleDestroyHook(moduleFixture);
|
|
256
|
+
await Promise.race([startedDrain, destroying]);
|
|
257
|
+
assert.equal(shutdown.isApplicationExit, true);
|
|
258
|
+
assert.equal(shutdown.getActiveInterceptorsCount(), 1);
|
|
259
|
+
assert.equal(calls.includes('close'), false);
|
|
260
|
+
shutdown.popActiveInterceptors(active);
|
|
261
|
+
await destroying;
|
|
262
|
+
await server.close();
|
|
263
|
+
assert.deepEqual(calls, failurePoint === 'cancel'
|
|
264
|
+
? ['cancel', 'drain', 'finished', 'close'] : ['drain', 'finished', 'close']);
|
|
265
|
+
assert.equal(warning.mock.callCount(), 1);
|
|
266
|
+
assert.equal(server.consumerTag, null);
|
|
267
|
+
assert.equal(shutdown.getActiveInterceptorsCount(), 0);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
155
271
|
test('채널 설정 중 종료가 시작되면 새 consumer를 만들지 않는다', async () => {
|
|
156
272
|
let finishPrefetch;
|
|
157
273
|
const prefetch = new Promise(resolve => { finishPrefetch = resolve; });
|