@lesomnus/grpc-dgram 0.0.1 → 0.1.0

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.d.mts CHANGED
@@ -1,6 +1,5 @@
1
- import { $ as StatusError, A as decodeEnvelop, B as isHalfClose, C as FlagCompressed, D as FlagWindow, E as FlagReset, F as frameStatus, G as isTerminal, H as isOpen, I as hasUnknownFlags, J as resetFor, K as isWindow, L as isClose, M as encodeEnvelop, N as encodeFrame, O as Frame, P as frame, Q as MessageTooLargeError, R as isCompressed, S as FlagClose, T as FlagPing, U as isPing, V as isHeaderFrame, W as isReset, X as shapeOf, Y as setFrameError, Z as Code, _ as TransportInfo, a as EndOfStreamError, at as Limits, b as unpack, c as Compressor, ct as Timing, d as DEFAULT_STALL_MS, et as abortCause, f as W_INIT, g as FrameHandler, h as FrameContext, i as DetailedStatusError, it as DropPolicy, j as decodeFrame, k as SHAPE_MASK, l as DEFAULT_MAX_RECV_MSG_SIZE, m as ConnAttacher, n as Conn, nt as statusError, o as FlowTiming, ot as RxBufferConfig, p as getCompressor, q as legalShape, r as ConnOptions, rt as toStatusError, s as statusDetails, t as ClientStream, tt as isMessageTooLarge, u as DEFAULT_MAX_SEND_MSG_SIZE, v as hasConnAttacher, w as FlagOpen, x as Any, y as hasTransportInfo, z as isData } from "./conn-DTWG9vIx.mjs";
1
+ import { $ as encodeEnvelope, A as DEFAULT_STALL_MS, At as DropPolicy, B as hasTransportInfo, C as ServerReader, Ct as statusError, D as Compressor, Dt as ProtocolEvent, E as UnaryHandler, Et as Counters, F as EnvelopeSender, G as FlagOpen, H as Any, I as FrameContext, J as FlagWindow, K as FlagPing, L as FrameHandler, M as W_INIT, Mt as RxBufferConfig, N as getCompressor, Nt as Timing, O as DEFAULT_MAX_RECV_MSG_SIZE, Ot as ProtocolEventKind, P as ConnAttacher, Q as decodeFrame, R as TransportInfo, S as ServerOptions, St as isMessageTooLarge, T as ServerWriter, Tt as CounterSnapshot, U as FlagClose, V as unpack, W as FlagCompressed, X as SHAPE_MASK, Y as Frame, Z as decodeEnvelope, _ as UnaryServerInterceptor, _t as shapeOf, a as DetailedStatusError, at as isCompressed, b as Server, bt as StatusError, c as statusDetails, ct as isHeaderFrame, d as StreamServerHandler, dt as isReset, et as encodeFrame, f as StreamServerInterceptor, ft as isTerminal, g as UnaryServerHandler, gt as setFrameError, h as UnaryInvoker, ht as resetFor, i as ConnOptions, it as isClose, j as W_CONN, jt as Limits, k as DEFAULT_MAX_SEND_MSG_SIZE, kt as ProtocolStats, l as ClientCall, lt as isOpen, m as UnaryClientInterceptor, mt as legalShape, n as ClientStream, nt as frameStatus, o as EndOfStreamError, ot as isData, p as Streamer, pt as isWindow, q as FlagReset, r as Conn, rt as hasUnknownFlags, s as FlowTiming, st as isHalfClose, t as CallConfig, tt as frame, u as StreamClientInterceptor, ut as isPing, v as BidiHandler, vt as Code, w as ServerStreamingHandler, wt as toStatusError, x as ServerContext, xt as abortCause, y as ClientStreamingHandler, yt as MessageTooLargeError, z as hasConnAttacher } from "./conn-Dc57BC-h.mjs";
2
2
  import { S as validateMetadataPair, _ as decodeBase64, a as MethodDesc, b as metadataJoin, c as ServerStreamingDesc, d as clientStreamingMethod, f as isUnary, g as cloneMetadata, h as Metadata, i as ForcedCodec, l as UnaryDesc, m as unaryMethod, n as CallOptions, o as NamedCodec, p as serverStreamingMethod, r as ClientStreamingDesc, s as PayloadCodec, t as BidiDesc, u as bidiMethod, v as encodeBase64, x as validateMetadata, y as isBinaryKey } from "./desc-BMF2FBqk.mjs";
3
- import { a as ServerOptions, c as ServerWriter, i as ServerContext, l as UnaryHandler, n as ClientStreamingHandler, o as ServerReader, r as Server, s as ServerStreamingHandler, t as BidiHandler } from "./server-xIr1mwqq.mjs";
4
3
  //#region src/seq.d.ts
5
4
  declare const W_FWD = 4096;
6
5
  declare const K_LOUD = 3;
@@ -21,7 +20,9 @@ declare class RxWindow {
21
20
  strict: boolean;
22
21
  private beyondN;
23
22
  private beyondLast;
23
+ private gap;
24
+ takeGap(): number;
24
25
  check(seq: number): RxVerdict;
25
26
  }
26
27
  //#endregion
