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