@hile/message-modem 4.0.3 → 4.0.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/AI.md +123 -4
- package/README.md +6 -0
- package/dist/exception.d.ts +5 -0
- package/dist/exception.js +9 -0
- package/dist/index.d.ts +62 -15
- package/dist/index.js +585 -101
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AbortException, Exception, TimeoutException } from "./exception.js";
|
|
1
|
+
import { AbortException, Exception, MessageInputError, TimeoutException } from "./exception.js";
|
|
2
2
|
import { Readable } from 'node:stream';
|
|
3
3
|
import { DeadlineScheduler } from './deadline-scheduler.js';
|
|
4
4
|
export * from './exception.js';
|
|
@@ -8,9 +8,44 @@ export var MESSAGE_MODEM_TYPE;
|
|
|
8
8
|
MESSAGE_MODEM_TYPE[MESSAGE_MODEM_TYPE["RESPONSE"] = 1] = "RESPONSE";
|
|
9
9
|
MESSAGE_MODEM_TYPE[MESSAGE_MODEM_TYPE["ABORT"] = 2] = "ABORT";
|
|
10
10
|
MESSAGE_MODEM_TYPE[MESSAGE_MODEM_TYPE["STREAM_CREDIT"] = 3] = "STREAM_CREDIT";
|
|
11
|
+
MESSAGE_MODEM_TYPE[MESSAGE_MODEM_TYPE["STREAM_DATA"] = 4] = "STREAM_DATA";
|
|
12
|
+
MESSAGE_MODEM_TYPE[MESSAGE_MODEM_TYPE["STREAM_CANCEL"] = 5] = "STREAM_CANCEL";
|
|
11
13
|
})(MESSAGE_MODEM_TYPE || (MESSAGE_MODEM_TYPE = {}));
|
|
12
14
|
const MAX_STREAM_WINDOW = 64;
|
|
15
|
+
const DEFAULT_INPUT_STREAM_WINDOW = 1;
|
|
16
|
+
const MAX_CONCURRENT_STREAMS = 128;
|
|
13
17
|
const MAX_TIMER_DELAY = 2_147_483_647;
|
|
18
|
+
export function isBinaryInput(value) {
|
|
19
|
+
return value instanceof Uint8Array || value instanceof ArrayBuffer;
|
|
20
|
+
}
|
|
21
|
+
export function isMessageInput(value) {
|
|
22
|
+
return isBinaryInput(value) || isAsyncIterable(value);
|
|
23
|
+
}
|
|
24
|
+
async function* toInputIterable(input) {
|
|
25
|
+
if (input instanceof ArrayBuffer) {
|
|
26
|
+
yield new Uint8Array(input);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (input instanceof Uint8Array) {
|
|
30
|
+
yield input;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
yield* input;
|
|
34
|
+
}
|
|
35
|
+
function normalizeMessageInput(data, explicitInput) {
|
|
36
|
+
if (explicitInput !== undefined) {
|
|
37
|
+
if (isMessageInput(data)) {
|
|
38
|
+
throw new TypeError('A message accepts only one request input stream');
|
|
39
|
+
}
|
|
40
|
+
if (!isMessageInput(explicitInput)) {
|
|
41
|
+
throw new TypeError('Message input must be an AsyncIterable, Uint8Array, or ArrayBuffer');
|
|
42
|
+
}
|
|
43
|
+
return { data, input: toInputIterable(explicitInput) };
|
|
44
|
+
}
|
|
45
|
+
if (!isMessageInput(data))
|
|
46
|
+
return { data };
|
|
47
|
+
return { data: undefined, input: toInputIterable(data) };
|
|
48
|
+
}
|
|
14
49
|
function streamLimit(value, name) {
|
|
15
50
|
if (value === undefined)
|
|
16
51
|
return undefined;
|
|
@@ -33,6 +68,17 @@ function streamTimeout(value, name) {
|
|
|
33
68
|
}
|
|
34
69
|
return normalized;
|
|
35
70
|
}
|
|
71
|
+
function returnProducerIterator(producer) {
|
|
72
|
+
if (producer.iteratorReturned || !producer.iterator?.return)
|
|
73
|
+
return;
|
|
74
|
+
producer.iteratorReturned = true;
|
|
75
|
+
try {
|
|
76
|
+
void Promise.resolve(producer.iterator.return()).catch(() => { });
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Iterator cleanup must not mask the request's original outcome.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
36
82
|
class CreditReadable extends Readable {
|
|
37
83
|
onConsumed;
|
|
38
84
|
constructor(onConsumed) {
|
|
@@ -56,6 +102,8 @@ export class MessageModem {
|
|
|
56
102
|
stacks = new Map();
|
|
57
103
|
streams = new Map();
|
|
58
104
|
streamProducers = new Map();
|
|
105
|
+
inputStreams = new Map();
|
|
106
|
+
inputStreamProducers = new Map();
|
|
59
107
|
_dispose() {
|
|
60
108
|
for (const { reject } of this.stacks.values()) {
|
|
61
109
|
reject(new AbortException());
|
|
@@ -66,13 +114,25 @@ export class MessageModem {
|
|
|
66
114
|
for (const { stream } of this.streams.values()) {
|
|
67
115
|
stream.destroy(new AbortException());
|
|
68
116
|
}
|
|
117
|
+
for (const { stream } of this.inputStreams.values()) {
|
|
118
|
+
stream.destroy(new AbortException());
|
|
119
|
+
}
|
|
69
120
|
for (const producer of this.streamProducers.values()) {
|
|
121
|
+
producer.cancelled = true;
|
|
70
122
|
producer.wake?.();
|
|
123
|
+
returnProducerIterator(producer);
|
|
124
|
+
}
|
|
125
|
+
for (const producer of this.inputStreamProducers.values()) {
|
|
126
|
+
producer.cancelled = true;
|
|
127
|
+
producer.wake?.();
|
|
128
|
+
returnProducerIterator(producer);
|
|
71
129
|
}
|
|
72
130
|
this.aborts.clear();
|
|
73
131
|
this.stacks.clear();
|
|
74
132
|
this.streams.clear();
|
|
75
133
|
this.streamProducers.clear();
|
|
134
|
+
this.inputStreams.clear();
|
|
135
|
+
this.inputStreamProducers.clear();
|
|
76
136
|
this.deadlines.clear();
|
|
77
137
|
}
|
|
78
138
|
/**
|
|
@@ -93,14 +153,13 @@ export class MessageModem {
|
|
|
93
153
|
* @param data - 消息数据
|
|
94
154
|
* @returns 消息数据
|
|
95
155
|
*/
|
|
96
|
-
createPostData(mode, data, twoway = true
|
|
156
|
+
createPostData(mode, data, twoway = true) {
|
|
97
157
|
const id = this.createIncrementId();
|
|
98
158
|
const state = {
|
|
99
|
-
id, twoway, data, mode,
|
|
159
|
+
id, twoway, data, mode,
|
|
100
160
|
};
|
|
101
161
|
if (mode === MESSAGE_MODEM_TYPE.ABORT) {
|
|
102
162
|
state.twoway = false;
|
|
103
|
-
state.stream = false;
|
|
104
163
|
}
|
|
105
164
|
return state;
|
|
106
165
|
}
|
|
@@ -112,10 +171,12 @@ export class MessageModem {
|
|
|
112
171
|
* @returns 消息响应
|
|
113
172
|
*/
|
|
114
173
|
_send(data, options) {
|
|
115
|
-
|
|
174
|
+
const normalized = normalizeMessageInput(data, options?.input);
|
|
175
|
+
return this._write(normalized.data, {
|
|
116
176
|
timeout: options?.timeout ?? 30000,
|
|
117
177
|
twoway: true,
|
|
118
178
|
signal: options?.signal,
|
|
179
|
+
input: normalized.input,
|
|
119
180
|
});
|
|
120
181
|
}
|
|
121
182
|
/**
|
|
@@ -132,21 +193,119 @@ export class MessageModem {
|
|
|
132
193
|
signal: options?.signal,
|
|
133
194
|
});
|
|
134
195
|
}
|
|
196
|
+
startInputProducer(id, input, onError, onActivity = () => { }) {
|
|
197
|
+
const producer = {
|
|
198
|
+
credits: 0,
|
|
199
|
+
nextCreditSeq: 0,
|
|
200
|
+
nextSeq: 0,
|
|
201
|
+
cancelled: false,
|
|
202
|
+
iteratorReturned: false,
|
|
203
|
+
fail: onError,
|
|
204
|
+
iterator: input[Symbol.asyncIterator](),
|
|
205
|
+
};
|
|
206
|
+
this.inputStreamProducers.set(id, producer);
|
|
207
|
+
void (async () => {
|
|
208
|
+
let readingInput = false;
|
|
209
|
+
try {
|
|
210
|
+
while (!producer.cancelled) {
|
|
211
|
+
while (producer.credits === 0 && !producer.cancelled) {
|
|
212
|
+
await new Promise((resolve) => {
|
|
213
|
+
producer.wake = resolve;
|
|
214
|
+
});
|
|
215
|
+
producer.wake = undefined;
|
|
216
|
+
}
|
|
217
|
+
if (producer.cancelled)
|
|
218
|
+
return;
|
|
219
|
+
producer.credits--;
|
|
220
|
+
readingInput = true;
|
|
221
|
+
const next = await producer.iterator.next();
|
|
222
|
+
if (!next.done && next.value == null) {
|
|
223
|
+
throw new TypeError('Stream chunk must not be null or undefined');
|
|
224
|
+
}
|
|
225
|
+
readingInput = false;
|
|
226
|
+
if (producer.cancelled)
|
|
227
|
+
return;
|
|
228
|
+
onActivity();
|
|
229
|
+
this.post({
|
|
230
|
+
id,
|
|
231
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_DATA,
|
|
232
|
+
twoway: false,
|
|
233
|
+
data: {
|
|
234
|
+
direction: 'input',
|
|
235
|
+
seq: producer.nextSeq++,
|
|
236
|
+
payload: next.done ? undefined : next.value,
|
|
237
|
+
final: next.done === true,
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
if (next.done) {
|
|
241
|
+
this.inputStreamProducers.delete(id);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
if (producer.cancelled)
|
|
248
|
+
return;
|
|
249
|
+
this.failInputProducer(id, readingInput
|
|
250
|
+
? new MessageInputError(error)
|
|
251
|
+
: error instanceof Error
|
|
252
|
+
? error
|
|
253
|
+
: new Error('Request input stream failed'));
|
|
254
|
+
}
|
|
255
|
+
})();
|
|
256
|
+
}
|
|
257
|
+
failInputProducer(id, error) {
|
|
258
|
+
const producer = this.inputStreamProducers.get(id);
|
|
259
|
+
if (!producer)
|
|
260
|
+
return;
|
|
261
|
+
this.cancelInputProducer(id);
|
|
262
|
+
try {
|
|
263
|
+
this.post({
|
|
264
|
+
id,
|
|
265
|
+
mode: MESSAGE_MODEM_TYPE.ABORT,
|
|
266
|
+
twoway: false,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
// The transport may already be closed.
|
|
271
|
+
}
|
|
272
|
+
producer.fail?.(error);
|
|
273
|
+
}
|
|
274
|
+
cancelInputProducer(id) {
|
|
275
|
+
const producer = this.inputStreamProducers.get(id);
|
|
276
|
+
if (!producer)
|
|
277
|
+
return;
|
|
278
|
+
this.inputStreamProducers.delete(id);
|
|
279
|
+
producer.cancelled = true;
|
|
280
|
+
producer.wake?.();
|
|
281
|
+
returnProducerIterator(producer);
|
|
282
|
+
}
|
|
135
283
|
_stream(data, options = {}) {
|
|
284
|
+
const normalized = normalizeMessageInput(data, options.input);
|
|
136
285
|
const window = streamWindow(options.window);
|
|
137
286
|
const timeout = streamTimeout(options.timeout, 'Stream timeout');
|
|
138
287
|
const idleTimeout = streamTimeout(options.idleTimeout, 'Stream idle timeout');
|
|
139
|
-
const state = this.createPostData(MESSAGE_MODEM_TYPE.REQUEST, data, true
|
|
140
|
-
state.
|
|
141
|
-
|
|
142
|
-
|
|
288
|
+
const state = this.createPostData(MESSAGE_MODEM_TYPE.REQUEST, normalized.data, true);
|
|
289
|
+
state.streams = {
|
|
290
|
+
...(normalized.input ? { input: true } : {}),
|
|
291
|
+
output: window > 1 ? { window } : {},
|
|
292
|
+
};
|
|
143
293
|
let consumer;
|
|
144
294
|
const stream = new CreditReadable(() => {
|
|
145
295
|
if (consumer.creditsOwed === 0 || consumer.completed || consumer.cancelled)
|
|
146
296
|
return;
|
|
147
297
|
consumer.creditsOwed--;
|
|
148
298
|
try {
|
|
149
|
-
this.post(
|
|
299
|
+
this.post({
|
|
300
|
+
id: state.id,
|
|
301
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_CREDIT,
|
|
302
|
+
twoway: false,
|
|
303
|
+
data: {
|
|
304
|
+
direction: 'output',
|
|
305
|
+
seq: consumer.nextCreditSeq++,
|
|
306
|
+
window: consumer.maxCredits,
|
|
307
|
+
},
|
|
308
|
+
});
|
|
150
309
|
}
|
|
151
310
|
catch (error) {
|
|
152
311
|
consumer.cancelled = true;
|
|
@@ -185,13 +344,18 @@ export class MessageModem {
|
|
|
185
344
|
nextCreditSeq: 0,
|
|
186
345
|
touch,
|
|
187
346
|
clearTimers,
|
|
347
|
+
cleanup: () => { },
|
|
188
348
|
};
|
|
189
349
|
const sendAbort = () => {
|
|
190
350
|
if (consumer.completed || consumer.cancelled)
|
|
191
351
|
return;
|
|
192
352
|
consumer.cancelled = true;
|
|
193
353
|
try {
|
|
194
|
-
this.post(
|
|
354
|
+
this.post({
|
|
355
|
+
id: state.id,
|
|
356
|
+
mode: MESSAGE_MODEM_TYPE.ABORT,
|
|
357
|
+
twoway: false,
|
|
358
|
+
});
|
|
195
359
|
}
|
|
196
360
|
catch {
|
|
197
361
|
// The transport may already be closed.
|
|
@@ -201,12 +365,24 @@ export class MessageModem {
|
|
|
201
365
|
sendAbort();
|
|
202
366
|
stream.destroy(new AbortException());
|
|
203
367
|
};
|
|
368
|
+
const cleanup = () => {
|
|
369
|
+
clearTimers();
|
|
370
|
+
this.streams.delete(state.id);
|
|
371
|
+
options?.signal?.removeEventListener('abort', onAbort);
|
|
372
|
+
};
|
|
373
|
+
consumer.cleanup = cleanup;
|
|
204
374
|
if (options?.signal?.aborted) {
|
|
205
375
|
consumer.cancelled = true;
|
|
206
376
|
queueMicrotask(() => stream.destroy(new AbortException()));
|
|
207
377
|
return stream;
|
|
208
378
|
}
|
|
209
379
|
this.streams.set(state.id, consumer);
|
|
380
|
+
if (normalized.input) {
|
|
381
|
+
this.startInputProducer(state.id, normalized.input, (error) => {
|
|
382
|
+
consumer.cancelled = true;
|
|
383
|
+
stream.destroy(error);
|
|
384
|
+
}, consumer.touch);
|
|
385
|
+
}
|
|
210
386
|
options?.signal?.addEventListener('abort', onAbort, { once: true });
|
|
211
387
|
if (timeout) {
|
|
212
388
|
totalTimer = this.deadlines.schedule(timeout, () => expire('Stream timeout'));
|
|
@@ -214,16 +390,16 @@ export class MessageModem {
|
|
|
214
390
|
touch();
|
|
215
391
|
stream.on('close', () => {
|
|
216
392
|
sendAbort();
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
options?.signal?.removeEventListener('abort', onAbort);
|
|
393
|
+
this.cancelInputProducer(state.id);
|
|
394
|
+
cleanup();
|
|
220
395
|
});
|
|
221
396
|
try {
|
|
222
397
|
this.post(state);
|
|
223
398
|
}
|
|
224
399
|
catch (error) {
|
|
225
400
|
consumer.cancelled = true;
|
|
226
|
-
this.
|
|
401
|
+
this.cancelInputProducer(state.id);
|
|
402
|
+
cleanup();
|
|
227
403
|
queueMicrotask(() => stream.destroy(error));
|
|
228
404
|
}
|
|
229
405
|
return stream;
|
|
@@ -240,6 +416,8 @@ export class MessageModem {
|
|
|
240
416
|
const signal = options?.signal;
|
|
241
417
|
// 创建请求消息数据
|
|
242
418
|
const state = this.createPostData(MESSAGE_MODEM_TYPE.REQUEST, data, twoway);
|
|
419
|
+
if (options?.input)
|
|
420
|
+
state.streams = { input: true };
|
|
243
421
|
// 如果消息是单向的,则直接返回
|
|
244
422
|
if (!twoway) {
|
|
245
423
|
if (!signal?.aborted)
|
|
@@ -257,13 +435,19 @@ export class MessageModem {
|
|
|
257
435
|
timer = undefined;
|
|
258
436
|
signal?.removeEventListener('abort', onAbort);
|
|
259
437
|
clear();
|
|
438
|
+
this.cancelInputProducer(state.id);
|
|
260
439
|
};
|
|
261
440
|
const onAbort = () => {
|
|
262
441
|
this.deadlines.cancel(timer);
|
|
263
442
|
timer = undefined;
|
|
264
443
|
try {
|
|
265
|
-
if (posted)
|
|
266
|
-
this.post(
|
|
444
|
+
if (posted) {
|
|
445
|
+
this.post({
|
|
446
|
+
id: state.id,
|
|
447
|
+
mode: MESSAGE_MODEM_TYPE.ABORT,
|
|
448
|
+
twoway: false,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
267
451
|
}
|
|
268
452
|
catch {
|
|
269
453
|
/* 例如 WebSocket 已关闭时 send 可能抛错 */
|
|
@@ -271,6 +455,7 @@ export class MessageModem {
|
|
|
271
455
|
finally {
|
|
272
456
|
signal?.removeEventListener('abort', onAbort);
|
|
273
457
|
clear();
|
|
458
|
+
this.cancelInputProducer(state.id);
|
|
274
459
|
reject(new AbortException());
|
|
275
460
|
}
|
|
276
461
|
};
|
|
@@ -288,12 +473,29 @@ export class MessageModem {
|
|
|
288
473
|
resolve: _resolve,
|
|
289
474
|
reject: _reject,
|
|
290
475
|
});
|
|
291
|
-
timer = this.deadlines.schedule(timeout, () =>
|
|
476
|
+
timer = this.deadlines.schedule(timeout, () => {
|
|
477
|
+
try {
|
|
478
|
+
if (posted) {
|
|
479
|
+
this.post({
|
|
480
|
+
id: state.id,
|
|
481
|
+
mode: MESSAGE_MODEM_TYPE.ABORT,
|
|
482
|
+
twoway: false,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
// The transport may already be closed.
|
|
488
|
+
}
|
|
489
|
+
_reject(new TimeoutException());
|
|
490
|
+
});
|
|
292
491
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
293
492
|
if (signal?.aborted) {
|
|
294
493
|
onAbort();
|
|
295
494
|
return;
|
|
296
495
|
}
|
|
496
|
+
if (options?.input) {
|
|
497
|
+
this.startInputProducer(state.id, options.input, _reject);
|
|
498
|
+
}
|
|
297
499
|
try {
|
|
298
500
|
posted = true;
|
|
299
501
|
this.post(state);
|
|
@@ -303,14 +505,127 @@ export class MessageModem {
|
|
|
303
505
|
}
|
|
304
506
|
});
|
|
305
507
|
}
|
|
508
|
+
createInputConsumer(id) {
|
|
509
|
+
const window = DEFAULT_INPUT_STREAM_WINDOW;
|
|
510
|
+
let consumer;
|
|
511
|
+
const stream = new CreditReadable(() => {
|
|
512
|
+
if (consumer.creditsOwed === 0 || consumer.completed || consumer.cancelled)
|
|
513
|
+
return;
|
|
514
|
+
consumer.creditsOwed--;
|
|
515
|
+
this.post({
|
|
516
|
+
id,
|
|
517
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_CREDIT,
|
|
518
|
+
twoway: false,
|
|
519
|
+
data: {
|
|
520
|
+
direction: 'input',
|
|
521
|
+
seq: consumer.nextCreditSeq++,
|
|
522
|
+
window,
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
consumer = {
|
|
527
|
+
stream,
|
|
528
|
+
completed: false,
|
|
529
|
+
cancelled: false,
|
|
530
|
+
creditsOwed: 0,
|
|
531
|
+
maxCredits: window,
|
|
532
|
+
nextSeq: 0,
|
|
533
|
+
nextCreditSeq: 0,
|
|
534
|
+
touch: () => { },
|
|
535
|
+
clearTimers: () => { },
|
|
536
|
+
cleanup: () => { },
|
|
537
|
+
};
|
|
538
|
+
this.inputStreams.set(id, consumer);
|
|
539
|
+
// A handler may intentionally ignore its body. Keep protocol failures from
|
|
540
|
+
// becoming process-level unhandled EventEmitter errors in that case.
|
|
541
|
+
stream.on('error', () => { });
|
|
542
|
+
stream.once('close', () => {
|
|
543
|
+
if (consumer.completed || consumer.cancelled)
|
|
544
|
+
return;
|
|
545
|
+
this.cancelInputConsumer(id, 'Request input consumer closed');
|
|
546
|
+
});
|
|
547
|
+
this.post({
|
|
548
|
+
id,
|
|
549
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_CREDIT,
|
|
550
|
+
twoway: false,
|
|
551
|
+
data: {
|
|
552
|
+
direction: 'input',
|
|
553
|
+
seq: consumer.nextCreditSeq++,
|
|
554
|
+
window,
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
return stream;
|
|
558
|
+
}
|
|
559
|
+
canAcceptInputStream(id) {
|
|
560
|
+
return !this.inputStreams.has(id) && this.inputStreams.size < MAX_CONCURRENT_STREAMS;
|
|
561
|
+
}
|
|
562
|
+
cancelInputConsumer(id, message = 'Request handler completed') {
|
|
563
|
+
const consumer = this.inputStreams.get(id);
|
|
564
|
+
if (!consumer)
|
|
565
|
+
return;
|
|
566
|
+
this.inputStreams.delete(id);
|
|
567
|
+
consumer.cancelled = true;
|
|
568
|
+
try {
|
|
569
|
+
this.post({
|
|
570
|
+
id,
|
|
571
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_CANCEL,
|
|
572
|
+
twoway: false,
|
|
573
|
+
data: { direction: 'input', message },
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
catch {
|
|
577
|
+
// The transport may already be closed.
|
|
578
|
+
}
|
|
579
|
+
consumer.stream.destroy();
|
|
580
|
+
}
|
|
581
|
+
failInputConsumer(id, error) {
|
|
582
|
+
const consumer = this.inputStreams.get(id);
|
|
583
|
+
if (!consumer)
|
|
584
|
+
return;
|
|
585
|
+
this.inputStreams.delete(id);
|
|
586
|
+
consumer.cancelled = true;
|
|
587
|
+
try {
|
|
588
|
+
this.post({
|
|
589
|
+
id,
|
|
590
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_CANCEL,
|
|
591
|
+
twoway: false,
|
|
592
|
+
data: { direction: 'input', status: error.status, message: error.message },
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
catch {
|
|
596
|
+
// The transport may already be closed.
|
|
597
|
+
}
|
|
598
|
+
consumer.stream.destroy(error);
|
|
599
|
+
this.cancelOutputProducer(id);
|
|
600
|
+
const controller = this.aborts.get(id);
|
|
601
|
+
this.aborts.delete(id);
|
|
602
|
+
if (controller && !controller.signal.aborted)
|
|
603
|
+
controller.abort();
|
|
604
|
+
}
|
|
306
605
|
/**
|
|
307
606
|
* 处理请求消息
|
|
308
607
|
* @param msg - 消息数据
|
|
309
608
|
*/
|
|
310
609
|
onRequest(msg) {
|
|
610
|
+
if (msg.streams?.input && !this.canAcceptInputStream(msg.id)) {
|
|
611
|
+
if (msg.twoway) {
|
|
612
|
+
this.post({
|
|
613
|
+
id: msg.id,
|
|
614
|
+
mode: MESSAGE_MODEM_TYPE.RESPONSE,
|
|
615
|
+
twoway: false,
|
|
616
|
+
data: {
|
|
617
|
+
status: 429,
|
|
618
|
+
data: null,
|
|
619
|
+
message: 'Request input stream capacity exceeded',
|
|
620
|
+
},
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
311
625
|
const controller = new AbortController();
|
|
312
626
|
this.aborts.set(msg.id, controller);
|
|
313
|
-
this.
|
|
627
|
+
const input = msg.streams?.input ? this.createInputConsumer(msg.id) : undefined;
|
|
628
|
+
Promise.resolve(this.exec(msg.data, controller.signal, input))
|
|
314
629
|
.then(value => {
|
|
315
630
|
if (controller.signal.aborted)
|
|
316
631
|
return;
|
|
@@ -346,6 +661,7 @@ export class MessageModem {
|
|
|
346
661
|
}
|
|
347
662
|
})
|
|
348
663
|
.finally(() => {
|
|
664
|
+
this.cancelInputConsumer(msg.id);
|
|
349
665
|
this.aborts.delete(msg.id);
|
|
350
666
|
});
|
|
351
667
|
}
|
|
@@ -360,38 +676,30 @@ export class MessageModem {
|
|
|
360
676
|
// 如果栈中存在该消息,则处理响应消息
|
|
361
677
|
if (stack) {
|
|
362
678
|
const { resolve, reject } = stack;
|
|
679
|
+
if (!res || (typeof res.status !== 'string' && typeof res.status !== 'number')) {
|
|
680
|
+
reject(new Exception(502, 'Invalid response frame'));
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
363
683
|
// 如果响应状态码不是 200,则拒绝响应
|
|
364
|
-
if (res
|
|
365
|
-
reject(new Exception(res
|
|
684
|
+
if (res.status !== 200) {
|
|
685
|
+
reject(new Exception(res.status, res.message ?? 'Request failed'));
|
|
366
686
|
}
|
|
367
687
|
else {
|
|
368
|
-
resolve(res
|
|
688
|
+
resolve(res.data);
|
|
369
689
|
}
|
|
370
690
|
}
|
|
371
691
|
}
|
|
372
692
|
onStreamRequest(msg) {
|
|
373
|
-
if (msg.streamVersion !== 1) {
|
|
374
|
-
this.post({
|
|
375
|
-
id: msg.id,
|
|
376
|
-
mode: MESSAGE_MODEM_TYPE.RESPONSE,
|
|
377
|
-
stream: true,
|
|
378
|
-
streamVersion: 1,
|
|
379
|
-
data: { status: 400, seq: 0, payload: 'Unsupported stream protocol', final: true },
|
|
380
|
-
twoway: false,
|
|
381
|
-
});
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
693
|
let window;
|
|
385
694
|
try {
|
|
386
|
-
window = streamWindow(msg.
|
|
695
|
+
window = streamWindow(msg.streams?.output?.window);
|
|
387
696
|
}
|
|
388
697
|
catch (error) {
|
|
389
698
|
this.post({
|
|
390
699
|
id: msg.id,
|
|
391
|
-
mode: MESSAGE_MODEM_TYPE.
|
|
392
|
-
stream: true,
|
|
393
|
-
streamVersion: 1,
|
|
700
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_DATA,
|
|
394
701
|
data: {
|
|
702
|
+
direction: 'output',
|
|
395
703
|
status: 400,
|
|
396
704
|
seq: 0,
|
|
397
705
|
payload: error instanceof Error ? error.message : 'Invalid stream window',
|
|
@@ -401,13 +709,19 @@ export class MessageModem {
|
|
|
401
709
|
});
|
|
402
710
|
return;
|
|
403
711
|
}
|
|
404
|
-
if (this.streamProducers.has(msg.id)
|
|
712
|
+
if (this.streamProducers.has(msg.id)
|
|
713
|
+
|| this.streamProducers.size >= MAX_CONCURRENT_STREAMS
|
|
714
|
+
|| (msg.streams?.input && !this.canAcceptInputStream(msg.id))) {
|
|
405
715
|
this.post({
|
|
406
716
|
id: msg.id,
|
|
407
|
-
mode: MESSAGE_MODEM_TYPE.
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
717
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_DATA,
|
|
718
|
+
data: {
|
|
719
|
+
direction: 'output',
|
|
720
|
+
status: 429,
|
|
721
|
+
seq: 0,
|
|
722
|
+
payload: 'Stream capacity exceeded',
|
|
723
|
+
final: true,
|
|
724
|
+
},
|
|
411
725
|
twoway: false,
|
|
412
726
|
});
|
|
413
727
|
return;
|
|
@@ -417,59 +731,63 @@ export class MessageModem {
|
|
|
417
731
|
credits: window,
|
|
418
732
|
maxCredits: window,
|
|
419
733
|
nextCreditSeq: 0,
|
|
734
|
+
nextSeq: 0,
|
|
735
|
+
cancelled: false,
|
|
736
|
+
iteratorReturned: false,
|
|
420
737
|
};
|
|
421
|
-
let sequence = 0;
|
|
422
738
|
this.aborts.set(msg.id, controller);
|
|
423
739
|
this.streamProducers.set(msg.id, producer);
|
|
740
|
+
const input = msg.streams?.input ? this.createInputConsumer(msg.id) : undefined;
|
|
424
741
|
const takeCredit = async () => {
|
|
425
|
-
while (producer.credits === 0 && !controller.signal.aborted) {
|
|
742
|
+
while (producer.credits === 0 && !controller.signal.aborted && !producer.cancelled) {
|
|
426
743
|
await new Promise((resolve) => {
|
|
427
744
|
producer.wake = resolve;
|
|
428
745
|
});
|
|
429
746
|
producer.wake = undefined;
|
|
430
747
|
}
|
|
431
|
-
if (controller.signal.aborted)
|
|
748
|
+
if (controller.signal.aborted || producer.cancelled)
|
|
432
749
|
throw new AbortException();
|
|
433
750
|
producer.credits--;
|
|
434
751
|
};
|
|
435
|
-
this.exec(msg.data, controller.signal)
|
|
752
|
+
Promise.resolve(this.exec(msg.data, controller.signal, input))
|
|
436
753
|
.then(async (value) => {
|
|
437
754
|
if (!isAsyncIterable(value)) {
|
|
438
755
|
throw new Exception(500, 'Invalid async iterable');
|
|
439
756
|
}
|
|
440
757
|
const iterator = value[Symbol.asyncIterator]();
|
|
441
758
|
producer.iterator = iterator;
|
|
442
|
-
while (!controller.signal.aborted) {
|
|
759
|
+
while (!controller.signal.aborted && !producer.cancelled) {
|
|
443
760
|
await takeCredit();
|
|
444
761
|
const next = await iterator.next();
|
|
445
|
-
if (controller.signal.aborted)
|
|
762
|
+
if (controller.signal.aborted || producer.cancelled)
|
|
446
763
|
return;
|
|
447
764
|
if (next.done)
|
|
448
765
|
break;
|
|
766
|
+
if (next.value == null) {
|
|
767
|
+
throw new Exception(500, 'Stream chunk must not be null or undefined');
|
|
768
|
+
}
|
|
449
769
|
this.post({
|
|
450
770
|
id: msg.id,
|
|
451
|
-
mode: MESSAGE_MODEM_TYPE.
|
|
452
|
-
stream: true,
|
|
453
|
-
streamVersion: msg.streamVersion,
|
|
771
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_DATA,
|
|
454
772
|
data: {
|
|
773
|
+
direction: 'output',
|
|
455
774
|
status: 200,
|
|
456
|
-
seq:
|
|
775
|
+
seq: producer.nextSeq++,
|
|
457
776
|
payload: next.value,
|
|
458
777
|
final: false,
|
|
459
778
|
},
|
|
460
779
|
twoway: false,
|
|
461
780
|
});
|
|
462
781
|
}
|
|
463
|
-
if (controller.signal.aborted)
|
|
782
|
+
if (controller.signal.aborted || producer.cancelled)
|
|
464
783
|
return;
|
|
465
784
|
this.post({
|
|
466
785
|
id: msg.id,
|
|
467
|
-
mode: MESSAGE_MODEM_TYPE.
|
|
468
|
-
stream: true,
|
|
469
|
-
streamVersion: msg.streamVersion,
|
|
786
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_DATA,
|
|
470
787
|
data: {
|
|
788
|
+
direction: 'output',
|
|
471
789
|
status: 200,
|
|
472
|
-
seq:
|
|
790
|
+
seq: producer.nextSeq++,
|
|
473
791
|
payload: undefined,
|
|
474
792
|
final: true,
|
|
475
793
|
},
|
|
@@ -477,16 +795,15 @@ export class MessageModem {
|
|
|
477
795
|
});
|
|
478
796
|
})
|
|
479
797
|
.catch(e => {
|
|
480
|
-
if (controller.signal.aborted)
|
|
798
|
+
if (controller.signal.aborted || producer.cancelled)
|
|
481
799
|
return;
|
|
482
800
|
this.post({
|
|
483
801
|
id: msg.id,
|
|
484
|
-
mode: MESSAGE_MODEM_TYPE.
|
|
485
|
-
stream: true,
|
|
486
|
-
streamVersion: msg.streamVersion,
|
|
802
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_DATA,
|
|
487
803
|
data: {
|
|
804
|
+
direction: 'output',
|
|
488
805
|
status: e instanceof Exception ? e.status : 500,
|
|
489
|
-
seq:
|
|
806
|
+
seq: producer.nextSeq,
|
|
490
807
|
payload: e instanceof Error ? e.message : 'Unknown error',
|
|
491
808
|
final: true,
|
|
492
809
|
},
|
|
@@ -494,10 +811,11 @@ export class MessageModem {
|
|
|
494
811
|
});
|
|
495
812
|
})
|
|
496
813
|
.finally(() => {
|
|
497
|
-
if (controller.signal.aborted && producer.iterator?.return) {
|
|
498
|
-
|
|
814
|
+
if ((controller.signal.aborted || producer.cancelled) && producer.iterator?.return) {
|
|
815
|
+
returnProducerIterator(producer);
|
|
499
816
|
}
|
|
500
817
|
producer.wake?.();
|
|
818
|
+
this.cancelInputConsumer(msg.id);
|
|
501
819
|
this.streamProducers.delete(msg.id);
|
|
502
820
|
this.aborts.delete(msg.id);
|
|
503
821
|
});
|
|
@@ -506,25 +824,29 @@ export class MessageModem {
|
|
|
506
824
|
const id = msg.id;
|
|
507
825
|
const res = msg.data;
|
|
508
826
|
const consumer = this.streams.get(id);
|
|
509
|
-
|
|
510
|
-
if (consumer) {
|
|
827
|
+
if (consumer && !consumer.completed && !consumer.cancelled) {
|
|
511
828
|
const stream = consumer.stream;
|
|
512
|
-
if (res) {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
829
|
+
if (res?.direction === 'output') {
|
|
830
|
+
if (!Number.isSafeInteger(res.seq)
|
|
831
|
+
|| res.seq !== consumer.nextSeq
|
|
832
|
+
|| typeof res.final !== 'boolean'
|
|
833
|
+
|| (typeof res.status !== 'string' && typeof res.status !== 'number')
|
|
834
|
+
|| (!res.final && res.payload == null)) {
|
|
835
|
+
this.failOutputConsumer(id, new Exception(409, `Invalid response stream frame: expected sequence ${consumer.nextSeq}, received ${String(res.seq)}`));
|
|
516
836
|
return;
|
|
517
837
|
}
|
|
838
|
+
consumer.touch();
|
|
518
839
|
consumer.nextSeq++;
|
|
519
840
|
if (res.status === 200) {
|
|
520
841
|
if (res.final) {
|
|
521
842
|
consumer.completed = true;
|
|
522
|
-
|
|
843
|
+
this.cancelInputProducer(id);
|
|
844
|
+
consumer.cleanup();
|
|
523
845
|
stream.push(null);
|
|
524
846
|
}
|
|
525
847
|
else {
|
|
526
848
|
if (consumer.creditsOwed >= consumer.maxCredits) {
|
|
527
|
-
|
|
849
|
+
this.failOutputConsumer(id, new Exception(429, 'Response stream window exceeded'));
|
|
528
850
|
return;
|
|
529
851
|
}
|
|
530
852
|
consumer.creditsOwed++;
|
|
@@ -533,18 +855,165 @@ export class MessageModem {
|
|
|
533
855
|
}
|
|
534
856
|
else {
|
|
535
857
|
consumer.completed = true;
|
|
536
|
-
|
|
537
|
-
|
|
858
|
+
this.cancelInputProducer(id);
|
|
859
|
+
consumer.cleanup();
|
|
860
|
+
const err = new Exception(res.status ?? 500, res.message ?? String(res.payload ?? 'Stream failed'));
|
|
538
861
|
setImmediate(() => stream.destroy(err));
|
|
539
862
|
}
|
|
540
863
|
}
|
|
541
864
|
else {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
865
|
+
this.failOutputConsumer(id, res
|
|
866
|
+
? new Exception(400, 'Invalid response stream direction')
|
|
867
|
+
: new Exception(404, 'Empty chunk data'));
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
failOutputConsumer(id, error) {
|
|
872
|
+
const consumer = this.streams.get(id);
|
|
873
|
+
if (!consumer || consumer.completed || consumer.cancelled)
|
|
874
|
+
return;
|
|
875
|
+
try {
|
|
876
|
+
this.post({
|
|
877
|
+
id,
|
|
878
|
+
mode: MESSAGE_MODEM_TYPE.ABORT,
|
|
879
|
+
twoway: false,
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
catch {
|
|
883
|
+
// The transport may already be closed.
|
|
884
|
+
}
|
|
885
|
+
consumer.cancelled = true;
|
|
886
|
+
this.cancelInputProducer(id);
|
|
887
|
+
consumer.cleanup();
|
|
888
|
+
consumer.stream.destroy(error);
|
|
889
|
+
}
|
|
890
|
+
onInputStreamData(msg) {
|
|
891
|
+
const consumer = this.inputStreams.get(msg.id);
|
|
892
|
+
if (!consumer)
|
|
893
|
+
return;
|
|
894
|
+
const chunk = msg.data;
|
|
895
|
+
if (!chunk
|
|
896
|
+
|| chunk.direction !== 'input'
|
|
897
|
+
|| !Number.isSafeInteger(chunk.seq)
|
|
898
|
+
|| chunk.seq !== consumer.nextSeq
|
|
899
|
+
|| typeof chunk.final !== 'boolean'
|
|
900
|
+
|| (!chunk.final && chunk.payload == null)) {
|
|
901
|
+
const received = chunk?.seq;
|
|
902
|
+
this.failInputConsumer(msg.id, new Exception(409, `Invalid request input sequence: expected ${consumer.nextSeq}, received ${String(received)}`));
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
consumer.nextSeq++;
|
|
906
|
+
if (chunk.final) {
|
|
907
|
+
consumer.completed = true;
|
|
908
|
+
this.inputStreams.delete(msg.id);
|
|
909
|
+
consumer.stream.push(null);
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
if (consumer.creditsOwed >= consumer.maxCredits) {
|
|
913
|
+
this.failInputConsumer(msg.id, new Exception(429, 'Request input stream window exceeded'));
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
consumer.creditsOwed++;
|
|
917
|
+
consumer.stream.push(chunk.payload);
|
|
918
|
+
}
|
|
919
|
+
onStreamCredit(msg) {
|
|
920
|
+
const credit = msg.data;
|
|
921
|
+
if (!credit
|
|
922
|
+
|| (credit.direction !== 'input' && credit.direction !== 'output'))
|
|
923
|
+
return;
|
|
924
|
+
const producer = credit.direction === 'input'
|
|
925
|
+
? this.inputStreamProducers.get(msg.id)
|
|
926
|
+
: this.streamProducers.get(msg.id);
|
|
927
|
+
if (!producer || producer.cancelled)
|
|
928
|
+
return;
|
|
929
|
+
if (!Number.isSafeInteger(credit.seq) || credit.seq < 0) {
|
|
930
|
+
if (credit.direction === 'input') {
|
|
931
|
+
this.failInputProducer(msg.id, new Exception(400, 'Invalid request input credit sequence'));
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
this.failOutputProducer(msg.id, new Exception(400, 'Invalid response stream credit sequence'));
|
|
935
|
+
}
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
if (credit.seq < producer.nextCreditSeq)
|
|
939
|
+
return;
|
|
940
|
+
if (credit.seq > producer.nextCreditSeq) {
|
|
941
|
+
if (credit.direction === 'input') {
|
|
942
|
+
this.failInputProducer(msg.id, new Exception(409, 'Request input credit sequence skipped'));
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
this.failOutputProducer(msg.id, new Exception(409, 'Response stream credit sequence skipped'));
|
|
946
|
+
}
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
if (producer.maxCredits === undefined) {
|
|
950
|
+
if (credit.direction !== 'input')
|
|
951
|
+
return;
|
|
952
|
+
try {
|
|
953
|
+
producer.maxCredits = streamWindow(credit.window);
|
|
954
|
+
}
|
|
955
|
+
catch {
|
|
956
|
+
this.failInputProducer(msg.id, new Exception(400, 'Invalid request input stream window'));
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
else if (credit.window !== undefined && credit.window !== producer.maxCredits) {
|
|
961
|
+
if (credit.direction === 'input') {
|
|
962
|
+
this.failInputProducer(msg.id, new Exception(409, 'Request input stream window changed'));
|
|
963
|
+
}
|
|
964
|
+
else {
|
|
965
|
+
this.failOutputProducer(msg.id, new Exception(409, 'Response stream window changed'));
|
|
546
966
|
}
|
|
967
|
+
return;
|
|
547
968
|
}
|
|
969
|
+
if (producer.credits >= producer.maxCredits) {
|
|
970
|
+
if (credit.direction === 'input') {
|
|
971
|
+
this.failInputProducer(msg.id, new Exception(429, 'Request input credit window exceeded'));
|
|
972
|
+
}
|
|
973
|
+
else {
|
|
974
|
+
this.failOutputProducer(msg.id, new Exception(429, 'Response stream credit window exceeded'));
|
|
975
|
+
}
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
producer.credits++;
|
|
979
|
+
producer.nextCreditSeq++;
|
|
980
|
+
producer.wake?.();
|
|
981
|
+
}
|
|
982
|
+
failOutputProducer(id, error) {
|
|
983
|
+
const producer = this.streamProducers.get(id);
|
|
984
|
+
if (!producer)
|
|
985
|
+
return;
|
|
986
|
+
try {
|
|
987
|
+
this.post({
|
|
988
|
+
id,
|
|
989
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_DATA,
|
|
990
|
+
twoway: false,
|
|
991
|
+
data: {
|
|
992
|
+
direction: 'output',
|
|
993
|
+
status: error.status,
|
|
994
|
+
seq: producer.nextSeq,
|
|
995
|
+
final: true,
|
|
996
|
+
message: error.message,
|
|
997
|
+
},
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
catch {
|
|
1001
|
+
// The transport may already be closed.
|
|
1002
|
+
}
|
|
1003
|
+
this.cancelOutputProducer(id);
|
|
1004
|
+
const controller = this.aborts.get(id);
|
|
1005
|
+
this.aborts.delete(id);
|
|
1006
|
+
if (controller && !controller.signal.aborted)
|
|
1007
|
+
controller.abort();
|
|
1008
|
+
}
|
|
1009
|
+
cancelOutputProducer(id) {
|
|
1010
|
+
const producer = this.streamProducers.get(id);
|
|
1011
|
+
if (!producer)
|
|
1012
|
+
return;
|
|
1013
|
+
this.streamProducers.delete(id);
|
|
1014
|
+
producer.cancelled = true;
|
|
1015
|
+
producer.wake?.();
|
|
1016
|
+
returnProducerIterator(producer);
|
|
548
1017
|
}
|
|
549
1018
|
/**
|
|
550
1019
|
* 接收消息
|
|
@@ -555,7 +1024,7 @@ export class MessageModem {
|
|
|
555
1024
|
switch (msg.mode) {
|
|
556
1025
|
// 处理请求消息
|
|
557
1026
|
case MESSAGE_MODEM_TYPE.REQUEST:
|
|
558
|
-
if (msg.
|
|
1027
|
+
if (msg.streams?.output) {
|
|
559
1028
|
this.onStreamRequest(msg);
|
|
560
1029
|
}
|
|
561
1030
|
else {
|
|
@@ -564,41 +1033,56 @@ export class MessageModem {
|
|
|
564
1033
|
break;
|
|
565
1034
|
// 处理响应消息
|
|
566
1035
|
case MESSAGE_MODEM_TYPE.RESPONSE:
|
|
567
|
-
|
|
1036
|
+
this.onResponse(msg);
|
|
1037
|
+
break;
|
|
1038
|
+
case MESSAGE_MODEM_TYPE.STREAM_DATA: {
|
|
1039
|
+
const chunk = msg.data;
|
|
1040
|
+
if (chunk?.direction === 'input')
|
|
1041
|
+
this.onInputStreamData(msg);
|
|
1042
|
+
else if (chunk?.direction === 'output')
|
|
568
1043
|
this.onStreamResponse(msg);
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
1044
|
+
else if (this.streams.has(msg.id))
|
|
1045
|
+
this.onStreamResponse(msg);
|
|
1046
|
+
else if (this.inputStreams.has(msg.id))
|
|
1047
|
+
this.onInputStreamData(msg);
|
|
573
1048
|
break;
|
|
1049
|
+
}
|
|
574
1050
|
// 处理终止消息
|
|
575
1051
|
case MESSAGE_MODEM_TYPE.ABORT: {
|
|
576
|
-
const id = msg.
|
|
1052
|
+
const id = msg.id;
|
|
577
1053
|
const controller = this.aborts.get(id);
|
|
578
1054
|
if (controller) {
|
|
579
1055
|
this.aborts.delete(id);
|
|
580
|
-
this.streamProducers.get(id)?.wake?.();
|
|
581
1056
|
if (!controller.signal.aborted) {
|
|
582
1057
|
controller.abort();
|
|
583
1058
|
}
|
|
584
1059
|
}
|
|
1060
|
+
this.cancelOutputProducer(id);
|
|
1061
|
+
this.cancelInputConsumer(id, 'Request aborted');
|
|
585
1062
|
break;
|
|
586
1063
|
}
|
|
587
|
-
case MESSAGE_MODEM_TYPE.STREAM_CREDIT:
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
1064
|
+
case MESSAGE_MODEM_TYPE.STREAM_CREDIT:
|
|
1065
|
+
this.onStreamCredit(msg);
|
|
1066
|
+
break;
|
|
1067
|
+
case MESSAGE_MODEM_TYPE.STREAM_CANCEL: {
|
|
1068
|
+
const cancel = msg.data;
|
|
1069
|
+
if (cancel?.direction === 'input') {
|
|
1070
|
+
this.cancelInputProducer(msg.id);
|
|
1071
|
+
if (cancel.status !== undefined) {
|
|
1072
|
+
const error = new Exception(cancel.status, cancel.message ?? 'Request input stream rejected');
|
|
1073
|
+
const stack = this.stacks.get(msg.id);
|
|
1074
|
+
if (stack)
|
|
1075
|
+
stack.reject(error);
|
|
1076
|
+
else
|
|
1077
|
+
this.failOutputConsumer(msg.id, error);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
else if (cancel?.direction === 'output') {
|
|
1081
|
+
this.cancelOutputProducer(msg.id);
|
|
1082
|
+
const controller = this.aborts.get(msg.id);
|
|
1083
|
+
this.aborts.delete(msg.id);
|
|
1084
|
+
if (controller && !controller.signal.aborted)
|
|
1085
|
+
controller.abort();
|
|
602
1086
|
}
|
|
603
1087
|
break;
|
|
604
1088
|
}
|