grpc-libp2p-client 0.0.38 → 0.0.40

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/dist/index.esm.js CHANGED
@@ -239,7 +239,6 @@ class HPACK {
239
239
  this.dynamicTable.unshift([name, value]);
240
240
  this.dynamicTableSize += size;
241
241
  }
242
- this.dynamicTable.push([name, value]);
243
242
  }
244
243
  // 获取索引的头部
245
244
  getIndexedHeader(index) {
@@ -356,22 +355,29 @@ class HPACK {
356
355
  // Huffman编码实现
357
356
  huffmanEncode(bytes) {
358
357
  const result = [];
358
+ // 使用高精度浮点数累积位,避免 JS 32-bit 有符号整数在位数 >31 时溢出。
359
+ // Huffman 码最长 30 bits,加上未输出的最多 7 bits = 37 bits,超过 32-bit 安全范围。
360
+ // Number 可精确表示 2^53 以内的整数,足够累积多个码字。
359
361
  let current = 0;
360
362
  let bits = 0;
361
363
  for (let i = 0; i < bytes.length; i++) {
362
364
  const b = bytes[i];
363
365
  const code = this.huffmanTable.codes[b];
364
366
  const length = this.huffmanTable.lengths[b];
367
+ // 用乘法左移替代 <<,避免 32-bit 截断
368
+ current = current * (1 << length) + code;
365
369
  bits += length;
366
- current = (current << length) | code;
367
370
  while (bits >= 8) {
368
371
  bits -= 8;
369
- result.push((current >> bits) & 0xFF);
372
+ result.push(Math.floor(current / (1 << bits)) & 0xFF);
373
+ // 保留低 bits 位
374
+ current = current % (1 << bits);
370
375
  }
371
376
  }
372
- // 处理剩余的位
377
+ // 处理剩余的位(用 EOS 填充 1)
373
378
  if (bits > 0) {
374
- current = (current << (8 - bits)) | ((1 << (8 - bits)) - 1);
379
+ const pad = 8 - bits;
380
+ current = current * (1 << pad) + ((1 << pad) - 1);
375
381
  result.push(current & 0xFF);
376
382
  }
377
383
  return new Uint8Array(result);
@@ -394,8 +400,16 @@ class HPACK {
394
400
  headers.set(name, value);
395
401
  index = newIndex;
396
402
  }
397
- else if ((firstByte & 0x20) !== 0) { // 001xxxxx - Dynamic Table Size Update
398
- index++; // 简单跳过,实际应该更新动态表大小
403
+ else if ((firstByte & 0x20) !== 0) { // 001xxxxx - Dynamic Table Size Update (RFC 7541 §6.3)
404
+ const [newSize, newIndex] = this.decodeInteger(buffer, index, 5);
405
+ this.maxDynamicTableSize = newSize;
406
+ // evict entries that exceed the new limit
407
+ while (this.dynamicTableSize > this.maxDynamicTableSize && this.dynamicTable.length > 0) {
408
+ const entry = this.dynamicTable.pop();
409
+ if (entry)
410
+ this.dynamicTableSize -= entry[0].length + entry[1].length + 32;
411
+ }
412
+ index = newIndex;
399
413
  }
400
414
  else if ((firstByte & 0x10) !== 0) { // 0001xxxx - Literal Header Field Never Indexed
401
415
  const [name, value, newIndex] = this.decodeLiteralHeaderWithoutIndexing(buffer, index);
@@ -467,18 +481,18 @@ class HPACK {
467
481
  if (staticIndex <= 0) {
468
482
  return ['', '', newIndex];
469
483
  }
470
- const headerField = this.staticTable[staticIndex];
484
+ const headerField = this.getIndexedHeader(staticIndex);
471
485
  if (!headerField) {
472
486
  return ['', '', newIndex];
473
487
  }
474
488
  return [headerField[0], headerField[1], newIndex];
475
489
  }
476
490
  decodeLiteralHeaderWithIndexing(buffer, index) {
477
- const [staticIndex, nameIndex] = this.decodeInteger(buffer, index, 6);
478
- index = nameIndex;
491
+ const [nameIndex, nextIndex] = this.decodeInteger(buffer, index, 6);
492
+ index = nextIndex;
479
493
  let name;
480
- if (staticIndex > 0) {
481
- const headerField = this.staticTable[staticIndex];
494
+ if (nameIndex > 0) {
495
+ const headerField = this.getIndexedHeader(nameIndex);
482
496
  name = headerField ? headerField[0] : '';
483
497
  }
484
498
  else {
@@ -487,10 +501,26 @@ class HPACK {
487
501
  index = newIndex;
488
502
  }
489
503
  const [value, finalIndex] = this.decodeLiteralString(buffer, index);
504
+ // RFC 7541 §6.2.1: Literal Header Field with Incremental Indexing must add to dynamic table
505
+ this.addToDynamicTable(name, value);
490
506
  return [name, value, finalIndex];
491
507
  }
492
508
  decodeLiteralHeaderWithoutIndexing(buffer, index) {
493
- return this.decodeLiteralHeaderWithIndexing(buffer, index);
509
+ // RFC 7541 §6.2.2 / §6.2.3: 4-bit prefix, do NOT add to dynamic table
510
+ const [nameIndex, nextIndex] = this.decodeInteger(buffer, index, 4);
511
+ index = nextIndex;
512
+ let name;
513
+ if (nameIndex > 0) {
514
+ const headerField = this.getIndexedHeader(nameIndex);
515
+ name = headerField ? headerField[0] : '';
516
+ }
517
+ else {
518
+ const [decodedName, newIndex] = this.decodeLiteralString(buffer, index);
519
+ name = decodedName;
520
+ index = newIndex;
521
+ }
522
+ const [value, finalIndex] = this.decodeLiteralString(buffer, index);
523
+ return [name, value, finalIndex];
494
524
  }
495
525
  // 直接转换为字符串的方法
496
526
  huffmanDecodeToString(bytes) {
@@ -578,9 +608,11 @@ const SETTINGS_PARAMETERS = {
578
608
  };
579
609
  const defaultSettings = {
580
610
  [SETTINGS_PARAMETERS.HEADER_TABLE_SIZE]: 4096,
581
- [SETTINGS_PARAMETERS.ENABLE_PUSH]: 1,
611
+ // gRPC 客户端不使用 Server Push,禁用以避免无效的 PUSH_PROMISE 处理
612
+ [SETTINGS_PARAMETERS.ENABLE_PUSH]: 0,
582
613
  [SETTINGS_PARAMETERS.MAX_CONCURRENT_STREAMS]: 100,
583
- [SETTINGS_PARAMETERS.INITIAL_WINDOW_SIZE]: 16 << 10, // 16k
614
+ // 匹配 parser 的实际接收缓冲区大小(4MB),避免服务端在单流上过早被限速
615
+ [SETTINGS_PARAMETERS.INITIAL_WINDOW_SIZE]: 4 << 20, // 4MB
584
616
  [SETTINGS_PARAMETERS.MAX_FRAME_SIZE]: 16 << 10, // 16k
585
617
  [SETTINGS_PARAMETERS.MAX_HEADER_LIST_SIZE]: 8192
586
618
  };
@@ -641,8 +673,8 @@ class Http2Frame {
641
673
  // Message-Data
642
674
  grpcMessage.set(data, 5);
643
675
  // 然后将完整的 gRPC 消息分割成多个 HTTP/2 DATA 帧
644
- // HTTP/2 帧头为 9 字节
645
- const maxDataPerFrame = maxFrameSize - 9;
676
+ // maxFrameSize 是 payload 上限(RFC 7540 §6.5.2 MAX_FRAME_SIZE),不含 9 字节帧头
677
+ const maxDataPerFrame = maxFrameSize;
646
678
  for (let offset = 0; offset < grpcMessage.length; offset += maxDataPerFrame) {
647
679
  const remaining = grpcMessage.length - offset;
648
680
  const chunkSize = Math.min(maxDataPerFrame, remaining);
@@ -674,13 +706,13 @@ class Http2Frame {
674
706
  const flags = endStream ? 0x01 : 0x0; // END_STREAM flag
675
707
  return Http2Frame.createFrame(0x0, flags, streamId, framedData);
676
708
  }
677
- static createHeadersFrame(streamId, path, endHeaders = true, token) {
709
+ static createHeadersFrame(streamId, path, endHeaders = true, token, authority = 'localhost') {
678
710
  // gRPC-Web 需要的标准 headers
679
711
  const headersList = {
680
712
  ':path': path,
681
713
  ':method': 'POST',
682
714
  ':scheme': 'http',
683
- ':authority': 'localhost',
715
+ ':authority': authority,
684
716
  'content-type': 'application/grpc+proto',
685
717
  'user-agent': 'grpc-web-client/0.1',
686
718
  'accept': 'application/grpc+proto',
@@ -807,8 +839,15 @@ function _createPayload(settings) {
807
839
 
808
840
  const HTTP2_PREFACE = new TextEncoder().encode("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
809
841
  class HTTP2Parser {
842
+ /** 兼容旧代码读取 buffer —— 仅在必须全量访问时调用 _flattenBuffer() */
843
+ get buffer() { return this._flattenBuffer(); }
844
+ set buffer(v) { this.bufferChunks = v.length ? [v] : []; this.bufferTotalLength = v.length; }
810
845
  constructor(writer, options) {
811
- this.buffer = new Uint8Array(0);
846
+ /** 分段缓冲:避免每次 chunk 到达时 O(n) 全量拷贝 */
847
+ this.bufferChunks = [];
848
+ this.bufferTotalLength = 0;
849
+ this.bufferChunks = [];
850
+ this.bufferTotalLength = 0;
812
851
  this.settingsAckReceived = false;
813
852
  this.peerSettingsReceived = false;
814
853
  // 初始化连接级别的流控制窗口大小(默认值:65,535)
@@ -822,11 +861,38 @@ class HTTP2Parser {
822
861
  this.sendStreamWindows = new Map();
823
862
  this.peerInitialStreamWindow = 65535;
824
863
  this.sendWindowWaiters = [];
864
+ this.settingsAckWaiters = [];
865
+ this.peerSettingsWaiters = [];
866
+ this.endOfStreamWaiters = [];
825
867
  // 结束标志
826
868
  this.endFlag = false;
827
869
  this.writer = writer;
828
870
  this.compatibilityMode = options?.compatibilityMode ?? false;
829
871
  }
872
+ /** 将所有分段合并为一个连续 Uint8Array(仅在必要时调用)*/
873
+ _flattenBuffer() {
874
+ if (this.bufferChunks.length === 0)
875
+ return new Uint8Array(0);
876
+ if (this.bufferChunks.length === 1)
877
+ return this.bufferChunks[0];
878
+ const out = new Uint8Array(this.bufferTotalLength);
879
+ let off = 0;
880
+ for (const c of this.bufferChunks) {
881
+ out.set(c, off);
882
+ off += c.length;
883
+ }
884
+ return out;
885
+ }
886
+ /** 唤醒所有发送窗口等待者 */
887
+ _wakeWindowWaiters() {
888
+ const ws = this.sendWindowWaiters.splice(0);
889
+ for (const w of ws) {
890
+ try {
891
+ w.resolve();
892
+ }
893
+ catch { /* ignore */ }
894
+ }
895
+ }
830
896
  // 持续处理流数据
831
897
  async processStream(stream) {
832
898
  try {
@@ -836,7 +902,6 @@ class HTTP2Parser {
836
902
  }
837
903
  // Stream 结束后的清理工作
838
904
  if (!this.compatibilityMode && !this.endFlag) {
839
- this.endFlag = true;
840
905
  try {
841
906
  this.onEnd?.();
842
907
  }
@@ -844,51 +909,84 @@ class HTTP2Parser {
844
909
  console.error("Error during onEnd callback:", err);
845
910
  }
846
911
  }
912
+ // 无论何种模式,stream 结束时都通知 waitForEndOfStream 等待者,
913
+ // 防止 compatibilityMode=true(server-streaming)时 waitForEndOfStream(0) 永久挂死
914
+ if (!this.endFlag) {
915
+ this._notifyEndOfStream();
916
+ }
847
917
  }
848
918
  catch (error) {
849
- console.error("Error processing stream:", error);
919
+ // abort() 触发的清理错误(如 'Call cleanup' / 'unaryCall cleanup')属于预期行为,降级为 debug 日志
920
+ const errMsg = error instanceof Error ? error.message : String(error);
921
+ const isAbortCleanup = /cleanup/i.test(errMsg) || /aborted/i.test(errMsg);
922
+ if (isAbortCleanup) {
923
+ console.debug("[processStream] stream aborted (expected):", errMsg);
924
+ }
925
+ else {
926
+ console.error("Error processing stream:", error);
927
+ }
928
+ // 确保 waitForEndOfStream 等待者得到通知,防止 operationPromise 后台挂死
929
+ if (!this.endFlag) {
930
+ this._notifyEndOfStream();
931
+ }
850
932
  throw error;
851
933
  }
852
934
  }
853
- // 处理单个数据块
935
+ // 处理单个数据块 — 分段列表追加,避免每次 O(n) 全量拷贝
854
936
  _processChunk(chunk) {
855
937
  // chunk 是 Uint8ArrayList 或 Uint8Array
856
938
  const newData = 'subarray' in chunk && typeof chunk.subarray === 'function'
857
939
  ? chunk.subarray()
858
940
  : chunk;
859
- // 原作者之前的 O(N) 内存拷贝优化被保留,去掉了存在 onEnd 竞态的 setTimeout
860
- const newBuffer = new Uint8Array(this.buffer.length + newData.length);
861
- newBuffer.set(this.buffer);
862
- newBuffer.set(newData, this.buffer.length);
863
- this.buffer = newBuffer;
941
+ // 追加到分段列表,O(1),不拷贝历史数据
942
+ if (newData.length > 0) {
943
+ this.bufferChunks.push(newData);
944
+ this.bufferTotalLength += newData.length;
945
+ }
946
+ // 将所有分段合并为一块后处理帧(只合并一次,后续 slice 替换)
947
+ // 仅在确实有完整帧时才触发合并,碎片仅 push 不合并
948
+ if (this.bufferTotalLength < 9)
949
+ return;
950
+ // 合并一次
951
+ const flat = this._flattenBuffer();
952
+ this.bufferChunks = [flat];
953
+ // bufferTotalLength 保持不变
864
954
  // 持续处理所有完整的帧
865
955
  let readOffset = 0;
866
- while (this.buffer.length - readOffset >= 9) {
956
+ while (flat.length - readOffset >= 9) {
867
957
  // 判断是否有HTTP/2前导
868
- if (this.buffer.length - readOffset >= 24 && this.isHttp2Preface(this.buffer.subarray(readOffset))) {
958
+ if (flat.length - readOffset >= 24 && this.isHttp2Preface(flat.subarray(readOffset))) {
869
959
  readOffset += 24;
870
960
  // 发送SETTINGS帧
871
961
  const settingFrame = Http2Frame.createSettingsFrame();
872
962
  this.writer.write(settingFrame);
873
963
  continue;
874
964
  }
875
- const frameHeader = this._parseFrameHeader(this.buffer.subarray(readOffset));
965
+ const frameHeader = this._parseFrameHeader(flat.subarray(readOffset));
876
966
  const totalFrameLength = 9 + frameHeader.length;
877
967
  // 检查是否有完整的帧
878
- if (this.buffer.length - readOffset < totalFrameLength) {
968
+ if (flat.length - readOffset < totalFrameLength) {
879
969
  break;
880
970
  }
881
- // 获取完整帧数据
882
- const frameData = this.buffer.subarray(readOffset, readOffset + totalFrameLength);
971
+ // 获取完整帧数据(subarray 视图,零拷贝)
972
+ const frameData = flat.subarray(readOffset, readOffset + totalFrameLength);
883
973
  // 处理不同类型的帧
884
974
  this._handleFrame(frameHeader, frameData).catch((err) => {
885
975
  console.error("Error handling frame:", err);
886
976
  });
887
- // 移动偏移量
888
977
  readOffset += totalFrameLength;
889
978
  }
979
+ // 保留未消费的尾部字节(slice 一次,后续仍分段追加)
890
980
  if (readOffset > 0) {
891
- this.buffer = this.buffer.slice(readOffset);
981
+ if (readOffset >= flat.length) {
982
+ this.bufferChunks = [];
983
+ this.bufferTotalLength = 0;
984
+ }
985
+ else {
986
+ const remaining = flat.slice(readOffset);
987
+ this.bufferChunks = [remaining];
988
+ this.bufferTotalLength = remaining.length;
989
+ }
892
990
  }
893
991
  }
894
992
  isHttp2Preface(buffer) {
@@ -900,50 +998,67 @@ class HTTP2Parser {
900
998
  }
901
999
  return true;
902
1000
  }
903
- // 移除之前的 for await 循环代码
904
- _oldProcessStream_removed() {
905
- // 这个方法已被上面的事件驱动实现替代
906
- }
907
- // 等待SETTINGS ACK
1001
+ // 等待SETTINGS ACK 事件驱动,无轮询
908
1002
  waitForSettingsAck() {
909
1003
  return new Promise((resolve, reject) => {
910
1004
  if (this.settingsAckReceived) {
911
1005
  resolve();
912
1006
  return;
913
1007
  }
914
- const interval = setInterval(() => {
915
- if (this.settingsAckReceived) {
916
- clearInterval(interval);
917
- clearTimeout(timeout);
918
- resolve();
919
- }
920
- }, 100);
1008
+ const waiter = { resolve, reject };
1009
+ this.settingsAckWaiters.push(waiter);
921
1010
  const timeout = setTimeout(() => {
922
- clearInterval(interval);
1011
+ const idx = this.settingsAckWaiters.indexOf(waiter);
1012
+ if (idx >= 0)
1013
+ this.settingsAckWaiters.splice(idx, 1);
923
1014
  reject(new Error("Settings ACK timeout"));
924
1015
  }, 30000);
1016
+ // 覆盖 resolve 以便超时前自动清理定时器
1017
+ waiter.resolve = () => { clearTimeout(timeout); resolve(); };
1018
+ waiter.reject = (e) => { clearTimeout(timeout); reject(e); };
925
1019
  });
926
1020
  }
927
- // 等待接收来自对端的 SETTINGS(非 ACK
1021
+ /** 内部调用:SETTINGS ACK 收到时唤醒所有等待者 */
1022
+ _notifySettingsAck() {
1023
+ this.settingsAckReceived = true;
1024
+ const ws = this.settingsAckWaiters.splice(0);
1025
+ for (const w of ws) {
1026
+ try {
1027
+ w.resolve();
1028
+ }
1029
+ catch { /* ignore */ }
1030
+ }
1031
+ }
1032
+ // 等待接收来自对端的 SETTINGS(非 ACK)— 事件驱动,无轮询
928
1033
  waitForPeerSettings(timeoutMs = 30000) {
929
1034
  return new Promise((resolve, reject) => {
930
1035
  if (this.peerSettingsReceived) {
931
1036
  resolve();
932
1037
  return;
933
1038
  }
934
- const interval = setInterval(() => {
935
- if (this.peerSettingsReceived) {
936
- clearInterval(interval);
937
- clearTimeout(timeout);
938
- resolve();
939
- }
940
- }, 100);
1039
+ const waiter = { resolve, reject };
1040
+ this.peerSettingsWaiters.push(waiter);
941
1041
  const timeout = setTimeout(() => {
942
- clearInterval(interval);
1042
+ const idx = this.peerSettingsWaiters.indexOf(waiter);
1043
+ if (idx >= 0)
1044
+ this.peerSettingsWaiters.splice(idx, 1);
943
1045
  reject(new Error("Peer SETTINGS timeout"));
944
1046
  }, timeoutMs);
1047
+ waiter.resolve = () => { clearTimeout(timeout); resolve(); };
1048
+ waiter.reject = (e) => { clearTimeout(timeout); reject(e); };
945
1049
  });
946
1050
  }
1051
+ /** 内部调用:收到对端 SETTINGS(非 ACK)时唤醒等待者 */
1052
+ _notifyPeerSettings() {
1053
+ this.peerSettingsReceived = true;
1054
+ const ws = this.peerSettingsWaiters.splice(0);
1055
+ for (const w of ws) {
1056
+ try {
1057
+ w.resolve();
1058
+ }
1059
+ catch { /* ignore */ }
1060
+ }
1061
+ }
947
1062
  // 注册我们要发送数据的出站流(用于初始化该流的对端窗口)
948
1063
  registerOutboundStream(streamId) {
949
1064
  if (!this.sendStreamWindows.has(streamId)) {
@@ -970,54 +1085,51 @@ class HTTP2Parser {
970
1085
  this.sendConnWindow = Math.min(0x7fffffff, this.sendConnWindow + bytes);
971
1086
  const cur = this.sendStreamWindows.get(streamId) ?? 0;
972
1087
  this.sendStreamWindows.set(streamId, Math.min(0x7fffffff, cur + bytes));
1088
+ // 窗口增大,唤醒等待者
1089
+ this._wakeWindowWaiters();
973
1090
  }
974
- // 等待可用发送窗口(两个窗口都需要 >0)
975
- async waitForSendWindow(streamId, minBytes = 1, timeoutMs = 30000) {
976
- const start = Date.now();
1091
+ // 等待可用发送窗口 — 事件驱动,WINDOW_UPDATE/SETTINGS 收到时直接唤醒
1092
+ waitForSendWindow(streamId, minBytes = 1, timeoutMs = 30000) {
1093
+ const { conn, stream } = this.getSendWindows(streamId);
1094
+ if (conn >= minBytes && stream >= minBytes)
1095
+ return Promise.resolve();
977
1096
  return new Promise((resolve, reject) => {
978
- let interval = null;
979
1097
  let settled = false;
980
- const check = () => {
981
- const { conn, stream } = this.getSendWindows(streamId);
982
- if (conn >= minBytes && stream >= minBytes) {
983
- if (!settled) {
984
- settled = true;
985
- if (interval) {
986
- clearInterval(interval);
987
- interval = null;
988
- }
989
- resolve();
990
- }
991
- return true;
992
- }
993
- if (Date.now() - start > timeoutMs) {
994
- if (!settled) {
995
- settled = true;
996
- if (interval) {
997
- clearInterval(interval);
998
- interval = null;
999
- }
1000
- reject(new Error('Send window wait timeout'));
1001
- }
1002
- return true;
1098
+ const timeout = timeoutMs > 0
1099
+ ? setTimeout(() => {
1100
+ if (settled)
1101
+ return;
1102
+ settled = true;
1103
+ const idx = this.sendWindowWaiters.findIndex(w => w.resolve === resolveWrap);
1104
+ if (idx >= 0)
1105
+ this.sendWindowWaiters.splice(idx, 1);
1106
+ reject(new Error('Send window wait timeout'));
1107
+ }, timeoutMs)
1108
+ : undefined;
1109
+ const resolveWrap = () => {
1110
+ if (settled)
1111
+ return;
1112
+ const { conn: c2, stream: s2 } = this.getSendWindows(streamId);
1113
+ if (c2 >= minBytes && s2 >= minBytes) {
1114
+ settled = true;
1115
+ if (timeout)
1116
+ clearTimeout(timeout);
1117
+ resolve();
1118
+ }
1119
+ else {
1120
+ // 窗口仍不够,重新入队等待下一次更新
1121
+ this.sendWindowWaiters.push({ resolve: resolveWrap, reject: rejectWrap });
1003
1122
  }
1004
- return false;
1005
1123
  };
1006
- if (check())
1007
- return;
1008
- const tick = () => {
1009
- if (!check()) ;
1124
+ const rejectWrap = (e) => {
1125
+ if (settled)
1126
+ return;
1127
+ settled = true;
1128
+ if (timeout)
1129
+ clearTimeout(timeout);
1130
+ reject(e);
1010
1131
  };
1011
- const wake = () => { tick(); };
1012
- // 简单的等待模型:依赖 WINDOW_UPDATE 到达时调用 wake
1013
- this.sendWindowWaiters.push(wake);
1014
- // 同时做一个轻微的轮询,防止错过唤醒
1015
- interval = setInterval(() => {
1016
- if (check() && interval) {
1017
- clearInterval(interval);
1018
- interval = null;
1019
- }
1020
- }, 50);
1132
+ this.sendWindowWaiters.push({ resolve: resolveWrap, reject: rejectWrap });
1021
1133
  });
1022
1134
  }
1023
1135
  // 处理单个帧
@@ -1025,7 +1137,7 @@ class HTTP2Parser {
1025
1137
  switch (frameHeader.type) {
1026
1138
  case FRAME_TYPES.SETTINGS:
1027
1139
  if ((frameHeader.flags & FRAME_FLAGS.ACK) === FRAME_FLAGS.ACK) {
1028
- this.settingsAckReceived = true;
1140
+ this._notifySettingsAck();
1029
1141
  }
1030
1142
  else {
1031
1143
  //接收到Setting请求,进行解析
@@ -1035,10 +1147,12 @@ class HTTP2Parser {
1035
1147
  for (let i = 0; i < settingsPayload.length; i += 6) {
1036
1148
  // 正确解析:2字节ID + 4字节值
1037
1149
  const id = (settingsPayload[i] << 8) | settingsPayload[i + 1];
1038
- const value = (settingsPayload[i + 2] << 24) |
1150
+ // >>> 0 将结果转为无符号 32 位整数,防止高位为 1 时(如 0xffffffff)
1151
+ // 被 JS 按有符号解读为负数,导致 maxConcurrentStreams 等字段为负值
1152
+ const value = ((settingsPayload[i + 2] << 24) |
1039
1153
  (settingsPayload[i + 3] << 16) |
1040
1154
  (settingsPayload[i + 4] << 8) |
1041
- settingsPayload[i + 5];
1155
+ settingsPayload[i + 5]) >>> 0;
1042
1156
  if (id === 4) {
1043
1157
  // SETTINGS_INITIAL_WINDOW_SIZE
1044
1158
  this.defaultStreamWindowSize = value; // 我方接收窗口(入站)
@@ -1075,47 +1189,47 @@ class HTTP2Parser {
1075
1189
  if (this.onSettings) {
1076
1190
  this.onSettings(frameHeader);
1077
1191
  }
1078
- // 标记已收到对端 SETTINGS
1079
- this.peerSettingsReceived = true;
1080
- // 唤醒等待窗口(以防部分实现通过 SETTINGS 改变有效窗口)
1081
- const waiters = this.sendWindowWaiters.splice(0);
1082
- waiters.forEach(fn => { try {
1083
- fn();
1084
- }
1085
- catch (e) {
1086
- console.debug('waiter error', e);
1087
- } });
1192
+ // 标记已收到对端 SETTINGS 并唤醒等待者
1193
+ this._notifyPeerSettings();
1194
+ // 唤醒发送窗口等待者(以防部分实现通过 SETTINGS 改变有效窗口)
1195
+ this._wakeWindowWaiters();
1088
1196
  }
1089
1197
  break;
1090
- case FRAME_TYPES.DATA:
1198
+ case FRAME_TYPES.DATA: {
1091
1199
  // 处理数据帧
1092
1200
  if (this.onData) {
1093
1201
  this.onData(frameData.slice(9), frameHeader); // 跳过帧头
1094
1202
  }
1095
1203
  // 更新流窗口和连接窗口
1096
- try {
1097
- // 更新流级别的窗口
1098
- if (frameHeader.streamId !== 0) {
1099
- const streamWindowUpdate = Http2Frame.createWindowUpdateFrame(frameHeader.streamId, frameHeader.length ?? 0);
1100
- this.writer.write(streamWindowUpdate);
1204
+ // 仅在帧有实际数据时才发送 WINDOW_UPDATE:
1205
+ // RFC 7540 §6.9.1 明确禁止 increment=0 的 WINDOW_UPDATE,
1206
+ // 服务端必须以 PROTOCOL_ERROR 响应,会导致连接被强制关闭。
1207
+ // DATA 帧(如纯 END_STREAM 帧)length=0,不需要归还窗口。
1208
+ const dataLength = frameHeader.length ?? 0;
1209
+ if (dataLength > 0) {
1210
+ try {
1211
+ // 更新流级别的窗口
1212
+ if (frameHeader.streamId !== 0) {
1213
+ const streamWindowUpdate = Http2Frame.createWindowUpdateFrame(frameHeader.streamId, dataLength);
1214
+ this.writer.write(streamWindowUpdate);
1215
+ }
1216
+ // 更新连接级别的窗口
1217
+ const connWindowUpdate = Http2Frame.createWindowUpdateFrame(0, dataLength);
1218
+ this.writer.write(connWindowUpdate);
1219
+ }
1220
+ catch (err) {
1221
+ console.error("[HTTP2] Error sending window update:", err);
1101
1222
  }
1102
- // 更新连接级别的窗口
1103
- const connWindowUpdate = Http2Frame.createWindowUpdateFrame(0, frameHeader.length ?? 0);
1104
- this.writer.write(connWindowUpdate);
1105
- }
1106
- catch (err) {
1107
- console.error("[HTTP2] Error sending window update:", err);
1108
1223
  }
1109
1224
  //判断是否是最后一个帧
1110
1225
  if ((frameHeader.flags & FRAME_FLAGS.END_STREAM) ===
1111
1226
  FRAME_FLAGS.END_STREAM) {
1112
- this.endFlag = true;
1113
- if (this.onEnd) {
1114
- this.onEnd();
1115
- }
1227
+ this.onEnd?.();
1228
+ this._notifyEndOfStream();
1116
1229
  return;
1117
1230
  }
1118
1231
  break;
1232
+ }
1119
1233
  case FRAME_TYPES.HEADERS:
1120
1234
  // 处理头部帧
1121
1235
  if (this.onHeaders) {
@@ -1124,35 +1238,26 @@ class HTTP2Parser {
1124
1238
  //判断是否是最后一个帧
1125
1239
  if ((frameHeader.flags & FRAME_FLAGS.END_STREAM) ===
1126
1240
  FRAME_FLAGS.END_STREAM) {
1127
- this.endFlag = true;
1128
- if (this.onEnd) {
1129
- this.onEnd();
1130
- }
1241
+ this.onEnd?.();
1242
+ this._notifyEndOfStream();
1131
1243
  return;
1132
1244
  }
1133
1245
  break;
1134
1246
  case FRAME_TYPES.WINDOW_UPDATE:
1135
- // 处理窗口更新帧
1136
- this.handleWindowUpdateFrame(frameHeader, frameData);
1137
- // 更新发送窗口(对端接收窗口)
1247
+ // 处理窗口更新帧(同时更新接收侧诊断计数器和发送侧流控窗口,只解析一次)
1138
1248
  try {
1139
- const inc = this.parseWindowUpdateFrame(frameData, frameHeader).windowSizeIncrement;
1249
+ const result = this.handleWindowUpdateFrame(frameHeader, frameData);
1250
+ // 更新发送方向窗口(对端的接收窗口)
1140
1251
  if (frameHeader.streamId === 0) {
1141
- this.sendConnWindow += inc;
1252
+ this.sendConnWindow += result.windowSizeIncrement;
1142
1253
  }
1143
1254
  else {
1144
1255
  const cur = this.sendStreamWindows.get(frameHeader.streamId) ?? this.peerInitialStreamWindow;
1145
- this.sendStreamWindows.set(frameHeader.streamId, cur + inc);
1256
+ this.sendStreamWindows.set(frameHeader.streamId, cur + result.windowSizeIncrement);
1146
1257
  }
1147
- const waiters = this.sendWindowWaiters.splice(0);
1148
- waiters.forEach(fn => { try {
1149
- fn();
1150
- }
1151
- catch (e) {
1152
- console.debug('waiter error', e);
1153
- } });
1258
+ this._wakeWindowWaiters();
1154
1259
  }
1155
- catch { /* ignore WINDOW_UPDATE parse errors */ }
1260
+ catch { /* ignore WINDOW_UPDATE parse errors (e.g. increment=0 is RFC PROTOCOL_ERROR) */ }
1156
1261
  break;
1157
1262
  case FRAME_TYPES.PING:
1158
1263
  // 处理PING帧
@@ -1181,13 +1286,13 @@ class HTTP2Parser {
1181
1286
  catch (err) {
1182
1287
  console.error('Error during GOAWAY callback:', err);
1183
1288
  }
1184
- this.endFlag = true;
1185
1289
  try {
1186
1290
  this.onEnd?.();
1187
1291
  }
1188
1292
  catch (err) {
1189
1293
  console.error('Error during GOAWAY onEnd callback:', err);
1190
1294
  }
1295
+ this._notifyEndOfStream();
1191
1296
  break;
1192
1297
  }
1193
1298
  // case FRAME_TYPES.PUSH_PROMISE:
@@ -1195,10 +1300,8 @@ class HTTP2Parser {
1195
1300
  // this.handlePushPromiseFrame(frameHeader, frameData);
1196
1301
  // break;
1197
1302
  case FRAME_TYPES.RST_STREAM:
1198
- this.endFlag = true;
1199
- if (this.onEnd) {
1200
- this.onEnd();
1201
- }
1303
+ this.onEnd?.();
1304
+ this._notifyEndOfStream();
1202
1305
  break;
1203
1306
  default:
1204
1307
  console.debug("Unknown frame type:", frameHeader.type);
@@ -1208,7 +1311,8 @@ class HTTP2Parser {
1208
1311
  const length = (buffer[0] << 16) | (buffer[1] << 8) | buffer[2];
1209
1312
  const type = buffer[3];
1210
1313
  const flags = buffer[4];
1211
- const streamId = (buffer[5] << 24) | (buffer[6] << 16) | (buffer[7] << 8) | buffer[8];
1314
+ // RFC 7540 §4.1: most significant bit is reserved and MUST be ignored on receipt
1315
+ const streamId = ((buffer[5] << 24) | (buffer[6] << 16) | (buffer[7] << 8) | buffer[8]) & 0x7fffffff;
1212
1316
  return {
1213
1317
  length,
1214
1318
  type,
@@ -1237,52 +1341,40 @@ class HTTP2Parser {
1237
1341
  throw error;
1238
1342
  }
1239
1343
  }
1240
- //等待流结束
1344
+ // 等待流结束 — 事件驱动,onEnd 触发时直接唤醒,无 setInterval 轮询
1241
1345
  waitForEndOfStream(waitTime) {
1242
1346
  return new Promise((resolve, reject) => {
1243
- // If the stream has already ended, resolve immediately
1244
1347
  if (this.endFlag) {
1245
1348
  resolve();
1246
1349
  return;
1247
1350
  }
1248
- // 如果是0 ,则不设置超时
1249
- let timeout = null;
1250
- if (waitTime > 0) {
1251
- timeout = setTimeout(() => {
1252
- clearInterval(interval);
1351
+ const waiter = { resolve, reject };
1352
+ this.endOfStreamWaiters.push(waiter);
1353
+ const timeout = waitTime > 0
1354
+ ? setTimeout(() => {
1355
+ const idx = this.endOfStreamWaiters.indexOf(waiter);
1356
+ if (idx >= 0)
1357
+ this.endOfStreamWaiters.splice(idx, 1);
1253
1358
  reject(new Error("End of stream timeout"));
1254
- }, waitTime);
1255
- }
1256
- // Check interval for real-time endFlag monitoring
1257
- const checkInterval = 100; // Check every 100 milliseconds
1258
- // Set an interval to check the endFlag regularly
1259
- const interval = setInterval(() => {
1260
- if (this.endFlag) {
1261
- if (timeout !== null) {
1262
- clearTimeout(timeout);
1263
- }
1264
- clearInterval(interval);
1265
- resolve();
1266
- }
1267
- }, checkInterval);
1268
- // If the onEnd is triggered externally, it should now be marked manually
1269
- const originalOnEnd = this.onEnd;
1270
- this.onEnd = () => {
1271
- if (!this.endFlag) {
1272
- // The external trigger may set endFlag; if not, handle here
1273
- this.endFlag = true;
1274
- }
1275
- if (timeout !== null) {
1276
- clearTimeout(timeout);
1277
- }
1278
- clearInterval(interval);
1279
- resolve();
1280
- if (originalOnEnd) {
1281
- originalOnEnd(); // Call the original onEnd function if set
1282
- }
1283
- };
1359
+ }, waitTime)
1360
+ : null;
1361
+ waiter.resolve = () => { if (timeout)
1362
+ clearTimeout(timeout); resolve(); };
1363
+ waiter.reject = (e) => { if (timeout)
1364
+ clearTimeout(timeout); reject(e); };
1284
1365
  });
1285
1366
  }
1367
+ /** 内部调用:流结束时唤醒所有 waitForEndOfStream 等待者 */
1368
+ _notifyEndOfStream() {
1369
+ this.endFlag = true;
1370
+ const ws = this.endOfStreamWaiters.splice(0);
1371
+ for (const w of ws) {
1372
+ try {
1373
+ w.resolve();
1374
+ }
1375
+ catch { /* ignore */ }
1376
+ }
1377
+ }
1286
1378
  // 解析 WINDOW_UPDATE 帧
1287
1379
  parseWindowUpdateFrame(frameBuffer, frameHeader) {
1288
1380
  // WINDOW_UPDATE帧的payload固定为4字节
@@ -1341,6 +1433,8 @@ class StreamWriter {
1341
1433
  this.lastBytesDrainedSeen = 0;
1342
1434
  this.lastBpWarnAt = 0;
1343
1435
  this.isHandlingError = false; // 防止重复错误处理
1436
+ /** drain 事件驱动等待者,替代 flush() 中的 setInterval 轮询 */
1437
+ this.drainWaiters = [];
1344
1438
  // 事件系统
1345
1439
  this.listeners = new Map();
1346
1440
  // 验证 stream 参数
@@ -1442,8 +1536,9 @@ class StreamWriter {
1442
1536
  // 使用 stream.send() 发送数据,返回 false 表示需要等待 drain
1443
1537
  const canContinue = this.stream.send(chunk);
1444
1538
  if (!canContinue) {
1445
- // 等待 drain 事件
1446
- await this.stream.onDrain();
1539
+ // 传入 abort signal,当流被 abort 时 onDrain() 会立即 reject,
1540
+ // 避免在 abort 路径下永久挂住
1541
+ await this.stream.onDrain({ signal: this.abortController.signal });
1447
1542
  }
1448
1543
  }
1449
1544
  catch (err) {
@@ -1459,6 +1554,9 @@ class StreamWriter {
1459
1554
  throw err;
1460
1555
  }
1461
1556
  }
1557
+ // pipeline 正常结束(stream 关闭或 pushable 耗尽)—— 确保资源清理
1558
+ // 若已通过 abort() 触发则 cleanup() 内部幂等处理
1559
+ this.cleanup();
1462
1560
  }
1463
1561
  createTransform() {
1464
1562
  // eslint-disable-next-line @typescript-eslint/no-this-alias
@@ -1480,6 +1578,16 @@ class StreamWriter {
1480
1578
  self.lastDrainEventAt = now;
1481
1579
  self.dispatchEvent(new CustomEvent('drain', { detail: { drained: self.bytesDrained, queueSize: self.queueSize } }));
1482
1580
  }
1581
+ // 唤醒所有在等 flush() 或背压解除 的 drainWaiters(队列降低时就可唤醒)
1582
+ if (self.drainWaiters.length > 0) {
1583
+ const ws = self.drainWaiters.splice(0);
1584
+ for (const fn of ws) {
1585
+ try {
1586
+ fn();
1587
+ }
1588
+ catch { /* ignore */ }
1589
+ }
1590
+ }
1483
1591
  // 记录本次已消耗字节,用于看门狗判断是否前进
1484
1592
  self.lastBytesDrainedSeen = self.bytesDrained;
1485
1593
  }
@@ -1491,10 +1599,11 @@ class StreamWriter {
1491
1599
  };
1492
1600
  }
1493
1601
  // 简单的卡顿看门狗:当队列长期高位且 bytesDrained 无进展时发出 stalled 事件
1602
+ // 使用递归 setTimeout 而非 setInterval,避免标签页后台恢复时回调堆积导致 Violation
1494
1603
  startWatchdog(intervalMs = 500, stallMs = 1500) {
1495
1604
  if (this.watchdogTimer)
1496
1605
  return;
1497
- this.watchdogTimer = setInterval(() => {
1606
+ const tick = () => {
1498
1607
  if (this.abortController.signal.aborted)
1499
1608
  return;
1500
1609
  const baseThreshold = this.options.bufferSize * 0.7;
@@ -1505,7 +1614,13 @@ class StreamWriter {
1505
1614
  if (!this.stallStartAt)
1506
1615
  this.stallStartAt = now;
1507
1616
  if (now - this.stallStartAt >= stallMs) {
1508
- this.dispatchEvent(new CustomEvent('stalled', { detail: { queueSize: q, drained: this.bytesDrained, sinceMs: now - this.stallStartAt } }));
1617
+ // 异步触发事件,让当前 tick 立即返回,避免同步事件处理器阻塞主线程
1618
+ const detail = { queueSize: q, drained: this.bytesDrained, sinceMs: now - this.stallStartAt };
1619
+ setTimeout(() => {
1620
+ if (!this.abortController.signal.aborted) {
1621
+ this.dispatchEvent(new CustomEvent('stalled', { detail }));
1622
+ }
1623
+ }, 0);
1509
1624
  // 避免持续触发,推进起点
1510
1625
  this.stallStartAt = now;
1511
1626
  }
@@ -1520,7 +1635,10 @@ class StreamWriter {
1520
1635
  // 队列回落,重置
1521
1636
  this.stallStartAt = 0;
1522
1637
  }
1523
- }, intervalMs);
1638
+ // 本次 tick 完成后再安排下一次,不会因主线程繁忙而堆积
1639
+ this.watchdogTimer = setTimeout(tick, intervalMs);
1640
+ };
1641
+ this.watchdogTimer = setTimeout(tick, intervalMs);
1524
1642
  }
1525
1643
  async write(data) {
1526
1644
  // 静默处理 aborted 状态,避免在正常的流关闭场景下抛出错误
@@ -1557,10 +1675,11 @@ class StreamWriter {
1557
1675
  return data;
1558
1676
  }
1559
1677
  async writeChunks(buffer) {
1560
- for (let offset = 0; offset < buffer.byteLength; offset += this.options.chunkSize) {
1561
- const end = Math.min(offset + this.options.chunkSize, buffer.byteLength);
1562
- const chunk = new Uint8Array(end - offset);
1563
- chunk.set(new Uint8Array(buffer.slice(offset, end)));
1678
+ const src = new Uint8Array(buffer);
1679
+ for (let offset = 0; offset < src.byteLength; offset += this.options.chunkSize) {
1680
+ const end = Math.min(offset + this.options.chunkSize, src.byteLength);
1681
+ // subarray 创建视图,不拷贝内存。pushable.push 不修改内容,安全。
1682
+ const chunk = src.subarray(offset, end);
1564
1683
  await this.retryableWrite(chunk);
1565
1684
  this.updateProgress(chunk.byteLength);
1566
1685
  }
@@ -1581,18 +1700,12 @@ class StreamWriter {
1581
1700
  if (this.abortController.signal.aborted) {
1582
1701
  throw new Error('Stream aborted during backpressure monitoring');
1583
1702
  }
1584
- await new Promise((resolve, reject) => {
1585
- try {
1586
- this.p.push(chunk);
1587
- }
1588
- catch (err) {
1589
- reject(err);
1590
- }
1591
- resolve();
1592
- });
1703
+ // push 是同步操作,直接调用即可
1704
+ this.p.push(chunk);
1593
1705
  }
1594
1706
  catch (err) {
1595
- if (attempt < this.options.retries) {
1707
+ // aborted 时不重试,立即抛出
1708
+ if (!this.abortController.signal.aborted && attempt < this.options.retries) {
1596
1709
  const delay = this.calculateRetryDelay(attempt);
1597
1710
  await new Promise(r => setTimeout(r, delay));
1598
1711
  return this.retryableWrite(chunk, attempt + 1);
@@ -1601,58 +1714,45 @@ class StreamWriter {
1601
1714
  }
1602
1715
  }
1603
1716
  async monitorBackpressure() {
1604
- const currentSize = this.queueSize;
1605
- const baseThreshold = this.options.bufferSize * 0.7; // 降低基础阈值,更早检测
1606
- const criticalThreshold = this.options.bufferSize * 0.9; // 临界阈值
1607
- // 快速路径:无背压时直接返回
1608
- if (currentSize < baseThreshold) {
1717
+ const baseThreshold = this.options.bufferSize * 0.7;
1718
+ const criticalThreshold = this.options.bufferSize * 0.9;
1719
+ // 快速路径
1720
+ if (this.queueSize < baseThreshold) {
1609
1721
  if (this.isBackpressure) {
1610
1722
  this.isBackpressure = false;
1611
- this.dispatchBackpressureEvent({
1612
- currentSize,
1613
- averageSize: this.getAverageQueueSize(),
1614
- threshold: baseThreshold,
1615
- waitingTime: 0
1616
- });
1723
+ this.dispatchBackpressureEvent({ currentSize: this.queueSize, averageSize: this.getAverageQueueSize(), threshold: baseThreshold, waitingTime: 0 });
1617
1724
  }
1618
1725
  return;
1619
1726
  }
1620
- // 进入背压状态
1621
1727
  if (!this.isBackpressure) {
1622
1728
  this.isBackpressure = true;
1623
- this.dispatchBackpressureEvent({
1624
- currentSize,
1625
- averageSize: this.getAverageQueueSize(),
1626
- threshold: baseThreshold,
1627
- waitingTime: 0
1628
- });
1629
- }
1630
- // 智能等待策略
1631
- const pressure = currentSize / this.options.bufferSize;
1632
- let waitTime;
1633
- if (currentSize >= criticalThreshold) {
1634
- // 临界状态:长时间等待
1635
- waitTime = 50 + Math.min(200, pressure * 100);
1636
- }
1637
- else {
1638
- // 轻度背压:短时间等待
1639
- waitTime = Math.min(20, pressure * 30);
1729
+ this.dispatchBackpressureEvent({ currentSize: this.queueSize, averageSize: this.getAverageQueueSize(), threshold: baseThreshold, waitingTime: 0 });
1640
1730
  }
1641
- // 使用指数退避,但最多等待3
1642
- let retryCount = 0;
1643
- const maxRetries = 3;
1644
- while (this.queueSize >= baseThreshold && retryCount < maxRetries) {
1731
+ // 事件驱动等待:每轮等到 drain 触发或超时,最多 3
1732
+ const maxRounds = 3;
1733
+ for (let i = 0; i < maxRounds; i++) {
1645
1734
  if (this.abortController.signal.aborted)
1646
1735
  break;
1647
- await new Promise(r => setTimeout(r, waitTime));
1648
- retryCount++;
1649
- // 动态调整等待时间
1650
- waitTime = Math.min(waitTime * 1.5, 100);
1736
+ if (this.queueSize < baseThreshold)
1737
+ break;
1738
+ const isCritical = this.queueSize >= criticalThreshold;
1739
+ const waitMs = isCritical ? 100 : 30;
1740
+ await new Promise(resolve => {
1741
+ let done = false;
1742
+ const timer = setTimeout(() => { if (!done) {
1743
+ done = true;
1744
+ resolve();
1745
+ } }, waitMs);
1746
+ this.drainWaiters.push(() => { if (!done) {
1747
+ done = true;
1748
+ clearTimeout(timer);
1749
+ resolve();
1750
+ } });
1751
+ });
1651
1752
  }
1652
- // 如果仍然背压但达到最大重试次数,记录警告但继续执行
1653
1753
  if (this.queueSize >= baseThreshold) {
1654
1754
  const now = Date.now();
1655
- if (now - this.lastBpWarnAt > 1000) { // 节流警告
1755
+ if (now - this.lastBpWarnAt > 1000) {
1656
1756
  this.lastBpWarnAt = now;
1657
1757
  console.warn(`Stream writer: High backpressure detected (${this.queueSize} bytes), continuing anyway`);
1658
1758
  }
@@ -1744,11 +1844,21 @@ class StreamWriter {
1744
1844
  if (!this.abortController.signal.aborted) {
1745
1845
  this.abortController.abort();
1746
1846
  }
1747
- // 立即拒绝所有待处理的写入任务,避免它们继续执行
1847
+ // 执行所有待处理的写入任务:它们会检查 signal.aborted 并立即 resolve,
1848
+ // 不执行的话调用方的 Promise 会永远挂住
1748
1849
  const pendingTasks = this.writeQueue.splice(0);
1749
- pendingTasks.forEach(() => {
1750
- // 这些任务的 Promise 会在执行时因为检查到 aborted 而被拒绝
1751
- });
1850
+ for (const task of pendingTasks) {
1851
+ task().catch(() => { });
1852
+ }
1853
+ // 唤醒所有 drainWaiters(flush / monitorBackpressure 中的等待者),
1854
+ // 让它们检查 signal.aborted 并立即 resolve,不必等到各自的超时
1855
+ const ws = this.drainWaiters.splice(0);
1856
+ for (const fn of ws) {
1857
+ try {
1858
+ fn();
1859
+ }
1860
+ catch { /* ignore */ }
1861
+ }
1752
1862
  try {
1753
1863
  this.p.end();
1754
1864
  }
@@ -1756,29 +1866,48 @@ class StreamWriter {
1756
1866
  // Ignore errors when ending pushable
1757
1867
  }
1758
1868
  if (this.watchdogTimer) {
1759
- clearInterval(this.watchdogTimer);
1869
+ clearTimeout(this.watchdogTimer);
1760
1870
  this.watchdogTimer = undefined;
1761
1871
  }
1762
1872
  }
1763
1873
  // 等待内部队列被下游完全消费(用于在结束前确保尽量发送完数据)
1764
1874
  // 默认超时 10s,避免无限等待
1765
1875
  async flush(timeoutMs = 10000) {
1766
- const start = Date.now();
1767
1876
  // 快速路径
1768
1877
  if (this.queueSize <= 0 && !this.isProcessingQueue && this.writeQueue.length === 0)
1769
1878
  return;
1770
- // 轮询等待队列清空
1771
- while (true) {
1772
- if (this.abortController.signal.aborted)
1773
- return;
1774
- if (this.queueSize <= 0 && !this.isProcessingQueue && this.writeQueue.length === 0)
1775
- return;
1776
- if (Date.now() - start > timeoutMs) {
1777
- console.warn(`Stream writer: flush timeout with ${this.queueSize} bytes still queued`);
1879
+ if (this.abortController.signal.aborted)
1880
+ return;
1881
+ await new Promise((resolve) => {
1882
+ // 已经清空
1883
+ if (this.queueSize <= 0 && !this.isProcessingQueue && this.writeQueue.length === 0) {
1884
+ resolve();
1778
1885
  return;
1779
1886
  }
1780
- await new Promise(r => setTimeout(r, 10));
1781
- }
1887
+ let done = false;
1888
+ const timer = setTimeout(() => {
1889
+ if (!done) {
1890
+ done = true;
1891
+ console.warn(`Stream writer: flush timeout with ${this.queueSize} bytes still queued`);
1892
+ resolve();
1893
+ }
1894
+ }, timeoutMs);
1895
+ // 由 createTransform 在每个 chunk 被下游消耗后唤醒
1896
+ const check = () => {
1897
+ if (this.abortController.signal.aborted || (this.queueSize <= 0 && !this.isProcessingQueue && this.writeQueue.length === 0)) {
1898
+ if (!done) {
1899
+ done = true;
1900
+ clearTimeout(timer);
1901
+ resolve();
1902
+ }
1903
+ }
1904
+ else {
1905
+ // 下次 drain 时再检查
1906
+ this.drainWaiters.push(check);
1907
+ }
1908
+ };
1909
+ this.drainWaiters.push(check);
1910
+ });
1782
1911
  }
1783
1912
  addEventListener(type, callback) {
1784
1913
  const handlers = this.listeners.get(type) || [];
@@ -2037,18 +2166,41 @@ class Libp2pGrpcClient {
2037
2166
  setToken(token) {
2038
2167
  this.token = token;
2039
2168
  }
2169
+ /** 从 peerAddr 提取 HTTP/2 :authority 字段(host:port 格式) */
2170
+ getAuthority() {
2171
+ try {
2172
+ const addr = this.peerAddr.toString();
2173
+ const ip4 = addr.match(/\/ip4\/(\d[\d.]+)\/tcp\/(\d+)/);
2174
+ if (ip4)
2175
+ return `${ip4[1]}:${ip4[2]}`;
2176
+ const ip6 = addr.match(/\/ip6\/([^/]+)\/tcp\/(\d+)/);
2177
+ if (ip6)
2178
+ return `[${ip6[1]}]:${ip6[2]}`;
2179
+ const dns = addr.match(/\/dns(?:4|6)?\/([.\w-]+)\/tcp\/(\d+)/);
2180
+ if (dns)
2181
+ return `${dns[1]}:${dns[2]}`;
2182
+ }
2183
+ catch { /* ignore */ }
2184
+ return 'localhost';
2185
+ }
2040
2186
  async unaryCall(method, requestData, timeout = 30000) {
2041
2187
  let stream = null;
2042
2188
  let responseData = null;
2043
2189
  let responseBuffer = []; // 添加缓冲区来累积数据
2044
2190
  let responseDataExpectedLength = -1; // 当前响应的期望长度
2191
+ /** 跨 DATA 帧的部分 gRPC 消息头缓冲(当一帧的 payload < 5 字节时积累) */
2192
+ let headerPartialBuffer = [];
2045
2193
  const hpack = new HPACK();
2046
2194
  let exitFlag = false;
2047
2195
  let errMsg = "";
2048
2196
  let isResponseComplete = false; // 添加标志来标识响应是否完成
2197
+ /** 事件驱动:响应完成时的唤醒函数 */
2198
+ let notifyResponseComplete = null;
2049
2199
  let connection = null;
2050
2200
  let state = null;
2051
2201
  let streamSlotAcquired = false;
2202
+ // 提升 writer 作用域到 finally 可访问,确保错误路径下也能调用 abort() 清理资源
2203
+ let writerRef = null;
2052
2204
  try {
2053
2205
  // const stream = await this.node.dialProtocol(this.peerAddr, this.protocol)
2054
2206
  connection = await this.acquireConnection(false);
@@ -2072,6 +2224,7 @@ class Libp2pGrpcClient {
2072
2224
  const writer = new StreamWriter(stream, {
2073
2225
  bufferSize: 16 * 1024 * 1024,
2074
2226
  });
2227
+ writerRef = writer;
2075
2228
  try {
2076
2229
  writer.addEventListener("backpressure", (e) => {
2077
2230
  const d = e.detail || {};
@@ -2102,6 +2255,7 @@ class Libp2pGrpcClient {
2102
2255
  }
2103
2256
  exitFlag = true;
2104
2257
  errMsg = `GOAWAY received: code=${info.errorCode}`;
2258
+ notifyResponseComplete?.(); // 唤醒等待中的 Promise
2105
2259
  try {
2106
2260
  connection?.close();
2107
2261
  }
@@ -2120,41 +2274,59 @@ class Libp2pGrpcClient {
2120
2274
  parser.registerOutboundStream(streamId);
2121
2275
  responseDataExpectedLength = -1; // 重置期望长度
2122
2276
  responseBuffer = []; // 重置缓冲区
2277
+ headerPartialBuffer = []; // 重置跨帧头部缓冲
2123
2278
  parser.onData = (payload, frameHeader) => {
2124
2279
  //接收数据
2125
2280
  if (responseDataExpectedLength === -1) {
2126
2281
  //grpc消息头部未读取
2282
+ // 如果有跨帧积累的部分头字节,先与本帧 payload 合并
2283
+ let effectivePayload = payload;
2284
+ if (headerPartialBuffer.length > 0) {
2285
+ headerPartialBuffer.push(payload);
2286
+ const totalLen = headerPartialBuffer.reduce((s, c) => s + c.length, 0);
2287
+ effectivePayload = new Uint8Array(totalLen);
2288
+ let off = 0;
2289
+ for (const c of headerPartialBuffer) {
2290
+ effectivePayload.set(c, off);
2291
+ off += c.length;
2292
+ }
2293
+ headerPartialBuffer = [];
2294
+ }
2127
2295
  //提取gRPC消息头部
2128
- if (payload.length < 5) {
2296
+ if (effectivePayload.length < 5) {
2297
+ // 头部字节不足 5,先缓存,等待后续帧补全
2298
+ headerPartialBuffer.push(effectivePayload);
2129
2299
  return;
2130
2300
  }
2131
- const lengthBytes = payload.slice(1, 5); // 消息长度的4字节
2132
- responseDataExpectedLength = new DataView(lengthBytes.buffer, lengthBytes.byteOffset).getUint32(0, false); // big-endian
2133
- if (responseDataExpectedLength < 0) {
2134
- throw new Error("Invalid gRPC message length");
2135
- }
2136
- if (responseDataExpectedLength + 5 > payload.length) {
2301
+ const lengthBytes = effectivePayload.slice(1, 5); // 消息长度的4字节
2302
+ responseDataExpectedLength = new DataView(lengthBytes.buffer, lengthBytes.byteOffset).getUint32(0, false); // big-endian(getUint32 返回无符号整数,结果不会为负)
2303
+ if (responseDataExpectedLength + 5 > effectivePayload.length) {
2137
2304
  // 如果当前 payload 不足以包含完整的 gRPC 消息,缓存数据
2138
- const grpcData = payload.subarray(5);
2305
+ const grpcData = effectivePayload.subarray(5);
2139
2306
  responseBuffer.push(grpcData);
2140
2307
  responseDataExpectedLength -= grpcData.length; // 更新期望长度
2141
2308
  return;
2142
2309
  }
2143
2310
  else {
2144
- // 如果当前 payload 足以包含完整的 gRPC 消息,重置缓冲区
2145
- const grpcData = payload.subarray(5); // 提取完整的 gRPC 消息
2311
+ // payload 已包含完整的 gRPC 消息体,精确截取(避免尾部多余字节污染)
2312
+ const msgLen = responseDataExpectedLength;
2313
+ const grpcData = effectivePayload.slice(5, 5 + msgLen);
2146
2314
  responseBuffer.push(grpcData);
2147
2315
  responseData = grpcData;
2148
2316
  isResponseComplete = true;
2149
- responseDataExpectedLength = -1; // 重置期望长度
2317
+ responseDataExpectedLength = -1;
2318
+ notifyResponseComplete?.();
2150
2319
  }
2151
2320
  }
2152
2321
  else if (responseDataExpectedLength > 0) {
2153
2322
  //grpc消息头部已读取
2154
- responseBuffer.push(payload); // 将数据添加到缓冲区
2155
- responseDataExpectedLength -= payload.length; // 更新期望长度
2323
+ responseDataExpectedLength -= payload.length;
2156
2324
  if (responseDataExpectedLength <= 0) {
2157
- // 如果缓冲区中的数据已经完全处理,重置缓冲区
2325
+ // 超收时截掉多余字节
2326
+ const exactPayload = responseDataExpectedLength < 0
2327
+ ? payload.slice(0, payload.length + responseDataExpectedLength)
2328
+ : payload;
2329
+ responseBuffer.push(exactPayload);
2158
2330
  responseData = new Uint8Array(responseBuffer.reduce((sum, chunk) => sum + chunk.length, 0));
2159
2331
  let offset = 0;
2160
2332
  for (const chunk of responseBuffer) {
@@ -2162,41 +2334,36 @@ class Libp2pGrpcClient {
2162
2334
  offset += chunk.length;
2163
2335
  }
2164
2336
  responseDataExpectedLength = -1;
2165
- isResponseComplete = true; // 设置响应完成标志
2337
+ isResponseComplete = true;
2338
+ notifyResponseComplete?.();
2166
2339
  }
2167
- }
2168
- // 检查是否是流的最后一个帧(END_STREAM 标志)
2169
- if (frameHeader && frameHeader.flags & 0x1 && !isResponseComplete) {
2170
- // END_STREAM flag
2171
- // 合并所有缓冲的数据
2172
- const totalLength = responseBuffer.reduce((sum, chunk) => sum + chunk.length, 0);
2173
- responseData = new Uint8Array(totalLength);
2174
- let offset = 0;
2175
- for (const chunk of responseBuffer) {
2176
- responseData.set(chunk, offset);
2177
- offset += chunk.length;
2340
+ else {
2341
+ responseBuffer.push(payload); // 还不完整,继续累积
2178
2342
  }
2179
- isResponseComplete = true;
2180
2343
  }
2181
- };
2182
- parser.onEnd = () => {
2183
- //接收结束
2184
- if (!isResponseComplete) {
2185
- isResponseComplete = true; // 设置响应完成标志
2186
- if (responseBuffer.length === 0) {
2187
- responseData = new Uint8Array(); // 如果没有数据,返回空数组
2188
- }
2189
- else {
2190
- // 合并所有缓冲的数据
2191
- const totalLength = responseBuffer.reduce((sum, chunk) => sum + chunk.length, 0);
2344
+ // END_STREAM 兜底:数据路径已处理大多数情况;此分支仅在边缘情况下触发
2345
+ if (frameHeader && frameHeader.flags & 0x1 && !isResponseComplete) {
2346
+ if (responseBuffer.length > 0) {
2347
+ const totalLength = responseBuffer.reduce((sum, c) => sum + c.length, 0);
2192
2348
  responseData = new Uint8Array(totalLength);
2193
2349
  let offset = 0;
2194
2350
  for (const chunk of responseBuffer) {
2195
2351
  responseData.set(chunk, offset);
2196
2352
  offset += chunk.length;
2197
2353
  }
2198
- isResponseComplete = true;
2199
2354
  }
2355
+ else {
2356
+ responseData = new Uint8Array(0);
2357
+ }
2358
+ isResponseComplete = true;
2359
+ notifyResponseComplete?.();
2360
+ }
2361
+ };
2362
+ parser.onEnd = () => {
2363
+ // 流结束时若响应未标记完成(空响应 / 纯 trailers),强制标记并唤醒等待者
2364
+ if (!isResponseComplete) {
2365
+ isResponseComplete = true;
2366
+ notifyResponseComplete?.();
2200
2367
  }
2201
2368
  };
2202
2369
  parser.onSettings = () => {
@@ -2212,6 +2379,7 @@ class Libp2pGrpcClient {
2212
2379
  else if (plainHeaders.get("grpc-status") !== undefined) {
2213
2380
  exitFlag = true;
2214
2381
  errMsg = plainHeaders.get("grpc-message") || "gRPC call failed";
2382
+ notifyResponseComplete?.(); // 唤醒等待中的 Promise
2215
2383
  }
2216
2384
  };
2217
2385
  // 启动后台流处理,捕获任何异步错误
@@ -2221,6 +2389,7 @@ class Libp2pGrpcClient {
2221
2389
  if (!errMsg) {
2222
2390
  errMsg = error instanceof Error ? error.message : 'Stream processing failed';
2223
2391
  }
2392
+ notifyResponseComplete?.(); // 流处理异常也需唤醒等待者
2224
2393
  });
2225
2394
  // 握手
2226
2395
  const preface = Http2Frame.createPreface();
@@ -2229,14 +2398,16 @@ class Libp2pGrpcClient {
2229
2398
  const settingFrme = Http2Frame.createSettingsFrame();
2230
2399
  await writer.write(settingFrme);
2231
2400
  // 等待对端 SETTINGS 或 ACK,择一即可,避免偶发握手竞态
2401
+ // 注意:未胜出的 promise 内部有超时定时器,它们最终会 reject。
2402
+ // 必须绑定 .catch(…) 消除错误,否则在 Node.js 新版本中会导致 UnhandledPromiseRejection 崩溃。
2232
2403
  await Promise.race([
2233
- parser.waitForPeerSettings(1000),
2234
- parser.waitForSettingsAck(),
2404
+ parser.waitForPeerSettings(1000).catch(() => { }),
2405
+ parser.waitForSettingsAck().catch(() => { }),
2235
2406
  new Promise((res) => setTimeout(res, 300)),
2236
2407
  ]);
2237
2408
  // 即使未等到,也继续;多数实现会随后发送
2238
2409
  // 创建头部帧
2239
- const headerFrame = Http2Frame.createHeadersFrame(streamId, method, true, this.token);
2410
+ const headerFrame = Http2Frame.createHeadersFrame(streamId, method, true, this.token, this.getAuthority());
2240
2411
  await writer.write(headerFrame);
2241
2412
  // 直接按帧大小分片发送(保持与之前一致的稳定路径)
2242
2413
  const dataFrames = Http2Frame.createDataFrames(streamId, requestData, true);
@@ -2244,22 +2415,21 @@ class Libp2pGrpcClient {
2244
2415
  for (const df of dataFrames) {
2245
2416
  await this.sendFrameWithFlowControl(parser, streamId, df, writer, undefined, frameSendTimeout);
2246
2417
  }
2247
- // 等待responseData 不为空,或超时
2418
+ // 等待 responseData 不为空,或超时(事件驱动,不轮询)
2248
2419
  await new Promise((resolve, reject) => {
2420
+ if (isResponseComplete || exitFlag) {
2421
+ resolve();
2422
+ return;
2423
+ }
2249
2424
  const t = setTimeout(() => {
2425
+ notifyResponseComplete = null;
2250
2426
  reject(new Error("gRPC response timeout"));
2251
2427
  }, timeout);
2252
- const checkResponse = () => {
2253
- if (isResponseComplete || exitFlag) {
2254
- // 使用新的完成标志
2255
- clearTimeout(t);
2256
- resolve(responseData);
2257
- }
2258
- else {
2259
- setTimeout(checkResponse, 50);
2260
- }
2428
+ notifyResponseComplete = () => {
2429
+ clearTimeout(t);
2430
+ notifyResponseComplete = null;
2431
+ resolve();
2261
2432
  };
2262
- checkResponse();
2263
2433
  });
2264
2434
  try {
2265
2435
  await writer.flush(timeout);
@@ -2272,8 +2442,18 @@ class Libp2pGrpcClient {
2272
2442
  throw err;
2273
2443
  }
2274
2444
  finally {
2445
+ // 必须先 abort writer(立即强制停止 pushable + stream),再 close stream。
2446
+ // 若顺序颠倒:stream.close() 会等待服务端半关闭确认,网络异常时永久挂住,
2447
+ // 导致 writer.abort() 永远不执行 → watchdog 定时器 / pushable 泄漏。
2448
+ // writer.abort() 内部幂等,成功路径下 writer.end() 已调用 cleanup(),安全。
2449
+ writerRef?.abort('unaryCall cleanup');
2275
2450
  if (stream) {
2276
- await stream.close();
2451
+ try {
2452
+ await stream.close();
2453
+ }
2454
+ catch {
2455
+ // 流已被 abort,close() 会立即抛出,忽略即可。
2456
+ }
2277
2457
  }
2278
2458
  if (streamSlotAcquired && state) {
2279
2459
  state.activeStreams = Math.max(0, state.activeStreams - 1);
@@ -2308,6 +2488,8 @@ class Libp2pGrpcClient {
2308
2488
  const internalController = new AbortController();
2309
2489
  let timeoutHandle;
2310
2490
  let stream = null;
2491
+ // 保存外部 abort 监听器引用,以便操作结束后移除,防止内存泄漏
2492
+ let contextAbortHandler;
2311
2493
  const profile = options?.transportProfile ?? this.getDefaultTransportProfile(mode);
2312
2494
  const useFlowControl = profile === "flow-control";
2313
2495
  // 取消函数 - 将在最后返回给调用者
@@ -2327,17 +2509,16 @@ class Libp2pGrpcClient {
2327
2509
  };
2328
2510
  // 如果提供了外部信号,监听它
2329
2511
  if (context?.signal) {
2330
- // 如果外部信号已经触发中止,立即返回
2512
+ // 如果外部信号已经触发中止,立即返回——避免启动 IIFE 后在 catch 中再次调用 onErrorCallback
2331
2513
  if (context.signal.aborted) {
2332
2514
  if (onErrorCallback) {
2333
2515
  onErrorCallback(new Error("Operation aborted by context"));
2334
2516
  }
2335
- cancelOperation();
2517
+ return cancelOperation;
2336
2518
  }
2337
- // 监听外部的abort事件
2338
- context.signal.addEventListener("abort", () => {
2339
- cancelOperation();
2340
- });
2519
+ // 监听外部的abort事件(保存引用以便后续移除,防止内存泄漏)
2520
+ contextAbortHandler = () => { cancelOperation(); };
2521
+ context.signal.addEventListener("abort", contextAbortHandler);
2341
2522
  }
2342
2523
  // 超时Promise
2343
2524
  const timeoutPromise = new Promise((_, reject) => {
@@ -2348,13 +2529,45 @@ class Libp2pGrpcClient {
2348
2529
  });
2349
2530
  // 主操作Promise
2350
2531
  const operationPromise = (async () => {
2351
- let messageBuffer = new Uint8Array(0); // 用于累积跨帧的消息数据
2532
+ /**
2533
+ * 统一错误报告:确保 onErrorCallback 只被调用一次,
2534
+ * 并同时中止操作,防止后续再触发 onEndCallback。
2535
+ * 适用于 onGoaway / onHeaders / processStream.catch / onData 等各个错误路径。
2536
+ */
2537
+ let errorCallbackFired = false;
2538
+ const reportError = (err) => {
2539
+ if (errorCallbackFired)
2540
+ return;
2541
+ errorCallbackFired = true;
2542
+ internalController.abort();
2543
+ if (onErrorCallback)
2544
+ onErrorCallback(err);
2545
+ };
2546
+ /** 分段列表缓冲,避免每次 payload 到达时 O(n) 全量拷贝 */
2547
+ let msgChunks = [];
2548
+ let msgTotalLen = 0;
2352
2549
  let expectedMessageLength = -1; // 当前消息的期望长度
2550
+ /** 将分段列表合并为单一 Uint8Array(仅在需要时调用) */
2551
+ const flattenMsgBuffer = () => {
2552
+ if (msgChunks.length === 0)
2553
+ return new Uint8Array(0);
2554
+ if (msgChunks.length === 1)
2555
+ return msgChunks[0];
2556
+ const out = new Uint8Array(msgTotalLen);
2557
+ let off = 0;
2558
+ for (const c of msgChunks) {
2559
+ out.set(c, off);
2560
+ off += c.length;
2561
+ }
2562
+ return out;
2563
+ };
2353
2564
  const hpack = new HPACK();
2354
2565
  let connection = null;
2355
2566
  let connectionKey = null;
2356
2567
  let state = null;
2357
2568
  let streamSlotAcquired = false;
2569
+ // 提升 writer 作用域到 finally 可访问,确保 unary/server-streaming 模式下也能清理资源
2570
+ let writer = null;
2358
2571
  try {
2359
2572
  // 检查是否已经中止
2360
2573
  if (internalController.signal.aborted) {
@@ -2392,7 +2605,7 @@ class Libp2pGrpcClient {
2392
2605
  });
2393
2606
  const streamManager = this.getStreamManagerFor(connection);
2394
2607
  const streamId = await streamManager.getNextAppLevelStreamId();
2395
- const writer = new StreamWriter(stream, {
2608
+ writer = new StreamWriter(stream, {
2396
2609
  bufferSize: 16 * 1024 * 1024,
2397
2610
  });
2398
2611
  try {
@@ -2427,10 +2640,8 @@ class Libp2pGrpcClient {
2427
2640
  if (state) {
2428
2641
  this.rejectStreamWaiters(state, new Error("Connection received GOAWAY"));
2429
2642
  }
2430
- if (onErrorCallback) {
2431
- onErrorCallback(new Error(`GOAWAY received: code=${info.errorCode}`));
2432
- }
2433
- internalController.abort();
2643
+ // reportError 统一完成:标记已报错 + abort + 触发回调(幂等,不会重复触发)
2644
+ reportError(new Error(`GOAWAY received: code=${info.errorCode}`));
2434
2645
  try {
2435
2646
  connection?.close();
2436
2647
  }
@@ -2468,52 +2679,43 @@ class Libp2pGrpcClient {
2468
2679
  };
2469
2680
  // 在各个回调中检查是否已中止
2470
2681
  parser.onData = async (payload) => {
2471
- // 检查是否已中止
2472
- if (internalController.signal.aborted) {
2682
+ if (internalController.signal.aborted)
2473
2683
  return;
2474
- }
2475
2684
  try {
2476
- // 将新数据添加到消息缓冲区
2477
- const newBuffer = new Uint8Array(messageBuffer.length + payload.length);
2478
- newBuffer.set(messageBuffer);
2479
- newBuffer.set(payload, messageBuffer.length);
2480
- messageBuffer = newBuffer;
2685
+ // 追加到分段列表,O(1),不拷贝历史数据
2686
+ msgChunks.push(payload);
2687
+ msgTotalLen += payload.length;
2481
2688
  // 处理缓冲区中的完整消息
2482
- while (messageBuffer.length > 0) {
2483
- // 如果已经中止,停止处理
2484
- if (internalController.signal.aborted) {
2689
+ while (msgTotalLen > 0) {
2690
+ if (internalController.signal.aborted)
2485
2691
  return;
2692
+ // 读取 gRPC 消息头(5字节)
2693
+ if (expectedMessageLength === -1 && msgTotalLen >= 5) {
2694
+ const flat = flattenMsgBuffer();
2695
+ msgChunks = [flat];
2696
+ const lengthBytes = flat.slice(1, 5);
2697
+ expectedMessageLength = new DataView(lengthBytes.buffer, lengthBytes.byteOffset).getUint32(0, false);
2486
2698
  }
2487
- // 如果还没有读取消息长度,且缓冲区有足够数据
2488
- if (expectedMessageLength === -1 && messageBuffer.length >= 5) {
2489
- // 读取 gRPC 消息头:1字节压缩标志 + 4字节长度
2490
- const lengthBytes = messageBuffer.slice(1, 5);
2491
- expectedMessageLength = new DataView(lengthBytes.buffer, lengthBytes.byteOffset).getUint32(0, false); // big-endian
2492
- }
2493
- // 如果知道期望长度且有足够数据
2494
- if (expectedMessageLength !== -1 &&
2495
- messageBuffer.length >= expectedMessageLength + 5) {
2496
- // 提取完整消息(跳过5字节头部)
2497
- const completeMessage = messageBuffer.slice(5, expectedMessageLength + 5);
2498
- // 调用回调处理这个完整消息
2699
+ // 有完整消息
2700
+ if (expectedMessageLength !== -1 && msgTotalLen >= expectedMessageLength + 5) {
2701
+ const flat = flattenMsgBuffer();
2702
+ msgChunks = [flat];
2703
+ const completeMessage = flat.slice(5, expectedMessageLength + 5);
2499
2704
  onDataCallback(completeMessage);
2500
- // 移除已处理的消息,保留剩余数据
2501
- messageBuffer = messageBuffer.slice(expectedMessageLength + 5);
2705
+ // 移除已处理消息,保留剩余
2706
+ const remaining = flat.slice(expectedMessageLength + 5);
2707
+ msgChunks = remaining.length > 0 ? [remaining] : [];
2708
+ msgTotalLen = remaining.length;
2502
2709
  expectedMessageLength = -1;
2503
2710
  }
2504
2711
  else {
2505
- // 没有足够数据构成完整消息,等待更多数据
2506
2712
  break;
2507
2713
  }
2508
2714
  }
2509
2715
  }
2510
2716
  catch (error) {
2511
- if (onErrorCallback) {
2512
- onErrorCallback(error);
2513
- }
2514
- else {
2515
- throw error;
2516
- }
2717
+ // reportError 统一报错并中止,防止 onEndCallback 在数据处理异常后仍被调用
2718
+ reportError(error);
2517
2719
  }
2518
2720
  };
2519
2721
  parser.onSettings = () => {
@@ -2533,20 +2735,16 @@ class Libp2pGrpcClient {
2533
2735
  }
2534
2736
  else if (plainHeaders.get("grpc-status") !== undefined) {
2535
2737
  const errMsg = plainHeaders.get("grpc-message") || "gRPC call failed";
2536
- const err = new Error(errMsg);
2537
- if (onErrorCallback) {
2538
- onErrorCallback(err);
2539
- }
2540
- else {
2541
- throw err;
2542
- }
2738
+ // reportError 统一完成:标记已报错 + abort + 触发回调(幂等,不会重复触发)
2739
+ reportError(new Error(errMsg));
2543
2740
  }
2544
2741
  };
2545
2742
  // 启动后台流处理
2546
2743
  parser.processStream(stream).catch((error) => {
2547
- console.error('Error in processStream:', error);
2548
- if (onErrorCallback) {
2549
- onErrorCallback(error);
2744
+ // abort() 触发的清理错误属于预期行为,不打印错误日志,不重复触发回调
2745
+ if (!internalController.signal.aborted) {
2746
+ console.error('Error in processStream:', error);
2747
+ reportError(error);
2550
2748
  }
2551
2749
  });
2552
2750
  // 检查是否已中止
@@ -2568,10 +2766,12 @@ class Libp2pGrpcClient {
2568
2766
  throw new Error("Operation aborted");
2569
2767
  }
2570
2768
  // 等待对端 SETTINGS 或 ACK,择一即可,避免偶发握手竞态
2769
+ // 注意:未胜出的 promise 内部有超时定时器,它们最终会 reject。
2770
+ // 必须绑定 .catch(…) 消除错误,否则在 Node.js 新版本中会导致 UnhandledPromiseRejection 崩溃。
2571
2771
  {
2572
2772
  await Promise.race([
2573
- parser.waitForPeerSettings(1000),
2574
- parser.waitForSettingsAck(),
2773
+ parser.waitForPeerSettings(1000).catch(() => { }),
2774
+ parser.waitForSettingsAck().catch(() => { }),
2575
2775
  new Promise((res) => setTimeout(res, 300)),
2576
2776
  ]);
2577
2777
  // 即使未等到,也继续;多数实现会随后发送
@@ -2580,12 +2780,8 @@ class Libp2pGrpcClient {
2580
2780
  if (internalController.signal.aborted) {
2581
2781
  throw new Error("Operation aborted");
2582
2782
  }
2583
- // 检查是否已中止
2584
- if (internalController.signal.aborted) {
2585
- throw new Error("Operation aborted");
2586
- }
2587
2783
  // Create header frame
2588
- const headerFrame = Http2Frame.createHeadersFrame(streamId, method, true, this.token);
2784
+ const headerFrame = Http2Frame.createHeadersFrame(streamId, method, true, this.token, this.getAuthority());
2589
2785
  if (mode === "unary" || mode === "server-streaming") {
2590
2786
  await writer.write(headerFrame);
2591
2787
  const dfs = Http2Frame.createDataFrames(streamId, requestData, true);
@@ -2610,7 +2806,18 @@ class Libp2pGrpcClient {
2610
2806
  const batchSize = options?.batchSize || 10;
2611
2807
  // 动态批处理器
2612
2808
  const processingQueue = [];
2809
+ /** 事件驱动:批处理完成后唤醒 waitForQueue 等待者 */
2810
+ const batchDoneWaiters = [];
2613
2811
  let isProcessing = false;
2812
+ const _notifyBatchDone = () => {
2813
+ const ws = batchDoneWaiters.splice(0);
2814
+ for (const fn of ws) {
2815
+ try {
2816
+ fn();
2817
+ }
2818
+ catch { /* ignore */ }
2819
+ }
2820
+ };
2614
2821
  const processNextBatch = async () => {
2615
2822
  if (isProcessing || processingQueue.length === 0)
2616
2823
  return;
@@ -2654,10 +2861,13 @@ class Libp2pGrpcClient {
2654
2861
  finally {
2655
2862
  isProcessing = false;
2656
2863
  // 如果队列中还有数据,继续处理
2657
- if (processingQueue.length > 0 &&
2658
- !internalController.signal.aborted) {
2659
- // 使用 setTimeout 避免阻塞,让新数据有机会加入队列
2660
- setTimeout(() => processNextBatch(), 0);
2864
+ if (processingQueue.length > 0 && !internalController.signal.aborted) {
2865
+ // 直接递归调用(已是 async,自动让出事件循环)
2866
+ processNextBatch().catch((err) => { console.error("Error in processNextBatch:", err); });
2867
+ }
2868
+ else {
2869
+ // 队列清空,唤醒等待者
2870
+ _notifyBatchDone();
2661
2871
  }
2662
2872
  }
2663
2873
  };
@@ -2707,34 +2917,30 @@ class Libp2pGrpcClient {
2707
2917
  });
2708
2918
  throw error;
2709
2919
  }
2710
- // 等待所有剩余的数据处理完成,添加超时保护
2711
- const queueWaitStart = Date.now();
2712
- const maxQueueWaitMs = timeout; // 使用主超时时间
2713
- while (processingQueue.length > 0 || isProcessing) {
2714
- if (internalController.signal.aborted) {
2715
- throw new Error("Operation aborted");
2716
- }
2717
- // 防止无限等待
2718
- if (Date.now() - queueWaitStart > maxQueueWaitMs) {
2719
- // 清理剩余队列
2720
- const remainingQueue = processingQueue.splice(0);
2721
- remainingQueue.forEach((item) => {
2722
- try {
2723
- item.reject(new Error("Queue wait timeout"));
2724
- }
2725
- catch (err) {
2726
- console.warn("Error rejecting timeout promise:", err);
2727
- }
2728
- });
2729
- throw new Error("Queue processing timeout");
2730
- }
2731
- await new Promise((resolve) => setTimeout(resolve, 10));
2732
- }
2920
+ // 等待所有剩余的数据处理完成(事件驱动,无 10ms 轮询)
2921
+ await new Promise((resolve, reject) => {
2922
+ const check = () => {
2923
+ if (internalController.signal.aborted) {
2924
+ reject(new Error("Operation aborted"));
2925
+ return;
2926
+ }
2927
+ if (processingQueue.length === 0 && !isProcessing) {
2928
+ resolve();
2929
+ return;
2930
+ }
2931
+ // processNextBatch 结束时会通知这里
2932
+ batchDoneWaiters.push(check);
2933
+ };
2934
+ check();
2935
+ });
2733
2936
  // 检查是否已中止
2734
2937
  if (internalController.signal.aborted) {
2735
2938
  throw new Error("Operation aborted");
2736
2939
  }
2737
- const finalFrame = Http2Frame.createDataFrame(streamId, new Uint8Array(), true);
2940
+ // 发送纯 HTTP/2 END_STREAM 信号帧(0 字节 payload),而非带 gRPC 消息头的空消息。
2941
+ // createDataFrame 会额外附加 5 字节 gRPC 消息头 [0,0,0,0,0],服务端会将其解析
2942
+ // 为一个长度=0 的额外 gRPC 消息,而不仅仅是流结束信号,可能导致协议混淆。
2943
+ const finalFrame = Http2Frame.createFrame(0x0, 0x01, streamId, new Uint8Array(0));
2738
2944
  await writeFrame(finalFrame);
2739
2945
  // 在结束前尽量冲刷内部队列,避免服务器看到部分数据 + context canceled
2740
2946
  try {
@@ -2747,9 +2953,24 @@ class Libp2pGrpcClient {
2747
2953
  if (internalController.signal.aborted) {
2748
2954
  throw new Error("Operation aborted");
2749
2955
  }
2750
- await parser.waitForEndOfStream(0);
2751
- if (onEndCallback) {
2752
- onEndCallback();
2956
+ // 仅在未中止时等待并回调:
2957
+ // 1. 若已中止(如 onHeaders gRPC 错误),跳过 waitForEndOfStream(0) 避免永久阻塞
2958
+ // (waitForEndOfStream(0) 无超时,需等到 processStream 自然结束,
2959
+ // 而 processStream 结束依赖 stream.close(),但 stream.close() 在 finally 中——形成死锁)
2960
+ // 2. 避免在 onErrorCallback 之后再调用 onEndCallback
2961
+ if (!internalController.signal.aborted) {
2962
+ await parser.waitForEndOfStream(0);
2963
+ // Yield one microtask tick so that processStream.catch (which calls
2964
+ // reportError + internalController.abort()) has a chance to run before
2965
+ // we check abort status. Without this yield, if the stream died
2966
+ // unexpectedly (network error), onEndCallback and onErrorCallback
2967
+ // could both fire because _notifyEndOfStream() is called in
2968
+ // processStream's catch block before the re-throw schedules the
2969
+ // .catch handler as a microtask.
2970
+ await Promise.resolve();
2971
+ if (!internalController.signal.aborted && onEndCallback) {
2972
+ onEndCallback();
2973
+ }
2753
2974
  }
2754
2975
  }
2755
2976
  catch (err) {
@@ -2757,14 +2978,16 @@ class Libp2pGrpcClient {
2757
2978
  if (internalController.signal.aborted &&
2758
2979
  err instanceof Error &&
2759
2980
  err.message === "Operation aborted") {
2760
- if (onErrorCallback) {
2981
+ // onHeaders / onGoaway / processStream 错误已通过 reportError 处理,
2982
+ // 此处仅在回调尚未触发时才报告(外部取消/超时场景)
2983
+ if (!errorCallbackFired && onErrorCallback) {
2761
2984
  onErrorCallback(new Error("Operation cancelled by user"));
2762
2985
  }
2763
2986
  }
2764
- else if (onErrorCallback) {
2987
+ else if (!errorCallbackFired && onErrorCallback) {
2765
2988
  onErrorCallback(err);
2766
2989
  }
2767
- else {
2990
+ else if (!errorCallbackFired) {
2768
2991
  if (err instanceof Error) {
2769
2992
  console.error("asyncCall error:", err.message);
2770
2993
  }
@@ -2775,12 +2998,21 @@ class Libp2pGrpcClient {
2775
2998
  }
2776
2999
  finally {
2777
3000
  clearTimeout(timeoutHandle);
3001
+ // 移除外部 abort 监听器,防止 AbortController 复用时触发迟到的 cancelOperation()
3002
+ if (contextAbortHandler && context?.signal) {
3003
+ context.signal.removeEventListener("abort", contextAbortHandler);
3004
+ }
3005
+ // 必须先 abort writer(立即强制停止 pushable + stream),再 close stream。
3006
+ // 若顺序颠倒:stream.close() 等待服务端半关闭确认,网络异常时永久挂住,
3007
+ // writer.abort() 永远不执行 → watchdog / pushable 泄漏。
3008
+ // abort() 内部幂等,重复调用安全。
3009
+ writer?.abort('Call cleanup');
2778
3010
  if (stream) {
2779
3011
  try {
2780
3012
  await stream.close();
2781
3013
  }
2782
- catch (err) {
2783
- console.error("Error closing stream:", err);
3014
+ catch {
3015
+ // 流已被 abort,close() 会立即抛出,忽略即可。
2784
3016
  }
2785
3017
  }
2786
3018
  // 如果本次强制使用了新连接,结束时尽量关闭它,避免连接泄漏