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/dc-http2/frame.cjs.js +51 -19
- 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 +51 -19
- package/dist/dc-http2/frame.esm.js.map +1 -1
- package/dist/dc-http2/hpack.cjs.js +43 -13
- package/dist/dc-http2/hpack.cjs.js.map +1 -1
- package/dist/dc-http2/hpack.esm.js +43 -13
- package/dist/dc-http2/hpack.esm.js.map +1 -1
- package/dist/dc-http2/parser.cjs.js +281 -189
- package/dist/dc-http2/parser.cjs.js.map +1 -1
- package/dist/dc-http2/parser.d.ts +21 -2
- package/dist/dc-http2/parser.esm.js +281 -189
- package/dist/dc-http2/parser.esm.js.map +1 -1
- package/dist/dc-http2/stream.cjs.js +111 -74
- package/dist/dc-http2/stream.cjs.js.map +1 -1
- package/dist/dc-http2/stream.d.ts +2 -0
- package/dist/dc-http2/stream.esm.js +111 -74
- package/dist/dc-http2/stream.esm.js.map +1 -1
- package/dist/grpc.js +824 -583
- 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 +647 -415
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +647 -415
- package/dist/index.esm.js.map +1 -1
- package/dist/stats.html +1 -1
- package/package.json +1 -1
- package/src/dc-http2/frame.ts +11 -9
- package/src/dc-http2/hpack.ts +43 -13
- package/src/dc-http2/parser.ts +219 -196
- package/src/dc-http2/stream.ts +99 -84
- package/src/index.ts +240 -183
package/src/dc-http2/stream.ts
CHANGED
|
@@ -41,11 +41,13 @@ export class StreamWriter {
|
|
|
41
41
|
private lastBackpressureCheck = 0 // 添加时间戳缓存
|
|
42
42
|
private bytesDrained = 0 // 统计下游实际消化的字节数
|
|
43
43
|
private lastDrainEventAt = 0
|
|
44
|
-
private watchdogTimer: ReturnType<typeof
|
|
44
|
+
private watchdogTimer: ReturnType<typeof setTimeout> | undefined
|
|
45
45
|
private stallStartAt = 0
|
|
46
46
|
private lastBytesDrainedSeen = 0
|
|
47
47
|
private lastBpWarnAt = 0
|
|
48
48
|
private isHandlingError = false // 防止重复错误处理
|
|
49
|
+
/** drain 事件驱动等待者,替代 flush() 中的 setInterval 轮询 */
|
|
50
|
+
private drainWaiters: Array<() => void> = []
|
|
49
51
|
|
|
50
52
|
private log?: { trace?: (...args: unknown[]) => void }
|
|
51
53
|
|
|
@@ -169,8 +171,9 @@ export class StreamWriter {
|
|
|
169
171
|
// 使用 stream.send() 发送数据,返回 false 表示需要等待 drain
|
|
170
172
|
const canContinue = this.stream.send(chunk)
|
|
171
173
|
if (!canContinue) {
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
+
// 传入 abort signal,当流被 abort 时 onDrain() 会立即 reject,
|
|
175
|
+
// 避免在 abort 路径下永久挂住
|
|
176
|
+
await this.stream.onDrain({ signal: this.abortController.signal })
|
|
174
177
|
}
|
|
175
178
|
} catch (err: unknown) {
|
|
176
179
|
// Gracefully handle stream closing errors - 不要传递到 handleError
|
|
@@ -185,6 +188,9 @@ export class StreamWriter {
|
|
|
185
188
|
throw err
|
|
186
189
|
}
|
|
187
190
|
}
|
|
191
|
+
// pipeline 正常结束(stream 关闭或 pushable 耗尽)—— 确保资源清理
|
|
192
|
+
// 若已通过 abort() 触发则 cleanup() 内部幂等处理
|
|
193
|
+
this.cleanup()
|
|
188
194
|
}
|
|
189
195
|
|
|
190
196
|
private createTransform() {
|
|
@@ -207,6 +213,11 @@ export class StreamWriter {
|
|
|
207
213
|
self.lastDrainEventAt = now
|
|
208
214
|
self.dispatchEvent(new CustomEvent('drain', { detail: { drained: self.bytesDrained, queueSize: self.queueSize } }))
|
|
209
215
|
}
|
|
216
|
+
// 唤醒所有在等 flush() 或背压解除 的 drainWaiters(队列降低时就可唤醒)
|
|
217
|
+
if (self.drainWaiters.length > 0) {
|
|
218
|
+
const ws = self.drainWaiters.splice(0);
|
|
219
|
+
for (const fn of ws) { try { fn(); } catch { /* ignore */ } }
|
|
220
|
+
}
|
|
210
221
|
// 记录本次已消耗字节,用于看门狗判断是否前进
|
|
211
222
|
self.lastBytesDrainedSeen = self.bytesDrained
|
|
212
223
|
} catch (err) {
|
|
@@ -219,9 +230,10 @@ export class StreamWriter {
|
|
|
219
230
|
}
|
|
220
231
|
|
|
221
232
|
// 简单的卡顿看门狗:当队列长期高位且 bytesDrained 无进展时发出 stalled 事件
|
|
233
|
+
// 使用递归 setTimeout 而非 setInterval,避免标签页后台恢复时回调堆积导致 Violation
|
|
222
234
|
private startWatchdog(intervalMs: number = 500, stallMs: number = 1500) {
|
|
223
235
|
if (this.watchdogTimer) return
|
|
224
|
-
|
|
236
|
+
const tick = () => {
|
|
225
237
|
if (this.abortController.signal.aborted) return
|
|
226
238
|
const baseThreshold = this.options.bufferSize! * 0.7
|
|
227
239
|
const q = this.queueSize
|
|
@@ -230,7 +242,13 @@ export class StreamWriter {
|
|
|
230
242
|
if (this.lastBytesDrainedSeen === this.bytesDrained) {
|
|
231
243
|
if (!this.stallStartAt) this.stallStartAt = now
|
|
232
244
|
if (now - this.stallStartAt >= stallMs) {
|
|
233
|
-
|
|
245
|
+
// 异步触发事件,让当前 tick 立即返回,避免同步事件处理器阻塞主线程
|
|
246
|
+
const detail = { queueSize: q, drained: this.bytesDrained, sinceMs: now - this.stallStartAt }
|
|
247
|
+
setTimeout(() => {
|
|
248
|
+
if (!this.abortController.signal.aborted) {
|
|
249
|
+
this.dispatchEvent(new CustomEvent('stalled', { detail }))
|
|
250
|
+
}
|
|
251
|
+
}, 0)
|
|
234
252
|
// 避免持续触发,推进起点
|
|
235
253
|
this.stallStartAt = now
|
|
236
254
|
}
|
|
@@ -243,7 +261,10 @@ export class StreamWriter {
|
|
|
243
261
|
// 队列回落,重置
|
|
244
262
|
this.stallStartAt = 0
|
|
245
263
|
}
|
|
246
|
-
|
|
264
|
+
// 本次 tick 完成后再安排下一次,不会因主线程繁忙而堆积
|
|
265
|
+
this.watchdogTimer = setTimeout(tick, intervalMs)
|
|
266
|
+
}
|
|
267
|
+
this.watchdogTimer = setTimeout(tick, intervalMs)
|
|
247
268
|
}
|
|
248
269
|
|
|
249
270
|
async write(data: ArrayBuffer | Uint8Array | Blob | string): Promise<void> {
|
|
@@ -281,10 +302,11 @@ export class StreamWriter {
|
|
|
281
302
|
}
|
|
282
303
|
|
|
283
304
|
private async writeChunks(buffer: ArrayBuffer) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
|
|
305
|
+
const src = new Uint8Array(buffer);
|
|
306
|
+
for (let offset = 0; offset < src.byteLength; offset += this.options.chunkSize!) {
|
|
307
|
+
const end = Math.min(offset + this.options.chunkSize!, src.byteLength)
|
|
308
|
+
// subarray 创建视图,不拷贝内存。pushable.push 不修改内容,安全。
|
|
309
|
+
const chunk = src.subarray(offset, end)
|
|
288
310
|
|
|
289
311
|
await this.retryableWrite(chunk)
|
|
290
312
|
this.updateProgress(chunk.byteLength)
|
|
@@ -311,16 +333,11 @@ export class StreamWriter {
|
|
|
311
333
|
throw new Error('Stream aborted during backpressure monitoring')
|
|
312
334
|
}
|
|
313
335
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
resolve()
|
|
321
|
-
})
|
|
322
|
-
} catch (err) {
|
|
323
|
-
if (attempt < this.options.retries!) {
|
|
336
|
+
// push 是同步操作,直接调用即可
|
|
337
|
+
this.p.push(chunk)
|
|
338
|
+
} catch (err) {
|
|
339
|
+
// aborted 时不重试,立即抛出
|
|
340
|
+
if (!this.abortController.signal.aborted && attempt < this.options.retries!) {
|
|
324
341
|
const delay = this.calculateRetryDelay(attempt)
|
|
325
342
|
await new Promise(r => setTimeout(r, delay))
|
|
326
343
|
return this.retryableWrite(chunk, attempt + 1)
|
|
@@ -329,71 +346,48 @@ export class StreamWriter {
|
|
|
329
346
|
}
|
|
330
347
|
}
|
|
331
348
|
|
|
332
|
-
private async monitorBackpressure(): Promise<void> {
|
|
333
|
-
const
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (currentSize < baseThreshold) {
|
|
349
|
+
private async monitorBackpressure(): Promise<void> {
|
|
350
|
+
const baseThreshold = this.options.bufferSize! * 0.7
|
|
351
|
+
const criticalThreshold = this.options.bufferSize! * 0.9
|
|
352
|
+
|
|
353
|
+
// 快速路径
|
|
354
|
+
if (this.queueSize < baseThreshold) {
|
|
339
355
|
if (this.isBackpressure) {
|
|
340
356
|
this.isBackpressure = false
|
|
341
|
-
this.dispatchBackpressureEvent({
|
|
342
|
-
currentSize,
|
|
343
|
-
averageSize: this.getAverageQueueSize(),
|
|
344
|
-
threshold: baseThreshold,
|
|
345
|
-
waitingTime: 0
|
|
346
|
-
})
|
|
357
|
+
this.dispatchBackpressureEvent({ currentSize: this.queueSize, averageSize: this.getAverageQueueSize(), threshold: baseThreshold, waitingTime: 0 })
|
|
347
358
|
}
|
|
348
359
|
return
|
|
349
360
|
}
|
|
350
|
-
|
|
351
|
-
// 进入背压状态
|
|
361
|
+
|
|
352
362
|
if (!this.isBackpressure) {
|
|
353
363
|
this.isBackpressure = true
|
|
354
|
-
this.dispatchBackpressureEvent({
|
|
355
|
-
currentSize,
|
|
356
|
-
averageSize: this.getAverageQueueSize(),
|
|
357
|
-
threshold: baseThreshold,
|
|
358
|
-
waitingTime: 0
|
|
359
|
-
})
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// 智能等待策略
|
|
363
|
-
const pressure = currentSize / this.options.bufferSize!
|
|
364
|
-
let waitTime: number
|
|
365
|
-
|
|
366
|
-
if (currentSize >= criticalThreshold) {
|
|
367
|
-
// 临界状态:长时间等待
|
|
368
|
-
waitTime = 50 + Math.min(200, pressure * 100)
|
|
369
|
-
} else {
|
|
370
|
-
// 轻度背压:短时间等待
|
|
371
|
-
waitTime = Math.min(20, pressure * 30)
|
|
364
|
+
this.dispatchBackpressureEvent({ currentSize: this.queueSize, averageSize: this.getAverageQueueSize(), threshold: baseThreshold, waitingTime: 0 })
|
|
372
365
|
}
|
|
373
|
-
|
|
374
|
-
//
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
while (this.queueSize >= baseThreshold && retryCount < maxRetries) {
|
|
366
|
+
|
|
367
|
+
// 事件驱动等待:每轮等到 drain 触发或超时,最多 3 轮
|
|
368
|
+
const maxRounds = 3
|
|
369
|
+
for (let i = 0; i < maxRounds; i++) {
|
|
379
370
|
if (this.abortController.signal.aborted) break
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
371
|
+
if (this.queueSize < baseThreshold) break
|
|
372
|
+
|
|
373
|
+
const isCritical = this.queueSize >= criticalThreshold
|
|
374
|
+
const waitMs = isCritical ? 100 : 30
|
|
375
|
+
|
|
376
|
+
await new Promise<void>(resolve => {
|
|
377
|
+
let done = false
|
|
378
|
+
const timer = setTimeout(() => { if (!done) { done = true; resolve() } }, waitMs)
|
|
379
|
+
this.drainWaiters.push(() => { if (!done) { done = true; clearTimeout(timer); resolve() } })
|
|
380
|
+
})
|
|
386
381
|
}
|
|
387
|
-
|
|
388
|
-
// 如果仍然背压但达到最大重试次数,记录警告但继续执行
|
|
382
|
+
|
|
389
383
|
if (this.queueSize >= baseThreshold) {
|
|
390
384
|
const now = Date.now()
|
|
391
|
-
if (now - this.lastBpWarnAt > 1000) {
|
|
385
|
+
if (now - this.lastBpWarnAt > 1000) {
|
|
392
386
|
this.lastBpWarnAt = now
|
|
393
387
|
console.warn(`Stream writer: High backpressure detected (${this.queueSize} bytes), continuing anyway`)
|
|
394
388
|
}
|
|
395
389
|
}
|
|
396
|
-
}
|
|
390
|
+
}
|
|
397
391
|
|
|
398
392
|
|
|
399
393
|
private calculateRetryDelay(attempt: number): number {
|
|
@@ -495,11 +489,17 @@ export class StreamWriter {
|
|
|
495
489
|
this.abortController.abort()
|
|
496
490
|
}
|
|
497
491
|
|
|
498
|
-
//
|
|
492
|
+
// 执行所有待处理的写入任务:它们会检查 signal.aborted 并立即 resolve,
|
|
493
|
+
// 不执行的话调用方的 Promise 会永远挂住
|
|
499
494
|
const pendingTasks = this.writeQueue.splice(0)
|
|
500
|
-
pendingTasks
|
|
501
|
-
|
|
502
|
-
}
|
|
495
|
+
for (const task of pendingTasks) {
|
|
496
|
+
task().catch(() => { /* already aborted, ignore */ })
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// 唤醒所有 drainWaiters(flush / monitorBackpressure 中的等待者),
|
|
500
|
+
// 让它们检查 signal.aborted 并立即 resolve,不必等到各自的超时
|
|
501
|
+
const ws = this.drainWaiters.splice(0)
|
|
502
|
+
for (const fn of ws) { try { fn() } catch { /* ignore */ } }
|
|
503
503
|
|
|
504
504
|
try {
|
|
505
505
|
this.p.end()
|
|
@@ -507,25 +507,40 @@ export class StreamWriter {
|
|
|
507
507
|
// Ignore errors when ending pushable
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
-
if (this.watchdogTimer) {
|
|
510
|
+
if (this.watchdogTimer) { clearTimeout(this.watchdogTimer); this.watchdogTimer = undefined }
|
|
511
511
|
}
|
|
512
512
|
|
|
513
513
|
// 等待内部队列被下游完全消费(用于在结束前确保尽量发送完数据)
|
|
514
514
|
// 默认超时 10s,避免无限等待
|
|
515
515
|
async flush(timeoutMs: number = 10000): Promise<void> {
|
|
516
|
-
const start = Date.now()
|
|
517
516
|
// 快速路径
|
|
518
517
|
if (this.queueSize <= 0 && !this.isProcessingQueue && this.writeQueue.length === 0) return
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
if (
|
|
524
|
-
|
|
525
|
-
return
|
|
518
|
+
if (this.abortController.signal.aborted) return
|
|
519
|
+
|
|
520
|
+
await new Promise<void>((resolve) => {
|
|
521
|
+
// 已经清空
|
|
522
|
+
if (this.queueSize <= 0 && !this.isProcessingQueue && this.writeQueue.length === 0) {
|
|
523
|
+
resolve(); return
|
|
526
524
|
}
|
|
527
|
-
|
|
528
|
-
|
|
525
|
+
let done = false
|
|
526
|
+
const timer = setTimeout(() => {
|
|
527
|
+
if (!done) {
|
|
528
|
+
done = true
|
|
529
|
+
console.warn(`Stream writer: flush timeout with ${this.queueSize} bytes still queued`)
|
|
530
|
+
resolve()
|
|
531
|
+
}
|
|
532
|
+
}, timeoutMs)
|
|
533
|
+
// 由 createTransform 在每个 chunk 被下游消耗后唤醒
|
|
534
|
+
const check = () => {
|
|
535
|
+
if (this.abortController.signal.aborted || (this.queueSize <= 0 && !this.isProcessingQueue && this.writeQueue.length === 0)) {
|
|
536
|
+
if (!done) { done = true; clearTimeout(timer); resolve() }
|
|
537
|
+
} else {
|
|
538
|
+
// 下次 drain 时再检查
|
|
539
|
+
this.drainWaiters.push(check)
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
this.drainWaiters.push(check)
|
|
543
|
+
})
|
|
529
544
|
}
|
|
530
545
|
|
|
531
546
|
// 事件系统
|