27
- export { type Any, type BidiDesc, type BidiHandler, type CallOptions, ClientStream, type ClientStreamingDesc, type ClientStreamingHandler, Code, type Compressor, Conn, type ConnAttacher, type ConnOptions, DEFAULT_MAX_RECV_MSG_SIZE, DEFAULT_MAX_SEND_MSG_SIZE, DEFAULT_STALL_MS, type DetailedStatusError, DropPolicy, EndOfStreamError, FlagClose, FlagCompressed, FlagOpen, FlagPing, FlagReset, FlagWindow, type FlowTiming, type ForcedCodec, type Frame, type FrameContext, type FrameHandler, K_LOUD, type Limits, MessageTooLargeError, type Metadata, type MethodDesc, type NamedCodec, type PayloadCodec, type RxBufferConfig, RxVerdict, RxWindow, SHAPE_MASK, Server, type ServerContext, type ServerOptions, type ServerReader, type ServerStreamingDesc, type ServerStreamingHandler, type ServerWriter, StatusError, type Timing, type TransportInfo, TxSeq, type UnaryDesc, type UnaryHandler, W_FWD, W_INIT, abortCause, bidiMethod, clientStreamingMethod, cloneMetadata, decodeBase64, decodeEnvelop, decodeFrame, encodeBase64, encodeEnvelop, encodeFrame, frame, frameStatus, getCompressor, hasConnAttacher, hasTransportInfo, hasUnknownFlags, isBinaryKey, isClose, isCompressed, isData, isHalfClose, isHeaderFrame, isMessageTooLarge, isOpen, isPing, isReset, isTerminal, isUnary, isWindow, legalShape, metadataJoin, resetFor, serverStreamingMethod, setFrameError, shapeOf, statusDetails, statusError, toStatusError, unaryMethod, unpack, validateMetadata, validateMetadataPair };
28
+ export { type Any, type BidiDesc, type BidiHandler, type CallConfig, type CallOptions, type ClientCall, ClientStream, type ClientStreamingDesc, type ClientStreamingHandler, Code, type Compressor, Conn, type ConnAttacher, type ConnOptions, type CounterSnapshot, Counters, DEFAULT_MAX_RECV_MSG_SIZE, DEFAULT_MAX_SEND_MSG_SIZE, DEFAULT_STALL_MS, type DetailedStatusError, DropPolicy, EndOfStreamError, type EnvelopeSender, FlagClose, FlagCompressed, FlagOpen, FlagPing, FlagReset, FlagWindow, type FlowTiming, type ForcedCodec, type Frame, type FrameContext, type FrameHandler, K_LOUD, type Limits, MessageTooLargeError, type Metadata, type MethodDesc, type NamedCodec, type PayloadCodec, type ProtocolEvent, type ProtocolEventKind, type ProtocolStats, type RxBufferConfig, RxVerdict, RxWindow, SHAPE_MASK, Server, type ServerContext, type ServerOptions, type ServerReader, type ServerStreamingDesc, type ServerStreamingHandler, type ServerWriter, StatusError, type StreamClientInterceptor, type StreamServerHandler, type StreamServerInterceptor, type Streamer, type Timing, type TransportInfo, TxSeq, type UnaryClientInterceptor, type UnaryDesc, type UnaryHandler, type UnaryInvoker, type UnaryServerHandler, type UnaryServerInterceptor, W_CONN, W_FWD, W_INIT, abortCause, bidiMethod, clientStreamingMethod, cloneMetadata, decodeBase64, decodeEnvelope, decodeFrame, encodeBase64, encodeEnvelope, encodeFrame, frame, frameStatus, getCompressor, hasConnAttacher, hasTransportInfo, hasUnknownFlags, isBinaryKey, isClose, isCompressed, isData, isHalfClose, isHeaderFrame, isMessageTooLarge, isOpen, isPing, isReset, isTerminal, isUnary, isWindow, legalShape, metadataJoin, resetFor, serverStreamingMethod, setFrameError, shapeOf, statusDetails, statusError, toStatusError, unaryMethod, unpack, validateMetadata, validateMetadataPair };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { $ as unpack, A as DEFAULT_MAX_SEND_MSG_SIZE, B as checkSendSize, C as isTerminal, D as setFrameError, E as resetFor, F as Latch, G as noop, H as decompressPayload, I as Sweeper, J as reliableRxSize, K as nowMs, L as W_INIT, M as FlowReceiver, N as FlowSender, O as shapeOf, P as FrameQueue, Q as hasTransportInfo, S as isReset, T as legalShape, U as getCompressor, V as compressPayload, W as nonzeroEpoch, X as unrefTimer, Y as sizeOr, Z as hasConnAttacher, _ as isData, a as FlagReset, at as validateMetadata, b as isOpen, c as decodeEnvelop, ct as resolveLimits, d as encodeFrame, et as cloneMetadata, f as frame, g as isCompressed, h as isClose, i as FlagPing, it as metadataJoin, j as DEFAULT_STALL_MS, k as DEFAULT_MAX_RECV_MSG_SIZE, l as decodeFrame, lt as resolveRxConfig, m as hasUnknownFlags, n as FlagCompressed, nt as encodeBase64, o as FlagWindow, ot as validateMetadataPair, p as frameStatus, q as rawPayload, r as FlagOpen, rt as isBinaryKey, s as SHAPE_MASK, st as DropPolicy, t as FlagClose, tt as decodeBase64, u as encodeEnvelop, v as isHalfClose, w as isWindow, x as isPing, y as isHeaderFrame, z as checkRecvSize } from "./wire-BR8KiyRg.mjs";
1
+ import { a as resolveTiming, c as statsSink, d as RxWindow, f as TxSeq, i as statusDetails, l as K_LOUD, m as chain2, n as Conn, o as Counters, p as W_FWD, r as EndOfStreamError, s as emit, t as ClientStream, u as RxVerdict } from "./conn-CmYPHTPR.mjs";
2
+ import { $ as sizeOr, A as DEFAULT_MAX_SEND_MSG_SIZE, B as W_INIT, C as isTerminal, D as setFrameError, E as resetFor, F as Latch, G as compressPayload, H as acquireBoth, I as PeerFlowRx, J as nonzeroEpoch, K as decompressPayload, L as Sweeper, M as FlowReceiver, N as FlowSender, O as shapeOf, P as FrameQueue, Q as reliableRxSize, R as U32_MAX, S as isReset, T as legalShape, U as checkRecvSize, W as checkSendSize, X as nowMs, Y as noop, Z as rawPayload, _ as isData, a as FlagReset, at as cloneMetadata, b as isOpen, c as decodeEnvelope, ct as isBinaryKey, d as encodeFrame, dt as validateMetadataPair, et as tryAcquireBoth, f as frame, ft as DropPolicy, g as isCompressed, h as isClose, i as FlagPing, it as unpack, j as DEFAULT_STALL_MS, k as DEFAULT_MAX_RECV_MSG_SIZE, l as decodeFrame, lt as metadataJoin, m as hasUnknownFlags, mt as resolveRxConfig, n as FlagCompressed, nt as hasConnAttacher, o as FlagWindow, ot as decodeBase64, p as frameStatus, pt as resolveLimits, q as getCompressor, r as FlagOpen, rt as hasTransportInfo, s as SHAPE_MASK, st as encodeBase64, t as FlagClose, tt as unrefTimer, u as encodeEnvelope, ut as validateMetadata, v as isHalfClose, w as isWindow, x as isPing, y as isHeaderFrame, z as W_CONN } from "./wire-DqHx0oUs.mjs";
2
3
  import { a as isMessageTooLarge, i as abortCause, n as MessageTooLargeError, o as statusError, r as StatusError, s as toStatusError, t as Code } from "./status-DZwMDWIn.mjs";
