grpc-libp2p-client 0.0.37 → 0.0.39
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/dc-http2/frame.cjs.js +6 -5
- package/dist/dc-http2/frame.cjs.js.map +1 -1
- package/dist/dc-http2/frame.d.ts +1 -1
- package/dist/dc-http2/frame.esm.js +6 -5
- package/dist/dc-http2/frame.esm.js.map +1 -1
- package/dist/dc-http2/hpack.cjs.js +3 -3
- package/dist/dc-http2/hpack.cjs.js.map +1 -1
- package/dist/dc-http2/hpack.esm.js +3 -3
- package/dist/dc-http2/hpack.esm.js.map +1 -1
- package/dist/dc-http2/parser.cjs.js +39 -28
- package/dist/dc-http2/parser.cjs.js.map +1 -1
- package/dist/dc-http2/parser.d.ts +7 -7
- package/dist/dc-http2/parser.esm.js +39 -28
- package/dist/dc-http2/parser.esm.js.map +1 -1
- package/dist/dc-http2/stream.cjs.js +26 -12
- package/dist/dc-http2/stream.cjs.js.map +1 -1
- package/dist/dc-http2/stream.d.ts +1 -1
- package/dist/dc-http2/stream.esm.js +26 -12
- package/dist/dc-http2/stream.esm.js.map +1 -1
- package/dist/grpc.js +245 -269
- package/dist/grpc.js.map +1 -1
- package/dist/grpc.min.js +1 -1
- package/dist/grpc.min.js.map +1 -1
- package/dist/index.cjs.js +103 -103
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +103 -103
- package/dist/index.esm.js.map +1 -1
- package/dist/stats.html +1 -1
- package/package.json +4 -4
- package/src/dc-http2/encoder.ts +3 -2
- package/src/dc-http2/hpack.ts +3 -3
- package/src/dc-http2/parser.ts +43 -38
- package/src/dc-http2/stream.ts +36 -23
- package/src/dc-http2/types.ts +1 -1
- package/src/index.ts +57 -85
package/dist/index.cjs.js
CHANGED
|
@@ -357,7 +357,7 @@ class HPACK {
|
|
|
357
357
|
}
|
|
358
358
|
// Huffman编码实现
|
|
359
359
|
huffmanEncode(bytes) {
|
|
360
|
-
|
|
360
|
+
const result = [];
|
|
361
361
|
let current = 0;
|
|
362
362
|
let bits = 0;
|
|
363
363
|
for (let i = 0; i < bytes.length; i++) {
|
|
@@ -450,7 +450,7 @@ class HPACK {
|
|
|
450
450
|
try {
|
|
451
451
|
result = this.decode(bytes);
|
|
452
452
|
}
|
|
453
|
-
catch
|
|
453
|
+
catch {
|
|
454
454
|
result = '';
|
|
455
455
|
}
|
|
456
456
|
}
|
|
@@ -458,7 +458,7 @@ class HPACK {
|
|
|
458
458
|
try {
|
|
459
459
|
result = new TextDecoder().decode(bytes);
|
|
460
460
|
}
|
|
461
|
-
catch
|
|
461
|
+
catch {
|
|
462
462
|
result = '';
|
|
463
463
|
}
|
|
464
464
|
}
|
|
@@ -504,7 +504,8 @@ class FrameEncoder {
|
|
|
504
504
|
// 编码SETTINGS帧
|
|
505
505
|
static encodeSettingsFrame(frame) {
|
|
506
506
|
// 计算payload总长度:每个设置项占6字节
|
|
507
|
-
const
|
|
507
|
+
const settingsPayload = frame.payload;
|
|
508
|
+
const payloadLength = settingsPayload.length * 6;
|
|
508
509
|
// 分配缓冲区:9字节头部 + payload长度
|
|
509
510
|
const buffer = new Uint8Array(9 + payloadLength);
|
|
510
511
|
// 编码帧头部(前9个字节)
|
|
@@ -515,7 +516,7 @@ class FrameEncoder {
|
|
|
515
516
|
streamId: frame.streamId
|
|
516
517
|
});
|
|
517
518
|
// 编码payload
|
|
518
|
-
_encodeSettingsPayload(buffer,
|
|
519
|
+
_encodeSettingsPayload(buffer, settingsPayload);
|
|
519
520
|
return buffer;
|
|
520
521
|
}
|
|
521
522
|
// 编码SETTINGS ACK帧
|
|
@@ -585,11 +586,11 @@ const defaultSettings = {
|
|
|
585
586
|
[SETTINGS_PARAMETERS.MAX_FRAME_SIZE]: 16 << 10, // 16k
|
|
586
587
|
[SETTINGS_PARAMETERS.MAX_HEADER_LIST_SIZE]: 8192
|
|
587
588
|
};
|
|
588
|
-
const HTTP2_PREFACE = 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n';
|
|
589
|
+
const HTTP2_PREFACE$1 = 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n';
|
|
589
590
|
class Http2Frame {
|
|
590
591
|
// 创建并编码PREFACE帧
|
|
591
592
|
static createPreface() {
|
|
592
|
-
return new TextEncoder().encode(HTTP2_PREFACE);
|
|
593
|
+
return new TextEncoder().encode(HTTP2_PREFACE$1);
|
|
593
594
|
}
|
|
594
595
|
static createPongFrame(payload) {
|
|
595
596
|
return Http2Frame.createFrame(0x6, 0x1, 0, payload);
|
|
@@ -806,6 +807,7 @@ function _createPayload(settings) {
|
|
|
806
807
|
return payload;
|
|
807
808
|
}
|
|
808
809
|
|
|
810
|
+
const HTTP2_PREFACE = new TextEncoder().encode("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
|
|
809
811
|
class HTTP2Parser {
|
|
810
812
|
constructor(writer, options) {
|
|
811
813
|
this.buffer = new Uint8Array(0);
|
|
@@ -853,44 +855,49 @@ class HTTP2Parser {
|
|
|
853
855
|
// 处理单个数据块
|
|
854
856
|
_processChunk(chunk) {
|
|
855
857
|
// chunk 是 Uint8ArrayList 或 Uint8Array
|
|
856
|
-
const newData =
|
|
857
|
-
|
|
858
|
+
const newData = 'subarray' in chunk && typeof chunk.subarray === 'function'
|
|
859
|
+
? chunk.subarray()
|
|
860
|
+
: chunk;
|
|
861
|
+
// 原作者之前的 O(N) 内存拷贝优化被保留,去掉了存在 onEnd 竞态的 setTimeout
|
|
858
862
|
const newBuffer = new Uint8Array(this.buffer.length + newData.length);
|
|
859
863
|
newBuffer.set(this.buffer);
|
|
860
864
|
newBuffer.set(newData, this.buffer.length);
|
|
861
865
|
this.buffer = newBuffer;
|
|
862
866
|
// 持续处理所有完整的帧
|
|
863
|
-
|
|
867
|
+
let readOffset = 0;
|
|
868
|
+
while (this.buffer.length - readOffset >= 9) {
|
|
864
869
|
// 判断是否有HTTP/2前导
|
|
865
|
-
if (this.buffer.length >= 24 && this.isHttp2Preface(this.buffer)) {
|
|
866
|
-
|
|
870
|
+
if (this.buffer.length - readOffset >= 24 && this.isHttp2Preface(this.buffer.subarray(readOffset))) {
|
|
871
|
+
readOffset += 24;
|
|
867
872
|
// 发送SETTINGS帧
|
|
868
873
|
const settingFrame = Http2Frame.createSettingsFrame();
|
|
869
874
|
this.writer.write(settingFrame);
|
|
870
|
-
|
|
875
|
+
continue;
|
|
871
876
|
}
|
|
872
|
-
const frameHeader = this._parseFrameHeader(this.buffer);
|
|
877
|
+
const frameHeader = this._parseFrameHeader(this.buffer.subarray(readOffset));
|
|
873
878
|
const totalFrameLength = 9 + frameHeader.length;
|
|
874
879
|
// 检查是否有完整的帧
|
|
875
|
-
if (this.buffer.length < totalFrameLength) {
|
|
880
|
+
if (this.buffer.length - readOffset < totalFrameLength) {
|
|
876
881
|
break;
|
|
877
882
|
}
|
|
878
883
|
// 获取完整帧数据
|
|
879
|
-
const frameData = this.buffer.
|
|
884
|
+
const frameData = this.buffer.subarray(readOffset, readOffset + totalFrameLength);
|
|
880
885
|
// 处理不同类型的帧
|
|
881
886
|
this._handleFrame(frameHeader, frameData).catch((err) => {
|
|
882
887
|
console.error("Error handling frame:", err);
|
|
883
888
|
});
|
|
884
|
-
//
|
|
885
|
-
|
|
889
|
+
// 移动偏移量
|
|
890
|
+
readOffset += totalFrameLength;
|
|
891
|
+
}
|
|
892
|
+
if (readOffset > 0) {
|
|
893
|
+
this.buffer = this.buffer.slice(readOffset);
|
|
886
894
|
}
|
|
887
895
|
}
|
|
888
896
|
isHttp2Preface(buffer) {
|
|
889
|
-
|
|
890
|
-
if (buffer.length < PREFACE.length)
|
|
897
|
+
if (buffer.length < HTTP2_PREFACE.length)
|
|
891
898
|
return false;
|
|
892
|
-
for (let i = 0; i <
|
|
893
|
-
if (buffer[i] !==
|
|
899
|
+
for (let i = 0; i < HTTP2_PREFACE.length; i++) {
|
|
900
|
+
if (buffer[i] !== HTTP2_PREFACE[i])
|
|
894
901
|
return false;
|
|
895
902
|
}
|
|
896
903
|
return true;
|
|
@@ -1077,7 +1084,9 @@ class HTTP2Parser {
|
|
|
1077
1084
|
waiters.forEach(fn => { try {
|
|
1078
1085
|
fn();
|
|
1079
1086
|
}
|
|
1080
|
-
catch {
|
|
1087
|
+
catch (e) {
|
|
1088
|
+
console.debug('waiter error', e);
|
|
1089
|
+
} });
|
|
1081
1090
|
}
|
|
1082
1091
|
break;
|
|
1083
1092
|
case FRAME_TYPES.DATA:
|
|
@@ -1126,7 +1135,7 @@ class HTTP2Parser {
|
|
|
1126
1135
|
break;
|
|
1127
1136
|
case FRAME_TYPES.WINDOW_UPDATE:
|
|
1128
1137
|
// 处理窗口更新帧
|
|
1129
|
-
this.handleWindowUpdateFrame(frameHeader, frameData
|
|
1138
|
+
this.handleWindowUpdateFrame(frameHeader, frameData);
|
|
1130
1139
|
// 更新发送窗口(对端接收窗口)
|
|
1131
1140
|
try {
|
|
1132
1141
|
const inc = this.parseWindowUpdateFrame(frameData, frameHeader).windowSizeIncrement;
|
|
@@ -1141,9 +1150,11 @@ class HTTP2Parser {
|
|
|
1141
1150
|
waiters.forEach(fn => { try {
|
|
1142
1151
|
fn();
|
|
1143
1152
|
}
|
|
1144
|
-
catch {
|
|
1153
|
+
catch (e) {
|
|
1154
|
+
console.debug('waiter error', e);
|
|
1155
|
+
} });
|
|
1145
1156
|
}
|
|
1146
|
-
catch
|
|
1157
|
+
catch { /* ignore WINDOW_UPDATE parse errors */ }
|
|
1147
1158
|
break;
|
|
1148
1159
|
case FRAME_TYPES.PING:
|
|
1149
1160
|
// 处理PING帧
|
|
@@ -1165,7 +1176,7 @@ class HTTP2Parser {
|
|
|
1165
1176
|
info = {};
|
|
1166
1177
|
}
|
|
1167
1178
|
}
|
|
1168
|
-
catch { }
|
|
1179
|
+
catch { /* ignore GOAWAY parse errors */ }
|
|
1169
1180
|
try {
|
|
1170
1181
|
this.onGoaway?.(info ?? {});
|
|
1171
1182
|
}
|
|
@@ -1298,7 +1309,7 @@ class HTTP2Parser {
|
|
|
1298
1309
|
};
|
|
1299
1310
|
}
|
|
1300
1311
|
// 处理 WINDOW_UPDATE 帧
|
|
1301
|
-
handleWindowUpdateFrame(frameHeader, payload
|
|
1312
|
+
handleWindowUpdateFrame(frameHeader, payload) {
|
|
1302
1313
|
try {
|
|
1303
1314
|
const windowUpdate = this.parseWindowUpdateFrame(payload, frameHeader);
|
|
1304
1315
|
this.connectionWindowSize += windowUpdate.windowSizeIncrement;
|
|
@@ -1439,8 +1450,8 @@ class StreamWriter {
|
|
|
1439
1450
|
}
|
|
1440
1451
|
catch (err) {
|
|
1441
1452
|
// Gracefully handle stream closing errors - 不要传递到 handleError
|
|
1442
|
-
const errMsg = err.message
|
|
1443
|
-
if (err.name === 'StreamStateError' ||
|
|
1453
|
+
const errMsg = (err instanceof Error ? err.message : '').toLowerCase();
|
|
1454
|
+
if ((err instanceof Error && err.name === 'StreamStateError') ||
|
|
1444
1455
|
errMsg.includes('closing') ||
|
|
1445
1456
|
errMsg.includes('closed') ||
|
|
1446
1457
|
errMsg.includes('write to a stream that is closed')) {
|
|
@@ -1452,6 +1463,7 @@ class StreamWriter {
|
|
|
1452
1463
|
}
|
|
1453
1464
|
}
|
|
1454
1465
|
createTransform() {
|
|
1466
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
1455
1467
|
const self = this;
|
|
1456
1468
|
return async function* (source) {
|
|
1457
1469
|
for await (const chunk of source) {
|
|
@@ -1461,7 +1473,7 @@ class StreamWriter {
|
|
|
1461
1473
|
// 因此这里统计的 bytesDrained 更接近实际被 sink 消费的字节数
|
|
1462
1474
|
try {
|
|
1463
1475
|
// 在下游消费后再扣减待消费队列,避免误判“next 没取”
|
|
1464
|
-
if (self.p
|
|
1476
|
+
if (self.p._queueSize != null) {
|
|
1465
1477
|
self.p._queueSize = Math.max(0, self.p._queueSize - chunk.byteLength);
|
|
1466
1478
|
}
|
|
1467
1479
|
self.bytesDrained += chunk.byteLength;
|
|
@@ -1481,10 +1493,11 @@ class StreamWriter {
|
|
|
1481
1493
|
};
|
|
1482
1494
|
}
|
|
1483
1495
|
// 简单的卡顿看门狗:当队列长期高位且 bytesDrained 无进展时发出 stalled 事件
|
|
1496
|
+
// 使用递归 setTimeout 而非 setInterval,避免标签页后台恢复时回调堆积导致 Violation
|
|
1484
1497
|
startWatchdog(intervalMs = 500, stallMs = 1500) {
|
|
1485
1498
|
if (this.watchdogTimer)
|
|
1486
1499
|
return;
|
|
1487
|
-
|
|
1500
|
+
const tick = () => {
|
|
1488
1501
|
if (this.abortController.signal.aborted)
|
|
1489
1502
|
return;
|
|
1490
1503
|
const baseThreshold = this.options.bufferSize * 0.7;
|
|
@@ -1495,7 +1508,13 @@ class StreamWriter {
|
|
|
1495
1508
|
if (!this.stallStartAt)
|
|
1496
1509
|
this.stallStartAt = now;
|
|
1497
1510
|
if (now - this.stallStartAt >= stallMs) {
|
|
1498
|
-
|
|
1511
|
+
// 异步触发事件,让当前 tick 立即返回,避免同步事件处理器阻塞主线程
|
|
1512
|
+
const detail = { queueSize: q, drained: this.bytesDrained, sinceMs: now - this.stallStartAt };
|
|
1513
|
+
setTimeout(() => {
|
|
1514
|
+
if (!this.abortController.signal.aborted) {
|
|
1515
|
+
this.dispatchEvent(new CustomEvent('stalled', { detail }));
|
|
1516
|
+
}
|
|
1517
|
+
}, 0);
|
|
1499
1518
|
// 避免持续触发,推进起点
|
|
1500
1519
|
this.stallStartAt = now;
|
|
1501
1520
|
}
|
|
@@ -1510,7 +1529,10 @@ class StreamWriter {
|
|
|
1510
1529
|
// 队列回落,重置
|
|
1511
1530
|
this.stallStartAt = 0;
|
|
1512
1531
|
}
|
|
1513
|
-
|
|
1532
|
+
// 本次 tick 完成后再安排下一次,不会因主线程繁忙而堆积
|
|
1533
|
+
this.watchdogTimer = setTimeout(tick, intervalMs);
|
|
1534
|
+
};
|
|
1535
|
+
this.watchdogTimer = setTimeout(tick, intervalMs);
|
|
1514
1536
|
}
|
|
1515
1537
|
async write(data) {
|
|
1516
1538
|
// 静默处理 aborted 状态,避免在正常的流关闭场景下抛出错误
|
|
@@ -1542,6 +1564,8 @@ class StreamWriter {
|
|
|
1542
1564
|
return data.arrayBuffer();
|
|
1543
1565
|
if (typeof data === 'string')
|
|
1544
1566
|
return new TextEncoder().encode(data).buffer;
|
|
1567
|
+
if (data instanceof Uint8Array)
|
|
1568
|
+
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
1545
1569
|
return data;
|
|
1546
1570
|
}
|
|
1547
1571
|
async writeChunks(buffer) {
|
|
@@ -1684,7 +1708,7 @@ class StreamWriter {
|
|
|
1684
1708
|
}
|
|
1685
1709
|
catch (err) {
|
|
1686
1710
|
// 忽略关闭已关闭流的错误
|
|
1687
|
-
const errMsg = err.message
|
|
1711
|
+
const errMsg = (err instanceof Error ? err.message : '').toLowerCase();
|
|
1688
1712
|
if (!errMsg.includes('closed') && !errMsg.includes('closing')) {
|
|
1689
1713
|
this.log?.trace?.('Stream close error:', err);
|
|
1690
1714
|
}
|
|
@@ -1702,7 +1726,8 @@ class StreamWriter {
|
|
|
1702
1726
|
// 先检查流状态,避免在已关闭的流上调用 abort
|
|
1703
1727
|
if (this.stream && typeof this.stream.abort === 'function') {
|
|
1704
1728
|
// 检查流的状态,避免操作已关闭的流
|
|
1705
|
-
const streamState = this.stream.status ||
|
|
1729
|
+
const streamState = this.stream.status ||
|
|
1730
|
+
this.stream.state;
|
|
1706
1731
|
if (streamState !== 'closed' && streamState !== 'closing') {
|
|
1707
1732
|
this.stream.abort(new Error(reason));
|
|
1708
1733
|
}
|
|
@@ -1711,7 +1736,7 @@ class StreamWriter {
|
|
|
1711
1736
|
catch (err) {
|
|
1712
1737
|
// Stream may already be closed, ignore all stream-related errors
|
|
1713
1738
|
// 完全忽略流操作错误,避免在错误处理中再次抛出错误
|
|
1714
|
-
const errMsg = err.message
|
|
1739
|
+
const errMsg = (err instanceof Error ? err.message : '').toLowerCase();
|
|
1715
1740
|
if (!errMsg.includes('closed') && !errMsg.includes('closing') && !errMsg.includes('write')) {
|
|
1716
1741
|
this.log?.trace?.('Stream abort error:', err);
|
|
1717
1742
|
}
|
|
@@ -1733,17 +1758,17 @@ class StreamWriter {
|
|
|
1733
1758
|
}
|
|
1734
1759
|
// 立即拒绝所有待处理的写入任务,避免它们继续执行
|
|
1735
1760
|
const pendingTasks = this.writeQueue.splice(0);
|
|
1736
|
-
pendingTasks.forEach(
|
|
1761
|
+
pendingTasks.forEach(() => {
|
|
1737
1762
|
// 这些任务的 Promise 会在执行时因为检查到 aborted 而被拒绝
|
|
1738
1763
|
});
|
|
1739
1764
|
try {
|
|
1740
1765
|
this.p.end();
|
|
1741
1766
|
}
|
|
1742
|
-
catch
|
|
1767
|
+
catch {
|
|
1743
1768
|
// Ignore errors when ending pushable
|
|
1744
1769
|
}
|
|
1745
1770
|
if (this.watchdogTimer) {
|
|
1746
|
-
|
|
1771
|
+
clearTimeout(this.watchdogTimer);
|
|
1747
1772
|
this.watchdogTimer = undefined;
|
|
1748
1773
|
}
|
|
1749
1774
|
}
|
|
@@ -1879,6 +1904,7 @@ class Libp2pGrpcClient {
|
|
|
1879
1904
|
}
|
|
1880
1905
|
await new Promise((resolve, reject) => {
|
|
1881
1906
|
let settled = false;
|
|
1907
|
+
// eslint-disable-next-line prefer-const
|
|
1882
1908
|
let timeoutId;
|
|
1883
1909
|
const cleanup = () => {
|
|
1884
1910
|
const idx = state.waiters.indexOf(waiter);
|
|
@@ -1961,7 +1987,7 @@ class Libp2pGrpcClient {
|
|
|
1961
1987
|
if (pooled) {
|
|
1962
1988
|
const conn = pooled.connection;
|
|
1963
1989
|
if (conn) {
|
|
1964
|
-
const status = conn
|
|
1990
|
+
const status = conn.status;
|
|
1965
1991
|
if (!status || status === "open") {
|
|
1966
1992
|
return conn;
|
|
1967
1993
|
}
|
|
@@ -1988,17 +2014,9 @@ class Libp2pGrpcClient {
|
|
|
1988
2014
|
}
|
|
1989
2015
|
};
|
|
1990
2016
|
try {
|
|
1991
|
-
|
|
1992
|
-
if (typeof anyConn.addEventListener === "function") {
|
|
1993
|
-
anyConn.addEventListener("close", removeFromPool, {
|
|
1994
|
-
once: true,
|
|
1995
|
-
});
|
|
1996
|
-
}
|
|
1997
|
-
else if (typeof anyConn.once === "function") {
|
|
1998
|
-
anyConn.once("close", removeFromPool);
|
|
1999
|
-
}
|
|
2017
|
+
conn.addEventListener("close", removeFromPool, { once: true });
|
|
2000
2018
|
}
|
|
2001
|
-
catch { }
|
|
2019
|
+
catch { /* ignore event listener registration errors */ }
|
|
2002
2020
|
}
|
|
2003
2021
|
}
|
|
2004
2022
|
return conn;
|
|
@@ -2068,14 +2086,14 @@ class Libp2pGrpcClient {
|
|
|
2068
2086
|
});
|
|
2069
2087
|
try {
|
|
2070
2088
|
writer.addEventListener("backpressure", (e) => {
|
|
2071
|
-
const d = e
|
|
2089
|
+
const d = e.detail || {};
|
|
2072
2090
|
console.warn(`[unary stream ${streamId}] backpressure current=${d.currentSize} avg=${d.averageSize} threshold=${d.threshold}`);
|
|
2073
2091
|
});
|
|
2074
|
-
writer.addEventListener("drain", (
|
|
2075
|
-
|
|
2092
|
+
writer.addEventListener("drain", () => {
|
|
2093
|
+
// drain event - no action needed
|
|
2076
2094
|
});
|
|
2077
2095
|
writer.addEventListener("stalled", (e) => {
|
|
2078
|
-
const d = e
|
|
2096
|
+
const d = e.detail || {};
|
|
2079
2097
|
console.warn(`[unary stream ${streamId}] stalled queue=${d.queueSize} drained=${d.drained} since=${d.sinceMs}ms — sending PING`);
|
|
2080
2098
|
try {
|
|
2081
2099
|
const payload = new Uint8Array(8);
|
|
@@ -2083,10 +2101,10 @@ class Libp2pGrpcClient {
|
|
|
2083
2101
|
const ping = Http2Frame.createFrame(0x6, 0x0, 0, payload);
|
|
2084
2102
|
writer.write(ping);
|
|
2085
2103
|
}
|
|
2086
|
-
catch { }
|
|
2104
|
+
catch { /* ignore ping write errors */ }
|
|
2087
2105
|
});
|
|
2088
2106
|
}
|
|
2089
|
-
catch { }
|
|
2107
|
+
catch { /* ignore addEventListener errors */ }
|
|
2090
2108
|
const parser = new HTTP2Parser(writer);
|
|
2091
2109
|
parser.onGoaway = (info) => {
|
|
2092
2110
|
console.warn("[unaryCall] GOAWAY received from server", info);
|
|
@@ -2097,7 +2115,7 @@ class Libp2pGrpcClient {
|
|
|
2097
2115
|
exitFlag = true;
|
|
2098
2116
|
errMsg = `GOAWAY received: code=${info.errorCode}`;
|
|
2099
2117
|
try {
|
|
2100
|
-
connection?.close
|
|
2118
|
+
connection?.close();
|
|
2101
2119
|
}
|
|
2102
2120
|
catch (err) {
|
|
2103
2121
|
console.warn("Error closing connection after GOAWAY:", err);
|
|
@@ -2122,7 +2140,6 @@ class Libp2pGrpcClient {
|
|
|
2122
2140
|
if (payload.length < 5) {
|
|
2123
2141
|
return;
|
|
2124
2142
|
}
|
|
2125
|
-
const compressionFlag = payload[0]; // 压缩标志
|
|
2126
2143
|
const lengthBytes = payload.slice(1, 5); // 消息长度的4字节
|
|
2127
2144
|
responseDataExpectedLength = new DataView(lengthBytes.buffer, lengthBytes.byteOffset).getUint32(0, false); // big-endian
|
|
2128
2145
|
if (responseDataExpectedLength < 0) {
|
|
@@ -2199,7 +2216,7 @@ class Libp2pGrpcClient {
|
|
|
2199
2216
|
const ackSettingFrame = Http2Frame.createSettingsAckFrame();
|
|
2200
2217
|
writer.write(ackSettingFrame);
|
|
2201
2218
|
};
|
|
2202
|
-
parser.onHeaders = (headers
|
|
2219
|
+
parser.onHeaders = (headers) => {
|
|
2203
2220
|
const plainHeaders = hpack.decodeHeaderFields(headers);
|
|
2204
2221
|
if (plainHeaders.get("grpc-status") === "0") {
|
|
2205
2222
|
// 成功状态
|
|
@@ -2224,21 +2241,12 @@ class Libp2pGrpcClient {
|
|
|
2224
2241
|
const settingFrme = Http2Frame.createSettingsFrame();
|
|
2225
2242
|
await writer.write(settingFrme);
|
|
2226
2243
|
// 等待对端 SETTINGS 或 ACK,择一即可,避免偶发握手竞态
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
})(),
|
|
2234
|
-
(async () => {
|
|
2235
|
-
await parser.waitForSettingsAck();
|
|
2236
|
-
progressed = true;
|
|
2237
|
-
})(),
|
|
2238
|
-
new Promise((res) => setTimeout(res, 300)),
|
|
2239
|
-
]);
|
|
2240
|
-
// 即使未等到,也继续;多数实现会随后发送
|
|
2241
|
-
}
|
|
2244
|
+
await Promise.race([
|
|
2245
|
+
parser.waitForPeerSettings(1000),
|
|
2246
|
+
parser.waitForSettingsAck(),
|
|
2247
|
+
new Promise((res) => setTimeout(res, 300)),
|
|
2248
|
+
]);
|
|
2249
|
+
// 即使未等到,也继续;多数实现会随后发送
|
|
2242
2250
|
// 创建头部帧
|
|
2243
2251
|
const headerFrame = Http2Frame.createHeadersFrame(streamId, method, true, this.token);
|
|
2244
2252
|
await writer.write(headerFrame);
|
|
@@ -2266,9 +2274,9 @@ class Libp2pGrpcClient {
|
|
|
2266
2274
|
checkResponse();
|
|
2267
2275
|
});
|
|
2268
2276
|
try {
|
|
2269
|
-
await writer.flush
|
|
2277
|
+
await writer.flush(timeout);
|
|
2270
2278
|
}
|
|
2271
|
-
catch { }
|
|
2279
|
+
catch { /* ignore flush errors */ }
|
|
2272
2280
|
await writer.end();
|
|
2273
2281
|
}
|
|
2274
2282
|
catch (err) {
|
|
@@ -2368,7 +2376,7 @@ class Libp2pGrpcClient {
|
|
|
2368
2376
|
if (options?.freshConnection) {
|
|
2369
2377
|
try {
|
|
2370
2378
|
this.connectionPool.delete(this.peerAddr.toString());
|
|
2371
|
-
await this.node
|
|
2379
|
+
await this.node.hangUp(this.peerAddr);
|
|
2372
2380
|
console.warn("[Call] hangUp existing connection before dialing due to freshConnection=true");
|
|
2373
2381
|
}
|
|
2374
2382
|
catch (err) {
|
|
@@ -2401,14 +2409,14 @@ class Libp2pGrpcClient {
|
|
|
2401
2409
|
});
|
|
2402
2410
|
try {
|
|
2403
2411
|
writer.addEventListener("backpressure", (e) => {
|
|
2404
|
-
const d = e
|
|
2412
|
+
const d = e.detail || {};
|
|
2405
2413
|
console.warn(`[stream ${streamId}] backpressure current=${d.currentSize} avg=${d.averageSize} threshold=${d.threshold}`);
|
|
2406
2414
|
});
|
|
2407
|
-
writer.addEventListener("drain", (
|
|
2408
|
-
|
|
2415
|
+
writer.addEventListener("drain", () => {
|
|
2416
|
+
// drain event - no action needed
|
|
2409
2417
|
});
|
|
2410
2418
|
writer.addEventListener("stalled", (e) => {
|
|
2411
|
-
const d = e
|
|
2419
|
+
const d = e.detail || {};
|
|
2412
2420
|
console.warn(`[stream ${streamId}] stalled queue=${d.queueSize} drained=${d.drained} since=${d.sinceMs}ms — sending PING`);
|
|
2413
2421
|
try {
|
|
2414
2422
|
const payload = new Uint8Array(8);
|
|
@@ -2416,10 +2424,10 @@ class Libp2pGrpcClient {
|
|
|
2416
2424
|
const ping = Http2Frame.createFrame(0x6, 0x0, 0, payload);
|
|
2417
2425
|
writer.write(ping);
|
|
2418
2426
|
}
|
|
2419
|
-
catch { /*
|
|
2427
|
+
catch { /* ignore ping write errors */ }
|
|
2420
2428
|
});
|
|
2421
2429
|
}
|
|
2422
|
-
catch { /*
|
|
2430
|
+
catch { /* ignore addEventListener errors */ }
|
|
2423
2431
|
const parser = new HTTP2Parser(writer, {
|
|
2424
2432
|
compatibilityMode: !useFlowControl,
|
|
2425
2433
|
});
|
|
@@ -2436,7 +2444,7 @@ class Libp2pGrpcClient {
|
|
|
2436
2444
|
}
|
|
2437
2445
|
internalController.abort();
|
|
2438
2446
|
try {
|
|
2439
|
-
connection?.close
|
|
2447
|
+
connection?.close();
|
|
2440
2448
|
}
|
|
2441
2449
|
catch (err) {
|
|
2442
2450
|
console.warn("Error closing connection after GOAWAY:", err);
|
|
@@ -2471,7 +2479,7 @@ class Libp2pGrpcClient {
|
|
|
2471
2479
|
}
|
|
2472
2480
|
};
|
|
2473
2481
|
// 在各个回调中检查是否已中止
|
|
2474
|
-
parser.onData = async (payload
|
|
2482
|
+
parser.onData = async (payload) => {
|
|
2475
2483
|
// 检查是否已中止
|
|
2476
2484
|
if (internalController.signal.aborted) {
|
|
2477
2485
|
return;
|
|
@@ -2491,7 +2499,6 @@ class Libp2pGrpcClient {
|
|
|
2491
2499
|
// 如果还没有读取消息长度,且缓冲区有足够数据
|
|
2492
2500
|
if (expectedMessageLength === -1 && messageBuffer.length >= 5) {
|
|
2493
2501
|
// 读取 gRPC 消息头:1字节压缩标志 + 4字节长度
|
|
2494
|
-
const compressionFlag = messageBuffer[0];
|
|
2495
2502
|
const lengthBytes = messageBuffer.slice(1, 5);
|
|
2496
2503
|
expectedMessageLength = new DataView(lengthBytes.buffer, lengthBytes.byteOffset).getUint32(0, false); // big-endian
|
|
2497
2504
|
}
|
|
@@ -2528,7 +2535,7 @@ class Libp2pGrpcClient {
|
|
|
2528
2535
|
const ackSettingFrame = Http2Frame.createSettingsAckFrame();
|
|
2529
2536
|
writer.write(ackSettingFrame);
|
|
2530
2537
|
};
|
|
2531
|
-
parser.onHeaders = (headers
|
|
2538
|
+
parser.onHeaders = (headers) => {
|
|
2532
2539
|
// 检查是否已中止
|
|
2533
2540
|
if (internalController.signal.aborted)
|
|
2534
2541
|
return;
|
|
@@ -2574,16 +2581,9 @@ class Libp2pGrpcClient {
|
|
|
2574
2581
|
}
|
|
2575
2582
|
// 等待对端 SETTINGS 或 ACK,择一即可,避免偶发握手竞态
|
|
2576
2583
|
{
|
|
2577
|
-
let progressed = false;
|
|
2578
2584
|
await Promise.race([
|
|
2579
|
-
(
|
|
2580
|
-
|
|
2581
|
-
progressed = true;
|
|
2582
|
-
})(),
|
|
2583
|
-
(async () => {
|
|
2584
|
-
await parser.waitForSettingsAck();
|
|
2585
|
-
progressed = true;
|
|
2586
|
-
})(),
|
|
2585
|
+
parser.waitForPeerSettings(1000),
|
|
2586
|
+
parser.waitForSettingsAck(),
|
|
2587
2587
|
new Promise((res) => setTimeout(res, 300)),
|
|
2588
2588
|
]);
|
|
2589
2589
|
// 即使未等到,也继续;多数实现会随后发送
|
|
@@ -2620,7 +2620,6 @@ class Libp2pGrpcClient {
|
|
|
2620
2620
|
}
|
|
2621
2621
|
// 动态批量处理逻辑 - 在处理过程中动态补充新数据
|
|
2622
2622
|
const batchSize = options?.batchSize || 10;
|
|
2623
|
-
const maxBatchWaitMs = options?.maxBatchWaitMs || 50;
|
|
2624
2623
|
// 动态批处理器
|
|
2625
2624
|
const processingQueue = [];
|
|
2626
2625
|
let isProcessing = false;
|
|
@@ -2751,9 +2750,9 @@ class Libp2pGrpcClient {
|
|
|
2751
2750
|
await writeFrame(finalFrame);
|
|
2752
2751
|
// 在结束前尽量冲刷内部队列,避免服务器看到部分数据 + context canceled
|
|
2753
2752
|
try {
|
|
2754
|
-
await writer.flush
|
|
2753
|
+
await writer.flush(timeout);
|
|
2755
2754
|
}
|
|
2756
|
-
catch { }
|
|
2755
|
+
catch { /* ignore flush errors */ }
|
|
2757
2756
|
await writer.end();
|
|
2758
2757
|
}
|
|
2759
2758
|
// 检查是否已中止
|
|
@@ -2800,15 +2799,16 @@ class Libp2pGrpcClient {
|
|
|
2800
2799
|
if (options?.freshConnection) {
|
|
2801
2800
|
try {
|
|
2802
2801
|
// 通过 libp2p 连接管理器关闭到该 peer 的连接
|
|
2803
|
-
|
|
2802
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2803
|
+
const conns = this.node.getConnections?.(this.peerAddr) || [];
|
|
2804
2804
|
for (const c of conns) {
|
|
2805
2805
|
try {
|
|
2806
2806
|
await c.close?.();
|
|
2807
2807
|
}
|
|
2808
|
-
catch { }
|
|
2808
|
+
catch { /* ignore close errors */ }
|
|
2809
2809
|
}
|
|
2810
2810
|
}
|
|
2811
|
-
catch { }
|
|
2811
|
+
catch { /* ignore connection cleanup errors */ }
|
|
2812
2812
|
}
|
|
2813
2813
|
if (streamSlotAcquired && state) {
|
|
2814
2814
|
state.activeStreams = Math.max(0, state.activeStreams - 1);
|