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.
@@ -46,6 +46,8 @@ export class StreamWriter {
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
- // 等待 drain 事件
173
- await this.stream.onDrain()
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) {
@@ -291,10 +302,11 @@ export class StreamWriter {
291
302
  }
292
303
 
293
304
  private async writeChunks(buffer: ArrayBuffer) {
294
- for (let offset = 0; offset < buffer.byteLength; offset += this.options.chunkSize!) {
295
- const end = Math.min(offset + this.options.chunkSize!, buffer.byteLength)
296
- const chunk = new Uint8Array( end - offset)
297
- chunk.set(new Uint8Array(buffer.slice(offset, end)))
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)
298
310
 
299
311
  await this.retryableWrite(chunk)
300
312
  this.updateProgress(chunk.byteLength)
@@ -321,16 +333,11 @@ export class StreamWriter {
321
333
  throw new Error('Stream aborted during backpressure monitoring')
322
334
  }
323
335
 
324
- await new Promise<void>((resolve, reject) => {
325
- try {
326
- this.p.push(chunk)
327
- }catch(err){
328
- reject(err)
329
- }
330
- resolve()
331
- })
332
- } catch (err) {
333
- 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!) {
334
341
  const delay = this.calculateRetryDelay(attempt)
335
342
  await new Promise(r => setTimeout(r, delay))
336
343
  return this.retryableWrite(chunk, attempt + 1)
@@ -339,71 +346,48 @@ export class StreamWriter {
339
346
  }
340
347
  }
341
348
 
342
- private async monitorBackpressure(): Promise<void> {
343
- const currentSize = this.queueSize
344
- const baseThreshold = this.options.bufferSize! * 0.7 // 降低基础阈值,更早检测
345
- const criticalThreshold = this.options.bufferSize! * 0.9 // 临界阈值
346
-
347
- // 快速路径:无背压时直接返回
348
- 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) {
349
355
  if (this.isBackpressure) {
350
356
  this.isBackpressure = false
351
- this.dispatchBackpressureEvent({
352
- currentSize,
353
- averageSize: this.getAverageQueueSize(),
354
- threshold: baseThreshold,
355
- waitingTime: 0
356
- })
357
+ this.dispatchBackpressureEvent({ currentSize: this.queueSize, averageSize: this.getAverageQueueSize(), threshold: baseThreshold, waitingTime: 0 })
357
358
  }
358
359
  return
359
360
  }
360
-
361
- // 进入背压状态
361
+
362
362
  if (!this.isBackpressure) {
363
363
  this.isBackpressure = true
364
- this.dispatchBackpressureEvent({
365
- currentSize,
366
- averageSize: this.getAverageQueueSize(),
367
- threshold: baseThreshold,
368
- waitingTime: 0
369
- })
364
+ this.dispatchBackpressureEvent({ currentSize: this.queueSize, averageSize: this.getAverageQueueSize(), threshold: baseThreshold, waitingTime: 0 })
370
365
  }
371
-
372
- // 智能等待策略
373
- const pressure = currentSize / this.options.bufferSize!
374
- let waitTime: number
375
-
376
- if (currentSize >= criticalThreshold) {
377
- // 临界状态:长时间等待
378
- waitTime = 50 + Math.min(200, pressure * 100)
379
- } else {
380
- // 轻度背压:短时间等待
381
- waitTime = Math.min(20, pressure * 30)
382
- }
383
-
384
- // 使用指数退避,但最多等待3次
385
- let retryCount = 0
386
- const maxRetries = 3
387
-
388
- while (this.queueSize >= baseThreshold && retryCount < maxRetries) {
366
+
367
+ // 事件驱动等待:每轮等到 drain 触发或超时,最多 3 轮
368
+ const maxRounds = 3
369
+ for (let i = 0; i < maxRounds; i++) {
389
370
  if (this.abortController.signal.aborted) break
390
-
391
- await new Promise(r => setTimeout(r, waitTime))
392
- retryCount++
393
-
394
- // 动态调整等待时间
395
- waitTime = Math.min(waitTime * 1.5, 100)
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
+ })
396
381
  }
397
-
398
- // 如果仍然背压但达到最大重试次数,记录警告但继续执行
382
+
399
383
  if (this.queueSize >= baseThreshold) {
400
384
  const now = Date.now()
401
- if (now - this.lastBpWarnAt > 1000) { // 节流警告
385
+ if (now - this.lastBpWarnAt > 1000) {
402
386
  this.lastBpWarnAt = now
403
387
  console.warn(`Stream writer: High backpressure detected (${this.queueSize} bytes), continuing anyway`)
404
388
  }
405
389
  }
406
- }
390
+ }
407
391
 
408
392
 
409
393
  private calculateRetryDelay(attempt: number): number {
@@ -505,11 +489,17 @@ export class StreamWriter {
505
489
  this.abortController.abort()
506
490
  }
507
491
 
508
- // 立即拒绝所有待处理的写入任务,避免它们继续执行
492
+ // 执行所有待处理的写入任务:它们会检查 signal.aborted 并立即 resolve,
493
+ // 不执行的话调用方的 Promise 会永远挂住
509
494
  const pendingTasks = this.writeQueue.splice(0)
510
- pendingTasks.forEach(() => {
511
- // 这些任务的 Promise 会在执行时因为检查到 aborted 而被拒绝
512
- })
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 */ } }
513
503
 
514
504
  try {
515
505
  this.p.end()
@@ -523,19 +513,34 @@ export class StreamWriter {
523
513
  // 等待内部队列被下游完全消费(用于在结束前确保尽量发送完数据)
524
514
  // 默认超时 10s,避免无限等待
525
515
  async flush(timeoutMs: number = 10000): Promise<void> {
526
- const start = Date.now()
527
516
  // 快速路径
528
517
  if (this.queueSize <= 0 && !this.isProcessingQueue && this.writeQueue.length === 0) return
529
- // 轮询等待队列清空
530
- while (true) {
531
- if (this.abortController.signal.aborted) return
532
- if (this.queueSize <= 0 && !this.isProcessingQueue && this.writeQueue.length === 0) return
533
- if (Date.now() - start > timeoutMs) {
534
- console.warn(`Stream writer: flush timeout with ${this.queueSize} bytes still queued`)
535
- 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
536
524
  }
537
- await new Promise(r => setTimeout(r, 10))
538
- }
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
+ })
539
544
  }
540
545
 
541
546
  // 事件系统