3
- import { a as resolveTiming, c as RxWindow, i as statusDetails, l as TxSeq, n as Conn, o as K_LOUD, r as EndOfStreamError, s as RxVerdict, t as ClientStream, u as W_FWD } from "./conn-DOmx4nbt.mjs";
4
4
  //#region src/desc.ts
5
5
  function unaryMethod(path, init) {
6
6
  return {
@@ -60,6 +60,10 @@ function windowOf(f) {
60
60
  const w = f.window;
61
61
  return typeof w === "number" && Number.isFinite(w) && w > 0 ? Math.floor(w) : 0;
62
62
  }
63
+ function connWindowOf(f) {
64
+ const w = f.connWindow;
65
+ return typeof w === "number" && Number.isFinite(w) && w > 0 ? Math.min(Math.floor(w), U32_MAX) : 0;
66
+ }
63
67
  var PeerState = class {
64
68
  peer;
65
69
  epoch;
@@ -76,6 +80,7 @@ var PeerState = class {
76
80
  tombFloor = 0;
77
81
  calls = /* @__PURE__ */ new Map();
78
82
  liveCalls = 0;
83
+ connTx = new FlowSender();
79
84
  lastRx;
80
85
  lastTx;
81
86
  lastPing = 0;
@@ -156,6 +161,7 @@ var PeerSlot = class {
156
161
  replyBudget;
157
162
  resetAt = /* @__PURE__ */ new Map();
158
163
  pendingResets = /* @__PURE__ */ new Map();
164
+ flow;
159
165
  constructor(peer) {
160
166
  this.peer = peer;
161
167
  }
@@ -174,6 +180,7 @@ var Server = class {
174
180
  maxRecv;
175
181
  maxSend;
176
182
  stallMs;
183
+ /** @internal */ pstats;
177
184
  services = /* @__PURE__ */ new Map();
178
185
  serving = false;
179
186
  slots = /* @__PURE__ */ new Map();
@@ -186,6 +193,8 @@ var Server = class {
186
193
  idleWaiters = [];
187
194
  sawUnreliable = false;
188
195
  sw = new Sweeper();
196
+ unaryInt;
197
+ streamInt;
189
198
  constructor(tx, opts = {}) {
190
199
  this.epoch = nonzeroEpoch();
191
200
  this.tx = tx;
@@ -202,6 +211,19 @@ var Server = class {
202
211
  this.maxRecv = sizeOr(opts.maxRecvMsgSize, DEFAULT_MAX_RECV_MSG_SIZE);
203
212
  this.maxSend = sizeOr(opts.maxSendMsgSize, DEFAULT_MAX_SEND_MSG_SIZE);
204
213
  this.stallMs = opts.timing?.stallMs ?? 3e4;
214
+ this.pstats = statsSink(opts.protocolStats);
215
+ this.unaryInt = chain2(opts.unaryInterceptors);
216
+ this.streamInt = chain2(opts.streamInterceptors);
217
+ }
218
+ protoEvent(kind, peer, sid = 0) {
219
+ if (this.pstats.length === 0) return;
220
+ emit(this.pstats, {
221
+ kind,
222
+ peer,
223
+ sid,
224
+ method: "",
225
+ count: 0
226
+ });
205
227
  }
206
228
  register(desc, handler) {
207
229
  if (this.serving) throw new Error("drpc: register called after the server started serving");
@@ -217,6 +239,7 @@ var Server = class {
217
239
  this.serving = true;
218
240
  if (isReset(f)) {
219
241
  if (f.epoch !== this.epoch) return;
242
+ this.protoEvent("reset-received", ctx.peer, f.sid);
220
243
  this.resetByPeerSid(ctx.peer, f.sid, f.peerEpoch);
221
244
  return;
222
245
  }
@@ -235,6 +258,7 @@ var Server = class {
235
258
  if (ps.replayDue(tb, now, rti) && this.allowReply(slot, now)) {
236
259
  const replay = ps.replayTomb(tb, now, rti);
237
260
  if (replay !== void 0) {
261
+ this.protoEvent("tombstone-replay", peer, sid);
238
262
  await this.send(replay, ctx);
239
263
  return;
240
264
  }
@@ -244,6 +268,11 @@ var Server = class {
244
268
  }
245
269
  return this.sendReset(slot, f, ctx);
246
270
  }
271
+ if (sid === 0 && shapeOf(f) === 16) {
272
+ if (this.rxReliable(ctx)) if (ps !== void 0) ps.connTx.grant(windowOf(f));
273
+ else slot?.flow?.creditEvicted(f.epoch, windowOf(f));
274
+ return;
275
+ }
247
276
  const st = ps?.calls.get(sid);
248
277
  if (st !== void 0) {
249
278
  await st.handleRx(f, ctx);
@@ -257,7 +286,10 @@ var Server = class {
257
286
  const rti = this.mode.timing.retransmitMs;
258
287
  if (ps.replayDue(tb, now, rti) && this.allowReply(slot, now)) {
259
288
  const replay = ps.replayTomb(tb, now, rti);
260
- if (replay !== void 0) await this.send(replay, ctx);
289
+ if (replay !== void 0) {
290
+ this.protoEvent("tombstone-replay", peer, sid);
291
+ await this.send(replay, ctx);
292
+ }
261
293
  }
262
294
  return;
263
295
  }
@@ -272,7 +304,17 @@ var Server = class {
272
304
  }
273
305
  return this.open(ctx, f);
274
306
  }
275
- if (this.rxReliable(ctx)) return this.sendReset(slot, f, ctx);
307
+ if (this.rxReliable(ctx)) {
308
+ const pf = slot?.flow;
309
+ if (pf !== void 0 && isData(f)) if (ps !== void 0) {
310
+ const g = pf.unadmitted(f.epoch, 1);
311
+ if (g > 0) this.grantPeer(ps, g);
312
+ } else {
313
+ const g = pf.unadmittedEvicted(f.epoch, 1);
314
+ if (g > 0) this.grantEpoch(peer, f.epoch, g);
315
+ }
316
+ return this.sendReset(slot, f, ctx);
317
+ }
276
318
  const s = this.ensureSlot(peer);
277
319
  const k = eksid(f.epoch, sid);
278
320
  if (!s.pendingResets.has(k) && this.pendingResetTotal < this.limits.maxPendingResets) {
@@ -311,6 +353,7 @@ var Server = class {
311
353
  const now = nowMs();
312
354
  const slot = this.ensureSlot(ctx.peer);
313
355
  const ps = this.ensurePeer(slot, f.epoch, now, rel);
356
+ if (rel) this.observeAdvertisement(ps, f);
314
357
  if (slot.liveCalls >= this.limits.maxLiveCalls) return this.rejectOpen(ctx, f, 8, "too many concurrent calls");
315
358
  const cfg = this.methodRx.get(reg.desc.path) ?? this.rxCfg;
316
359
  const rxCfg = {
@@ -339,6 +382,7 @@ var Server = class {
339
382
  if (ps.hwm < f.sid) ps.hwm = f.sid;
340
383
  ps.dead = false;
341
384
  ps.lastRx = now;
385
+ if (rel) st.pf = this.ensurePeerFlow(slot);
342
386
  if (slot.pendingResets.delete(eksid(f.epoch, f.sid))) this.pendingResetTotal--;
343
387
  this.liveTasks++;
344
388
  this.kickSweep();
@@ -356,6 +400,7 @@ var Server = class {
356
400
  }
357
401
  }
358
402
  async rejectOpen(ctx, f, code, msg) {
403
+ const rel = this.rxReliable(ctx);
359
404
  const t = frame({
360
405
  epoch: this.epoch,
361
406
  sid: f.sid,
@@ -365,15 +410,19 @@ var Server = class {
365
410
  peerEpoch: f.epoch
366
411
  });
367
412
  t.code = code;
368
- if (!this.rxReliable(ctx)) {
369
- const now = nowMs();
370
- const slot = this.ensureSlot(ctx.peer);
371
- const ps = this.ensurePeer(slot, f.epoch, now, false);
372
- ps.lastRx = now;
413
+ this.advertise(t, rel);
414
+ const now = nowMs();
415
+ const slot = this.ensureSlot(ctx.peer);
416
+ const ps = this.ensurePeer(slot, f.epoch, now, rel);
417
+ ps.lastRx = now;
418
+ if (rel) {
419
+ this.observeAdvertisement(ps, f);
420
+ this.ensurePeerFlow(slot);
421
+ } else {
373
422
  if (ps.hwm < f.sid) ps.hwm = f.sid;
374
423
  ps.addTomb(f.sid, t, now + this.mode.timing.tombstoneMs);
375
- this.kickSweep();
376
424
  }
425
+ this.kickSweep();
377
426
  await this.send(t, ctx);
378
427
  }
379
428
  async runUnary(st, open) {
@@ -383,7 +432,9 @@ var Server = class {
383
432
  let resp;
384
433
  try {
385
434
  const req = await st.decodeMessage(open);
386
- resp = await st.reg.handler(req, st.context);
435
+ const h = async (r, c) => await st.reg.handler(r, c);
436
+ resp = this.unaryInt === void 0 ? await h(req, st.context) : await this.unaryInt(req, st.context, h);
437
+ if (resp === void 0) throw statusError(13, "unary handler produced no response");
387
438
  } catch (e) {
388
439
  err = toStatusError(e);
389
440
  }
@@ -408,14 +459,20 @@ var Server = class {
408
459
  const desc = st.reg.desc;
409
460
  let err;
410
461
  try {
411
- if (!desc.clientStreams) {
412
- const req = await st.recv();
413
- if (req === void 0) throw statusError(2, "missing request message");
414
- await st.reg.handler(req, st, st.context);
415
- } else if (!desc.serverStreams) {
416
- const resp = await st.reg.handler(st, st.context);
417
- st.setResponse(resp);
418
- } else await st.reg.handler(st, st.context);
462
+ const last = async (stream, ctx) => {
463
+ if (!desc.clientStreams) {
464
+ const req = await stream.recv();
465
+ if (req === void 0) throw statusError(2, "missing request message");
466
+ return st.reg.handler(req, stream, ctx);
467
+ }
468
+ if (!desc.serverStreams) return st.reg.handler(stream, ctx);
469
+ return st.reg.handler(stream, ctx);
470
+ };
471
+ const out = this.streamInt === void 0 ? await last(st, st.context) : await this.streamInt(st, st.context, last);
472
+ if (desc.clientStreams && !desc.serverStreams) {
473
+ if (out === void 0) throw statusError(13, "client-streaming handler produced no response");
474
+ st.setResponse(out);
475
+ }
419
476
  } catch (e) {
420
477
  err = toStatusError(e);
421
478
  }
@@ -445,6 +502,7 @@ var Server = class {
445
502
  }
446
503
  if (slot !== void 0 && slot.liveCalls > 0) slot.liveCalls--;
447
504
  st.flowTx.release();
505
+ st.releasePinned();
448
506
  st.cancel(statusError(1, "call finished"));
449
507
  if (st.deadlineTimer !== void 0) {
450
508
  clearTimeout(st.deadlineTimer);
@@ -482,6 +540,7 @@ var Server = class {
482
540
  this.sawUnreliable = true;
483
541
  this.kickSweep();
484
542
  }
543
+ this.protoEvent("reset-sent", ctx.peer, f.sid);
485
544
  await this.send(resetFor(f), ctx);
486
545
  }
487
546
  allowReply(slot, now) {
@@ -523,13 +582,18 @@ var Server = class {
523
582
  const slot = this.slots.get(peer);
524
583
  if (slot === void 0) return;
525
584
  const targets = [];
526
- for (const ps of slot.epochs.values()) for (const st of ps.calls.values()) targets.push(st);
585
+ const senders = [];
586
+ for (const ps of slot.epochs.values()) {
587
+ for (const st of ps.calls.values()) targets.push(st);
588
+ senders.push(ps.connTx);
589
+ }
527
590
  this.pendingResetTotal -= slot.pendingResets.size;
528
591
  this.resetAtTotal -= slot.resetAt.size;
529
592
  if (slot.replyBudget !== void 0) this.replyBudgetTotal--;
530
593
  this.slots.delete(peer);
531
594
  const cause = err === void 0 || err === null ? statusError(14, "transport closed") : statusError(14, `transport closed: ${err instanceof Error ? err.message : String(err)}`);
532
595
  for (const st of targets) st.cancel(cause);
596
+ for (const tx of senders) tx.release();
533
597
  }
534
598
  waitIdle() {
535
599
  if (this.liveTasks === 0) return Promise.resolve();
@@ -575,18 +639,54 @@ var Server = class {
575
639
  ensurePeer(slot, epoch, now, reliable) {
576
640
  let ps = slot.epochs.get(epoch);
577
641
  if (ps !== void 0) return ps;
642
+ let held;
643
+ if (reliable) held = slot.flow?.unstash(epoch);
578
644
  const dead = [];
579
645
  for (const p of slot.epochs.values()) if (p.liveCalls === 0) dead.push(p);
580
646
  if (dead.length >= this.limits.maxDeadPeers) {
581
647
  let oldest = dead[0];
582
648
  for (const p of dead) if (p.createdAt < oldest.createdAt) oldest = p;
649
+ if (oldest.reliable) this.ensurePeerFlow(slot).stash(oldest.epoch, oldest.connTx.state());
583
650
  slot.epochs.delete(oldest.epoch);
584
651
  }
585
652
  ps = new PeerState(slot.peer, epoch, reliable, now, this.limits.maxTombstones, this.limits.maxTombstoneBytes);
653
+ if (held !== void 0) ps.connTx.restore(held);
586
654
  slot.epochs.set(epoch, ps);
587
655
  if (!reliable) this.sawUnreliable = true;
588
656
  return ps;
589
657
  }
658
+ observeAdvertisement(ps, f) {
659
+ ps.connTx.observe(connWindowOf(f));
660
+ }
661
+ /** @internal */
662
+ advertise(f, reliable) {
663
+ if (reliable) f.connWindow = this.limits.maxPeerWindow;
664
+ }
665
+ ensurePeerFlow(slot) {
666
+ let pf = slot.flow;
667
+ if (pf === void 0) {
668
+ pf = new PeerFlowRx();
669
+ pf.enable(this.limits.maxPeerWindow, this.limits.maxDeadPeers);
670
+ slot.flow = pf;
671
+ }
672
+ return pf;
673
+ }
674
+ /** @internal */
675
+ grantPeer(ps, n) {
676
+ this.grantOn(ps.peer, ps.epoch, n);
677
+ }
678
+ grantEpoch(peer, epoch, n) {
679
+ this.grantOn(peer, epoch, n);
680
+ }
681
+ grantOn(peer, epoch, n) {
682
+ if (this.closed) return;
683
+ this.send(frame({
684
+ epoch: this.epoch,
685
+ peerEpoch: epoch,
686
+ flags: 16,
687
+ window: n
688
+ }), this.txFor(peer));
689
+ }
590
690
  /** @internal */
591
691
  kickSweep() {
592
692
  if (!this.sawUnreliable) return;
@@ -613,6 +713,7 @@ var Server = class {
613
713
  if (!this.allowReply(slot, now)) continue;
614
714
  slot.pendingResets.delete(k);
615
715
  this.pendingResetTotal--;
716
+ this.protoEvent("reset-sent", slot.peer, pr.sid);
616
717
  jobs.push({
617
718
  f: frame({
618
719
  flags: 4,
@@ -644,6 +745,7 @@ var Server = class {
644
745
  if (ps.liveCalls > 0 && !ps.dead) {
645
746
  if (now - ps.lastRx >= t.livenessMs) {
646
747
  ps.dead = true;
748
+ this.protoEvent("liveness-expired", slot.peer);
647
749
  for (const st of ps.calls.values()) {
648
750
  st.suppressTerm = true;
649
751
  lost.push(st);
@@ -656,6 +758,7 @@ var Server = class {
656
758
  } else if (now - ps.lastTx >= t.probeMs && now - ps.lastPing >= t.probeMs) {
657
759
  ps.lastPing = now;
658
760
  ps.lastTx = now;
761
+ this.protoEvent("keepalive-sent", slot.peer);
659
762
  jobs.push({
660
763
  f: frame({
661
764
  epoch: this.epoch,
@@ -668,20 +771,19 @@ var Server = class {
668
771
  }
669
772
  if (ps.liveCalls === 0 && ps.tombs.size === 0 && now - ps.lastRx > 2 * t.tombstoneMs) slot.epochs.delete(epoch);
670
773
  }
671
- for (const ps of slot.epochs.values()) {
672
- if (ps.reliable) continue;
673
- for (const st of ps.calls.values()) {
674
- const f = st.probeDue(now, t.probeMs, this.epoch);
675
- if (f !== void 0) {
676
- ps.lastTx = now;
677
- jobs.push({
678
- f,
679
- ctx: this.txFor(slot.peer)
680
- });
681
- }
774
+ for (const ps of slot.epochs.values()) for (const st of ps.calls.values()) {
775
+ if (st.reliable) continue;
776
+ const f = st.probeDue(now, t.probeMs, this.epoch);
777
+ if (f !== void 0) {
778
+ st.protoEvent("probe-sent");
779
+ ps.lastTx = now;
780
+ jobs.push({
781
+ f,
782
+ ctx: this.txFor(slot.peer)
783
+ });
682
784
  }
683
785
  }
684
- if (slot.epochs.size === 0 && slot.liveCalls === 0 && slot.pendingResets.size === 0 && slot.resetAt.size === 0 && slot.replyBudget === void 0) this.slots.delete(peerKey);
786
+ if (slot.epochs.size === 0 && slot.liveCalls === 0 && slot.pendingResets.size === 0 && slot.resetAt.size === 0 && slot.replyBudget === void 0 && slot.flow === void 0) this.slots.delete(peerKey);
685
787
  }
686
788
  const cause = statusError(14, "peer lost");
687
789
  for (const st of lost) st.cancel(cause);
@@ -701,6 +803,7 @@ var ServerStream = class {
701
803
  stallMs;
702
804
  context;
703
805
  /** @internal */ ps;
806
+ /** @internal */ pf;
704
807
  /** @internal */ slot;
705
808
  /** @internal */ deadlineAt;
706
809
  /** @internal */ deadlineTimer;
@@ -721,6 +824,8 @@ var ServerStream = class {
721
824
  lastProbe = 0;
722
825
  hReplayAt = 0;
723
826
  rxWin = new RxWindow();
827
+ pinned = 0;
828
+ rxReleased = false;
724
829
  /** @internal */ rxq;
725
830
  rxCfg;
726
831
  /** @internal */ rxDropped = 0;
@@ -757,6 +862,7 @@ var ServerStream = class {
757
862
  },
758
863
  peer,
759
864
  method: reg.desc.path,
865
+ desc: reg.desc,
760
866
  get deadline() {
761
867
  return self.deadlineAt;
762
868
  },
@@ -767,6 +873,7 @@ var ServerStream = class {
767
873
  try {
768
874
  validateMetadata(md);
769
875
  } catch {
876
+ this.protoEvent("off-shape", 1);
770
877
  return;
771
878
  }
772
879
  this.trailerMd = metadataJoin(this.trailerMd, md);
@@ -784,9 +891,22 @@ var ServerStream = class {
784
891
  this.endLatch.trip();
785
892
  }
786
893
  /** @internal */
894
+ protoEvent(kind, count = 0) {
895
+ const sink = this.server.pstats;
896
+ if (sink.length === 0) return;
897
+ emit(sink, {
898
+ kind,
899
+ peer: this.peer,
900
+ sid: this.sid,
901
+ method: this.reg.desc.path,
902
+ count
903
+ });
904
+ }
905
+ /** @internal */
787
906
  async handleRx(f, ctx) {
788
907
  if (hasUnknownFlags(f) || !legalShape(shapeOf(f))) {
789
908
  this.cancel(statusError(13, `drpc: frame carries unsupported flags 0x${f.flags.toString(16)}`));
909
+ this.creditUnbuffered(f);
790
910
  return;
791
911
  }
792
912
  if (shapeOf(f) === 16) {
@@ -806,12 +926,15 @@ var ServerStream = class {
806
926
  if (!isUnary(this.reg.desc) || this.hdrFrame !== void 0) this.replayH();
807
927
  return;
808
928
  }
809
- switch (this.rxWin.check(f.seq)) {
929
+ const v = this.rxWin.check(f.seq);
930
+ if (v !== 0) this.creditUnbuffered(f);
931
+ switch (v) {
810
932
  case 1:
811
933
  this.noteValidatedRx();
812
934
  return;
813
935
  case 2: return;
814
936
  case 3:
937
+ this.protoEvent("data-loss");
815
938
  this.cancel(statusError(15, "seq window overrun: >W_fwd consecutive frames lost"));
816
939
  return;
817
940
  case 4:
@@ -819,22 +942,38 @@ var ServerStream = class {
819
942
  return;
820
943
  case 0: break;
821
944
  }
945
+ const gap = this.rxWin.takeGap();
946
+ if (gap > 0) this.protoEvent("skipped", gap);
822
947
  this.noteValidatedRx();
823
948
  if (isTerminal(f)) this.cancel(frameStatus(f));
824
949
  else if (isHalfClose(f)) this.rxEOF.trip();
825
950
  else if (isData(f)) {
826
951
  if (isUnary(this.reg.desc) || !this.reg.desc.clientStreams) {
827
952
  this.rxDropped++;
953
+ this.creditUnbuffered(f);
828
954
  return;
829
955
  }
830
956
  if (this.reliable) {
957
+ if (this.pf !== void 0 && !this.pf.admit()) {
958
+ this.cancel(statusError(13, "drpc: peer exceeded the connection flow-control window"));
959
+ this.creditUnbuffered(f);
960
+ return;
961
+ }
962
+ if (!this.pin()) {
963
+ this.retirePeer(1);
964
+ return;
965
+ }
831
966
  if (this.flowRx.active) {
832
- if (!this.rxq.tryPut(f)) this.cancel(statusError(13, "drpc: peer exceeded the advertised flow-control window"));
967
+ if (!this.rxq.tryPut(f)) {
968
+ if (this.unpin()) this.retirePeer(1);
969
+ this.cancel(statusError(13, "drpc: peer exceeded the advertised flow-control window"));
970
+ }
833
971
  } else if (!await this.rxq.putBlocking(f, this.endLatch, ctx.signal)) {
972
+ if (this.unpin()) this.retirePeer(1);
834
973
  this.rxDropped++;
835
974
  this.cancel(statusError(14, "transport closed during delivery"));
836
975
  }
837
- } else this.rxq.putDrop(f, this.rxCfg.policy);
976
+ } else if (this.rxq.putDrop(f, this.rxCfg.policy) > 0) this.protoEvent("dropped", 1);
838
977
  } else this.rxDropped++;
839
978
  }
840
979
  noteValidatedRx() {
@@ -845,11 +984,7 @@ var ServerStream = class {
845
984
  async recv() {
846
985
  for (;;) {
847
986
  const f = this.rxq.tryTake();
848
- if (f !== void 0) {
849
- const msg = await this.decodeMessage(f);
850
- this.grantWindow(1);
851
- return msg;
852
- }
987
+ if (f !== void 0) return this.recvBuffered(f);
853
988
  if (this.rxEOF.tripped) return void 0;
854
989
  if (this.ctrl.signal.aborted) throw abortCause(this.ctrl.signal);
855
990
  await Promise.race([
@@ -859,6 +994,39 @@ var ServerStream = class {
859
994
  ]);
860
995
  }
861
996
  }
997
+ async recvBuffered(f) {
998
+ if (!isOpen(f) && this.unpin()) this.retirePeer(1);
999
+ const msg = await this.decodeMessage(f);
1000
+ this.grantWindow(1);
1001
+ return msg;
1002
+ }
1003
+ pin() {
1004
+ if (this.rxReleased) return false;
1005
+ this.pinned++;
1006
+ return true;
1007
+ }
1008
+ unpin() {
1009
+ if (this.pinned === 0) return false;
1010
+ this.pinned--;
1011
+ return true;
1012
+ }
1013
+ /** @internal */
1014
+ releasePinned() {
1015
+ const pinned = this.pinned;
1016
+ this.pinned = 0;
1017
+ this.rxReleased = true;
1018
+ this.retirePeer(pinned);
1019
+ }
1020
+ creditUnbuffered(f) {
1021
+ if (!this.reliable || this.pf === void 0 || this.ps === void 0 || !isData(f)) return;
1022
+ const g = this.pf.unadmitted(this.clientEpoch, 1);
1023
+ if (g > 0) this.server.grantPeer(this.ps, g);
1024
+ }
1025
+ retirePeer(n) {
1026
+ if (n === 0 || this.pf === void 0 || this.ps === void 0) return;
1027
+ const g = this.pf.retire(this.clientEpoch, n, true);
1028
+ if (g > 0) this.server.grantPeer(this.ps, g);
1029
+ }
862
1030
  async *[Symbol.asyncIterator]() {
863
1031
  for (;;) {
864
1032
  const m = await this.recv();
@@ -898,6 +1066,7 @@ var ServerStream = class {
898
1066
  sendH() {
899
1067
  const f = this.nextFrame();
900
1068
  if (this.reliable) f.window = this.rxCfg.size;
1069
+ this.server.advertise(f, this.reliable);
901
1070
  this.attachHeader(f, false);
902
1071
  if (this.hdrFrame === void 0) this.hdrFrame = f;
903
1072
  this.transmit(f).catch(noop);
@@ -924,6 +1093,7 @@ var ServerStream = class {
924
1093
  if (f === void 0) {
925
1094
  f = this.nextFrame();
926
1095
  if (this.reliable) f.window = this.rxCfg.size;
1096
+ this.server.advertise(f, this.reliable);
927
1097
  this.attachHeader(f, false);
928
1098
  }
929
1099
  this.transmit(f).catch(noop);
@@ -953,6 +1123,7 @@ var ServerStream = class {
953
1123
  this.hdrFlushed = true;
954
1124
  if (md !== void 0) this.txHeader = metadataJoin(this.txHeader, md);
955
1125
  const f = this.nextFrame();
1126
+ this.server.advertise(f, this.reliable);
956
1127
  this.attachHeader(f, true);
957
1128
  if (this.hdrFrame === void 0) this.hdrFrame = f;
958
1129
  try {
@@ -965,19 +1136,33 @@ var ServerStream = class {
965
1136
  async send(msg) {
966
1137
  const raw = this.resCodec.marshal(msg);
967
1138
  if (!this.reg.desc.serverStreams) throw statusError(13, "send on a non-server-streaming call");
968
- if (!this.flowTx.tryAcquire()) switch (await this.flowTx.acquire(this.endLatch, this.stallMs)) {
969
- case "ok": break;
970
- case "stalled": throw statusError(14, `drpc: flow-control stall: the peer granted no credit for ${this.stallMs}ms`);
971
- default: throw abortCause(this.ctrl.signal);
1139
+ if (this.ctrl.signal.aborted) throw abortCause(this.ctrl.signal);
1140
+ const connTx = this.reliable && this.ps !== void 0 ? this.ps.connTx : void 0;
1141
+ if (!tryAcquireBoth(this.flowTx, connTx)) {
1142
+ let peer = false;
1143
+ switch (await acquireBoth(this.flowTx, connTx, this.endLatch, this.stallMs, void 0, (p) => {
1144
+ peer = p;
1145
+ this.protoEvent(p ? "peer-flow-stall" : "flow-stall");
1146
+ })) {
1147
+ case "ok":
1148
+ this.protoEvent(peer ? "peer-flow-resume" : "flow-resume");
1149
+ break;
1150
+ case "stalled": throw statusError(14, `drpc: flow-control stall: the peer granted no credit for ${this.stallMs}ms`);
1151
+ case "peer-stalled": throw statusError(14, `drpc: flow-control stall: the peer granted no connection credit for ${this.stallMs}ms`);
1152
+ default: throw abortCause(this.ctrl.signal);
1153
+ }
972
1154
  }
973
1155
  let enc;
974
1156
  try {
975
1157
  enc = await this.encodePayload(raw);
976
1158
  } catch (e) {
977
- this.flowTx.undo();
1159
+ this.undoCredit();
978
1160
  throw e;
979
1161
  }
980
- if (this.ctrl.signal.aborted) throw abortCause(this.ctrl.signal);
1162
+ if (this.ctrl.signal.aborted) {
1163
+ this.undoCredit();
1164
+ throw abortCause(this.ctrl.signal);
1165
+ }
981
1166
  const f = this.nextFrame();
982
1167
  f.payload = enc.bytes;
983
1168
  if (enc.compressed) f.flags |= 32;
@@ -998,7 +1183,11 @@ var ServerStream = class {
998
1183
  undoRefused(f, err) {
999
1184
  if (!isMessageTooLarge(err)) return;
1000
1185
  this.txSeq.undo(f.seq);
1001
- if (isData(f)) this.flowTx.undo();
1186
+ if (isData(f)) this.undoCredit();
1187
+ }
1188
+ undoCredit() {
1189
+ this.flowTx.undo();
1190
+ if (this.reliable && this.ps !== void 0) this.ps.connTx.undo();
1002
1191
  }
1003
1192
  async encodePayload(payload) {
1004
1193
  const enc = this.comp === void 0 ? rawPayload(payload) : await compressPayload(this.comp, payload);
@@ -1029,6 +1218,7 @@ var ServerStream = class {
1029
1218
  }
1030
1219
  const f = this.nextFrame();
1031
1220
  f.flags = 2;
1221
+ this.server.advertise(f, this.reliable);
1032
1222
  if (this.txHeader !== void 0) {
1033
1223
  f.header = this.txHeader;
1034
1224
  this.hdrSent = true;
@@ -1076,4 +1266,4 @@ function illegalHeaderWrite() {
1076
1266
  return statusError(13, "drpc: sendHeader called multiple times");
1077
1267
  }
1078
1268
  //#endregion
1079
- export { ClientStream, Code, Conn, DEFAULT_MAX_RECV_MSG_SIZE, DEFAULT_MAX_SEND_MSG_SIZE, DEFAULT_STALL_MS, DropPolicy, EndOfStreamError, FlagClose, FlagCompressed, FlagOpen, FlagPing, FlagReset, FlagWindow, K_LOUD, MessageTooLargeError, RxVerdict, RxWindow, SHAPE_MASK, Server, StatusError, TxSeq, W_FWD, W_INIT, abortCause, bidiMethod, clientStreamingMethod, cloneMetadata, decodeBase64, decodeEnvelop, decodeFrame, encodeBase64, encodeEnvelop, encodeFrame, frame, frameStatus, getCompressor, hasConnAttacher, hasTransportInfo, hasUnknownFlags, isBinaryKey, isClose, isCompressed, isData, isHalfClose, isHeaderFrame, isMessageTooLarge, isOpen, isPing, isReset, isTerminal, isUnary, isWindow, legalShape, metadataJoin, resetFor, serverStreamingMethod, setFrameError, shapeOf, statusDetails, statusError, toStatusError, unaryMethod, unpack, validateMetadata, validateMetadataPair };
1269
+ export { ClientStream, Code, Conn, Counters, DEFAULT_MAX_RECV_MSG_SIZE, DEFAULT_MAX_SEND_MSG_SIZE, DEFAULT_STALL_MS, DropPolicy, EndOfStreamError, FlagClose, FlagCompressed, FlagOpen, FlagPing, FlagReset, FlagWindow, K_LOUD, MessageTooLargeError, RxVerdict, RxWindow, SHAPE_MASK, Server, StatusError, TxSeq, W_CONN, W_FWD, W_INIT, abortCause, bidiMethod, clientStreamingMethod, cloneMetadata, decodeBase64, decodeEnvelope, decodeFrame, encodeBase64, encodeEnvelope, encodeFrame, frame, frameStatus, getCompressor, hasConnAttacher, hasTransportInfo, hasUnknownFlags, isBinaryKey, isClose, isCompressed, isData, isHalfClose, isHeaderFrame, isMessageTooLarge, isOpen, isPing, isReset, isTerminal, isUnary, isWindow, legalShape, metadataJoin, resetFor, serverStreamingMethod, setFrameError, shapeOf, statusDetails, statusError, toStatusError, unaryMethod, unpack, validateMetadata, validateMetadataPair };