@lesomnus/grpc-dgram 0.0.1
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/LICENSE +202 -0
- package/README.md +245 -0
- package/dist/conn-DOmx4nbt.mjs +849 -0
- package/dist/conn-DTWG9vIx.d.mts +331 -0
- package/dist/desc-BMF2FBqk.d.mts +72 -0
- package/dist/index.d.mts +27 -0
- package/dist/index.mjs +1079 -0
- package/dist/protocol-K4Zy8MuQ.mjs +156 -0
- package/dist/server-xIr1mwqq.d.mts +112 -0
- package/dist/status-DZwMDWIn.mjs +63 -0
- package/dist/transport/connect.d.mts +6 -0
- package/dist/transport/connect.mjs +122 -0
- package/dist/transport/node-udp.d.mts +42 -0
- package/dist/transport/node-udp.mjs +144 -0
- package/dist/transport/port.d.mts +52 -0
- package/dist/transport/port.mjs +240 -0
- package/dist/transport/protobuf-es.d.mts +19 -0
- package/dist/transport/protobuf-es.mjs +27 -0
- package/dist/transport/webrtc.d.mts +56 -0
- package/dist/transport/webrtc.mjs +240 -0
- package/dist/transport/websocket.d.mts +59 -0
- package/dist/transport/websocket.mjs +324 -0
- package/dist/wasm/worker.d.mts +8 -0
- package/dist/wasm/worker.mjs +73 -0
- package/dist/wasm.d.mts +36 -0
- package/dist/wasm.mjs +156 -0
- package/dist/wire-BR8KiyRg.mjs +876 -0
- package/package.json +73 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1079 @@
|
|
|
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";
|
|
2
|
+
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
|
+
//#region src/desc.ts
|
|
5
|
+
function unaryMethod(path, init) {
|
|
6
|
+
return {
|
|
7
|
+
path,
|
|
8
|
+
clientStreams: false,
|
|
9
|
+
serverStreams: false,
|
|
10
|
+
...init
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function serverStreamingMethod(path, init) {
|
|
14
|
+
return {
|
|
15
|
+
path,
|
|
16
|
+
clientStreams: false,
|
|
17
|
+
serverStreams: true,
|
|
18
|
+
...init
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function clientStreamingMethod(path, init) {
|
|
22
|
+
return {
|
|
23
|
+
path,
|
|
24
|
+
clientStreams: true,
|
|
25
|
+
serverStreams: false,
|
|
26
|
+
...init
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function bidiMethod(path, init) {
|
|
30
|
+
return {
|
|
31
|
+
path,
|
|
32
|
+
clientStreams: true,
|
|
33
|
+
serverStreams: true,
|
|
34
|
+
...init
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function isUnary(desc) {
|
|
38
|
+
return !desc.clientStreams && !desc.serverStreams;
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/server.ts
|
|
42
|
+
const EMPTY = /* @__PURE__ */ new Uint8Array(0);
|
|
43
|
+
function detailsOf(err) {
|
|
44
|
+
if (!(err instanceof StatusError)) return void 0;
|
|
45
|
+
const d = err.details;
|
|
46
|
+
if (!Array.isArray(d) || d.length === 0) return void 0;
|
|
47
|
+
const out = [];
|
|
48
|
+
for (const e of d) {
|
|
49
|
+
if (e === null || typeof e !== "object") continue;
|
|
50
|
+
const a = e;
|
|
51
|
+
if (typeof a.typeUrl === "string" && a.value instanceof Uint8Array) out.push({
|
|
52
|
+
typeUrl: a.typeUrl,
|
|
53
|
+
value: a.value
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return out.length > 0 ? out : void 0;
|
|
57
|
+
}
|
|
58
|
+
const errText = (e) => e instanceof Error ? e.message : String(e);
|
|
59
|
+
function windowOf(f) {
|
|
60
|
+
const w = f.window;
|
|
61
|
+
return typeof w === "number" && Number.isFinite(w) && w > 0 ? Math.floor(w) : 0;
|
|
62
|
+
}
|
|
63
|
+
var PeerState = class {
|
|
64
|
+
peer;
|
|
65
|
+
epoch;
|
|
66
|
+
reliable;
|
|
67
|
+
createdAt;
|
|
68
|
+
maxTombs;
|
|
69
|
+
maxTombBytes;
|
|
70
|
+
hwm = 0;
|
|
71
|
+
cps = [];
|
|
72
|
+
dead = false;
|
|
73
|
+
tombs = /* @__PURE__ */ new Map();
|
|
74
|
+
tombOrder = [];
|
|
75
|
+
tombBytes = 0;
|
|
76
|
+
tombFloor = 0;
|
|
77
|
+
calls = /* @__PURE__ */ new Map();
|
|
78
|
+
liveCalls = 0;
|
|
79
|
+
lastRx;
|
|
80
|
+
lastTx;
|
|
81
|
+
lastPing = 0;
|
|
82
|
+
constructor(peer, epoch, reliable, createdAt, maxTombs, maxTombBytes) {
|
|
83
|
+
this.peer = peer;
|
|
84
|
+
this.epoch = epoch;
|
|
85
|
+
this.reliable = reliable;
|
|
86
|
+
this.createdAt = createdAt;
|
|
87
|
+
this.maxTombs = maxTombs;
|
|
88
|
+
this.maxTombBytes = maxTombBytes;
|
|
89
|
+
this.lastRx = createdAt;
|
|
90
|
+
this.lastTx = createdAt;
|
|
91
|
+
}
|
|
92
|
+
hwmAged(now, ttlMs) {
|
|
93
|
+
if (this.reliable) return this.hwm;
|
|
94
|
+
let aged = 0;
|
|
95
|
+
for (const cp of this.cps) if (now - cp.at >= ttlMs) aged = cp.hwm;
|
|
96
|
+
return aged;
|
|
97
|
+
}
|
|
98
|
+
addTomb(sid, term, expireAt) {
|
|
99
|
+
if (sid <= this.tombFloor) return;
|
|
100
|
+
const size = term?.payload?.length ?? 0;
|
|
101
|
+
const old = this.tombs.get(sid);
|
|
102
|
+
if (old !== void 0) {
|
|
103
|
+
this.tombBytes += size - old.size;
|
|
104
|
+
old.term = term;
|
|
105
|
+
old.size = size;
|
|
106
|
+
if (expireAt > old.expireAt) old.expireAt = expireAt;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
this.tombs.set(sid, {
|
|
110
|
+
sid,
|
|
111
|
+
term,
|
|
112
|
+
size,
|
|
113
|
+
expireAt,
|
|
114
|
+
lastReplay: 0
|
|
115
|
+
});
|
|
116
|
+
this.tombOrder.push(sid);
|
|
117
|
+
this.tombBytes += size;
|
|
118
|
+
for (let i = 0; this.tombBytes > this.maxTombBytes && i < this.tombOrder.length; i++) {
|
|
119
|
+
const tb = this.tombs.get(this.tombOrder[i]);
|
|
120
|
+
if (tb !== void 0 && tb.term !== void 0) {
|
|
121
|
+
this.tombBytes -= tb.size;
|
|
122
|
+
tb.term = void 0;
|
|
123
|
+
tb.size = 0;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
while (this.tombs.size > this.maxTombs) {
|
|
127
|
+
let lowest = 0;
|
|
128
|
+
for (const tsid of this.tombs.keys()) if (lowest === 0 || tsid < lowest) lowest = tsid;
|
|
129
|
+
const tb = this.tombs.get(lowest);
|
|
130
|
+
if (tb !== void 0) this.tombBytes -= tb.size;
|
|
131
|
+
this.tombs.delete(lowest);
|
|
132
|
+
if (this.tombFloor < lowest) this.tombFloor = lowest;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
removeTomb(sid) {
|
|
136
|
+
const tb = this.tombs.get(sid);
|
|
137
|
+
if (tb !== void 0) {
|
|
138
|
+
this.tombBytes -= tb.size;
|
|
139
|
+
this.tombs.delete(sid);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
replayDue(tb, now, rtiMs) {
|
|
143
|
+
return tb.term !== void 0 && now - tb.lastReplay >= rtiMs;
|
|
144
|
+
}
|
|
145
|
+
replayTomb(tb, now, rtiMs) {
|
|
146
|
+
if (tb.term === void 0 || now - tb.lastReplay < rtiMs) return void 0;
|
|
147
|
+
tb.lastReplay = now;
|
|
148
|
+
this.lastTx = now;
|
|
149
|
+
return tb.term;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
var PeerSlot = class {
|
|
153
|
+
peer;
|
|
154
|
+
epochs = /* @__PURE__ */ new Map();
|
|
155
|
+
liveCalls = 0;
|
|
156
|
+
replyBudget;
|
|
157
|
+
resetAt = /* @__PURE__ */ new Map();
|
|
158
|
+
pendingResets = /* @__PURE__ */ new Map();
|
|
159
|
+
constructor(peer) {
|
|
160
|
+
this.peer = peer;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
const eksid = (epoch, sid) => `${epoch}:${sid}`;
|
|
164
|
+
var Server = class {
|
|
165
|
+
epoch;
|
|
166
|
+
tx;
|
|
167
|
+
mode;
|
|
168
|
+
maxHandlerTimeoutMs;
|
|
169
|
+
rxCfg;
|
|
170
|
+
methodRx;
|
|
171
|
+
limits;
|
|
172
|
+
codecs;
|
|
173
|
+
compressors;
|
|
174
|
+
maxRecv;
|
|
175
|
+
maxSend;
|
|
176
|
+
stallMs;
|
|
177
|
+
services = /* @__PURE__ */ new Map();
|
|
178
|
+
serving = false;
|
|
179
|
+
slots = /* @__PURE__ */ new Map();
|
|
180
|
+
pendingResetTotal = 0;
|
|
181
|
+
resetAtTotal = 0;
|
|
182
|
+
replyBudgetTotal = 0;
|
|
183
|
+
drain = false;
|
|
184
|
+
closed = false;
|
|
185
|
+
liveTasks = 0;
|
|
186
|
+
idleWaiters = [];
|
|
187
|
+
sawUnreliable = false;
|
|
188
|
+
sw = new Sweeper();
|
|
189
|
+
constructor(tx, opts = {}) {
|
|
190
|
+
this.epoch = nonzeroEpoch();
|
|
191
|
+
this.tx = tx;
|
|
192
|
+
this.mode = {
|
|
193
|
+
reliable: opts.reliable ?? (hasTransportInfo(tx) ? tx.reliable() : false),
|
|
194
|
+
timing: resolveTiming(opts.timing)
|
|
195
|
+
};
|
|
196
|
+
this.maxHandlerTimeoutMs = opts.maxHandlerTimeoutMs ?? 0;
|
|
197
|
+
this.rxCfg = resolveRxConfig(opts.rxBuffer);
|
|
198
|
+
this.methodRx = new Map(Object.entries(opts.methodRxBuffer ?? {}).map(([k, v]) => [k, resolveRxConfig(v)]));
|
|
199
|
+
this.limits = resolveLimits(opts.limits);
|
|
200
|
+
this.codecs = new Map(Object.entries(opts.codecs ?? {}));
|
|
201
|
+
this.compressors = new Map(Object.entries(opts.compressors ?? {}));
|
|
202
|
+
this.maxRecv = sizeOr(opts.maxRecvMsgSize, DEFAULT_MAX_RECV_MSG_SIZE);
|
|
203
|
+
this.maxSend = sizeOr(opts.maxSendMsgSize, DEFAULT_MAX_SEND_MSG_SIZE);
|
|
204
|
+
this.stallMs = opts.timing?.stallMs ?? 3e4;
|
|
205
|
+
}
|
|
206
|
+
register(desc, handler) {
|
|
207
|
+
if (this.serving) throw new Error("drpc: register called after the server started serving");
|
|
208
|
+
this.services.set(desc.path, {
|
|
209
|
+
desc,
|
|
210
|
+
handler
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
rxReliable(ctx) {
|
|
214
|
+
return ctx.reliable ?? this.mode.reliable;
|
|
215
|
+
}
|
|
216
|
+
async handle(f, ctx = {}) {
|
|
217
|
+
this.serving = true;
|
|
218
|
+
if (isReset(f)) {
|
|
219
|
+
if (f.epoch !== this.epoch) return;
|
|
220
|
+
this.resetByPeerSid(ctx.peer, f.sid, f.peerEpoch);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const peer = ctx.peer;
|
|
224
|
+
const sid = f.sid;
|
|
225
|
+
const slot = this.slots.get(peer);
|
|
226
|
+
const ps = slot?.epochs.get(f.epoch);
|
|
227
|
+
const now = nowMs();
|
|
228
|
+
if (isPing(f)) {
|
|
229
|
+
if (ps !== void 0) ps.lastRx = now;
|
|
230
|
+
if (sid === 0) return;
|
|
231
|
+
if (ps?.calls.has(sid)) return;
|
|
232
|
+
const tb = ps?.tombs.get(sid);
|
|
233
|
+
if (ps !== void 0 && tb !== void 0) {
|
|
234
|
+
const rti = this.mode.timing.retransmitMs;
|
|
235
|
+
if (ps.replayDue(tb, now, rti) && this.allowReply(slot, now)) {
|
|
236
|
+
const replay = ps.replayTomb(tb, now, rti);
|
|
237
|
+
if (replay !== void 0) {
|
|
238
|
+
await this.send(replay, ctx);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (tb.term !== void 0) return;
|
|
243
|
+
return this.sendReset(slot, f, ctx);
|
|
244
|
+
}
|
|
245
|
+
return this.sendReset(slot, f, ctx);
|
|
246
|
+
}
|
|
247
|
+
const st = ps?.calls.get(sid);
|
|
248
|
+
if (st !== void 0) {
|
|
249
|
+
await st.handleRx(f, ctx);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (shapeOf(f) === 16) return;
|
|
253
|
+
if (ps !== void 0) {
|
|
254
|
+
const tb = ps.tombs.get(sid);
|
|
255
|
+
if (tb !== void 0) {
|
|
256
|
+
ps.lastRx = now;
|
|
257
|
+
const rti = this.mode.timing.retransmitMs;
|
|
258
|
+
if (ps.replayDue(tb, now, rti) && this.allowReply(slot, now)) {
|
|
259
|
+
const replay = ps.replayTomb(tb, now, rti);
|
|
260
|
+
if (replay !== void 0) await this.send(replay, ctx);
|
|
261
|
+
}
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (sid <= ps.tombFloor) {
|
|
265
|
+
ps.lastRx = now;
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (isOpen(f) && f.seq === 1) {
|
|
270
|
+
if (ps !== void 0) {
|
|
271
|
+
if (sid <= ps.hwmAged(now, this.mode.timing.tombstoneMs)) return this.sendReset(slot, f, ctx);
|
|
272
|
+
}
|
|
273
|
+
return this.open(ctx, f);
|
|
274
|
+
}
|
|
275
|
+
if (this.rxReliable(ctx)) return this.sendReset(slot, f, ctx);
|
|
276
|
+
const s = this.ensureSlot(peer);
|
|
277
|
+
const k = eksid(f.epoch, sid);
|
|
278
|
+
if (!s.pendingResets.has(k) && this.pendingResetTotal < this.limits.maxPendingResets) {
|
|
279
|
+
s.pendingResets.set(k, {
|
|
280
|
+
due: now + this.mode.timing.holdMs,
|
|
281
|
+
echo: f.epoch,
|
|
282
|
+
peerEcho: f.peerEpoch,
|
|
283
|
+
epoch: f.epoch,
|
|
284
|
+
sid
|
|
285
|
+
});
|
|
286
|
+
this.pendingResetTotal++;
|
|
287
|
+
this.sawUnreliable = true;
|
|
288
|
+
}
|
|
289
|
+
this.kickSweep();
|
|
290
|
+
}
|
|
291
|
+
async open(ctx, f) {
|
|
292
|
+
if (this.drain || this.closed) return this.sendReset(this.slots.get(ctx.peer), f, ctx);
|
|
293
|
+
const rel = this.rxReliable(ctx);
|
|
294
|
+
const reg = this.services.get(f.method);
|
|
295
|
+
if (reg === void 0) return this.rejectOpen(ctx, f, 12, "method not found");
|
|
296
|
+
let codec;
|
|
297
|
+
if (f.codec === "") codec = {
|
|
298
|
+
request: reg.desc.request,
|
|
299
|
+
response: reg.desc.response
|
|
300
|
+
};
|
|
301
|
+
else {
|
|
302
|
+
const named = this.codecs.get(f.codec);
|
|
303
|
+
if (named === void 0) return this.rejectOpen(ctx, f, 12, `unsupported codec: ${f.codec}`);
|
|
304
|
+
codec = named.resolve(reg.desc);
|
|
305
|
+
}
|
|
306
|
+
let comp;
|
|
307
|
+
if (f.compressor) {
|
|
308
|
+
comp = this.compressors.get(f.compressor);
|
|
309
|
+
if (comp === void 0) return this.rejectOpen(ctx, f, 12, `unsupported compressor: ${f.compressor}`);
|
|
310
|
+
}
|
|
311
|
+
const now = nowMs();
|
|
312
|
+
const slot = this.ensureSlot(ctx.peer);
|
|
313
|
+
const ps = this.ensurePeer(slot, f.epoch, now, rel);
|
|
314
|
+
if (slot.liveCalls >= this.limits.maxLiveCalls) return this.rejectOpen(ctx, f, 8, "too many concurrent calls");
|
|
315
|
+
const cfg = this.methodRx.get(reg.desc.path) ?? this.rxCfg;
|
|
316
|
+
const rxCfg = {
|
|
317
|
+
size: reliableRxSize(cfg.size, rel),
|
|
318
|
+
policy: cfg.policy
|
|
319
|
+
};
|
|
320
|
+
const st = new ServerStream(this, ctx.peer, f.epoch, f.sid, reg, codec, rxCfg, rel, comp, this.maxRecv, this.maxSend, this.stallMs);
|
|
321
|
+
st.ps = ps;
|
|
322
|
+
st.slot = slot;
|
|
323
|
+
if (rel) st.flowTx.observe(windowOf(f));
|
|
324
|
+
if (f.timeoutMs !== void 0) {
|
|
325
|
+
let d = f.timeoutMs;
|
|
326
|
+
if (this.maxHandlerTimeoutMs > 0 && d > this.maxHandlerTimeoutMs) d = this.maxHandlerTimeoutMs;
|
|
327
|
+
st.deadlineAt = now + d;
|
|
328
|
+
if (d <= 0) st.cancel(statusError(4, "call timeout"));
|
|
329
|
+
else {
|
|
330
|
+
const t = setTimeout(() => st.cancel(statusError(4, "call timeout")), d);
|
|
331
|
+
unrefTimer(t);
|
|
332
|
+
st.deadlineTimer = t;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
st.metadata = f.header;
|
|
336
|
+
ps.calls.set(f.sid, st);
|
|
337
|
+
slot.liveCalls++;
|
|
338
|
+
ps.liveCalls++;
|
|
339
|
+
if (ps.hwm < f.sid) ps.hwm = f.sid;
|
|
340
|
+
ps.dead = false;
|
|
341
|
+
ps.lastRx = now;
|
|
342
|
+
if (slot.pendingResets.delete(eksid(f.epoch, f.sid))) this.pendingResetTotal--;
|
|
343
|
+
this.liveTasks++;
|
|
344
|
+
this.kickSweep();
|
|
345
|
+
const desc = reg.desc;
|
|
346
|
+
if (isUnary(desc)) this.runUnary(st, f);
|
|
347
|
+
else if (!desc.clientStreams) {
|
|
348
|
+
if (f.payload !== void 0) st.rxq.tryPut(f);
|
|
349
|
+
if (isClose(f)) st.rxEOF.trip();
|
|
350
|
+
st.sendH();
|
|
351
|
+
this.runStream(st);
|
|
352
|
+
} else {
|
|
353
|
+
if (f.payload !== void 0 || isClose(f)) st.rxDropped++;
|
|
354
|
+
st.sendH();
|
|
355
|
+
this.runStream(st);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async rejectOpen(ctx, f, code, msg) {
|
|
359
|
+
const t = frame({
|
|
360
|
+
epoch: this.epoch,
|
|
361
|
+
sid: f.sid,
|
|
362
|
+
seq: 1,
|
|
363
|
+
flags: 2,
|
|
364
|
+
desc: msg,
|
|
365
|
+
peerEpoch: f.epoch
|
|
366
|
+
});
|
|
367
|
+
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;
|
|
373
|
+
if (ps.hwm < f.sid) ps.hwm = f.sid;
|
|
374
|
+
ps.addTomb(f.sid, t, now + this.mode.timing.tombstoneMs);
|
|
375
|
+
this.kickSweep();
|
|
376
|
+
}
|
|
377
|
+
await this.send(t, ctx);
|
|
378
|
+
}
|
|
379
|
+
async runUnary(st, open) {
|
|
380
|
+
let term;
|
|
381
|
+
try {
|
|
382
|
+
let err;
|
|
383
|
+
let resp;
|
|
384
|
+
try {
|
|
385
|
+
const req = await st.decodeMessage(open);
|
|
386
|
+
resp = await st.reg.handler(req, st.context);
|
|
387
|
+
} catch (e) {
|
|
388
|
+
err = toStatusError(e);
|
|
389
|
+
}
|
|
390
|
+
if (err === void 0 && st.signal.aborted) err = abortCause(st.signal);
|
|
391
|
+
if (err === void 0) try {
|
|
392
|
+
st.setResponse(resp);
|
|
393
|
+
} catch (e) {
|
|
394
|
+
err = statusError(13, `marshal response: ${errText(e)}`);
|
|
395
|
+
}
|
|
396
|
+
const t = await st.terminalFrame(err);
|
|
397
|
+
if (st.suppressTerm) return;
|
|
398
|
+
term = t;
|
|
399
|
+
st.transmitTerminal(t).catch(noop);
|
|
400
|
+
} finally {
|
|
401
|
+
this.finish(st, term);
|
|
402
|
+
this.taskDone();
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
async runStream(st) {
|
|
406
|
+
let term;
|
|
407
|
+
try {
|
|
408
|
+
const desc = st.reg.desc;
|
|
409
|
+
let err;
|
|
410
|
+
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);
|
|
419
|
+
} catch (e) {
|
|
420
|
+
err = toStatusError(e);
|
|
421
|
+
}
|
|
422
|
+
if (err === void 0 && st.signal.aborted) err = abortCause(st.signal);
|
|
423
|
+
const t = await st.terminalFrame(err);
|
|
424
|
+
if (st.suppressTerm) return;
|
|
425
|
+
term = t;
|
|
426
|
+
st.transmitTerminal(t).catch(noop);
|
|
427
|
+
} finally {
|
|
428
|
+
this.finish(st, term);
|
|
429
|
+
this.taskDone();
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
finish(st, term) {
|
|
433
|
+
const now = nowMs();
|
|
434
|
+
const slot = st.slot;
|
|
435
|
+
const ps = st.ps;
|
|
436
|
+
if (ps !== void 0) {
|
|
437
|
+
ps.calls.delete(st.sid);
|
|
438
|
+
ps.liveCalls--;
|
|
439
|
+
if (!st.reliable) {
|
|
440
|
+
let ttl = this.mode.timing.tombstoneMs;
|
|
441
|
+
if (st.deadlineAt !== void 0) ttl = Math.max(ttl, st.deadlineAt - now);
|
|
442
|
+
if (ps.dead) term = void 0;
|
|
443
|
+
ps.addTomb(st.sid, term, now + ttl);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (slot !== void 0 && slot.liveCalls > 0) slot.liveCalls--;
|
|
447
|
+
st.flowTx.release();
|
|
448
|
+
st.cancel(statusError(1, "call finished"));
|
|
449
|
+
if (st.deadlineTimer !== void 0) {
|
|
450
|
+
clearTimeout(st.deadlineTimer);
|
|
451
|
+
st.deadlineTimer = void 0;
|
|
452
|
+
}
|
|
453
|
+
this.kickSweep();
|
|
454
|
+
}
|
|
455
|
+
resetByPeerSid(peer, sid, peerEpoch) {
|
|
456
|
+
const slot = this.slots.get(peer);
|
|
457
|
+
if (slot === void 0) return;
|
|
458
|
+
const targets = [];
|
|
459
|
+
for (const [epoch, ps] of slot.epochs) {
|
|
460
|
+
if (peerEpoch !== 0 && epoch !== peerEpoch) continue;
|
|
461
|
+
const st = ps.calls.get(sid);
|
|
462
|
+
if (st !== void 0) targets.push(st);
|
|
463
|
+
}
|
|
464
|
+
const cause = statusError(14, "call reset by peer");
|
|
465
|
+
for (const st of targets) {
|
|
466
|
+
st.suppressTerm = true;
|
|
467
|
+
st.cancel(cause);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
async sendReset(slot, f, ctx) {
|
|
471
|
+
if (!this.rxReliable(ctx)) {
|
|
472
|
+
const n = nowMs();
|
|
473
|
+
const s = slot ?? this.ensureSlot(ctx.peer);
|
|
474
|
+
const k = eksid(f.epoch, f.sid);
|
|
475
|
+
const last = s.resetAt.get(k);
|
|
476
|
+
if (last !== void 0) {
|
|
477
|
+
if (n - last < this.mode.timing.retransmitMs) return;
|
|
478
|
+
} else if (this.resetAtTotal >= this.limits.maxPendingResets) return;
|
|
479
|
+
if (!this.allowReply(s, n)) return;
|
|
480
|
+
if (!s.resetAt.has(k)) this.resetAtTotal++;
|
|
481
|
+
s.resetAt.set(k, n);
|
|
482
|
+
this.sawUnreliable = true;
|
|
483
|
+
this.kickSweep();
|
|
484
|
+
}
|
|
485
|
+
await this.send(resetFor(f), ctx);
|
|
486
|
+
}
|
|
487
|
+
allowReply(slot, now) {
|
|
488
|
+
let b = slot.replyBudget;
|
|
489
|
+
if (b === void 0) {
|
|
490
|
+
if (this.replyBudgetTotal >= this.limits.maxPendingResets) return false;
|
|
491
|
+
b = {
|
|
492
|
+
windowStart: now,
|
|
493
|
+
n: 0
|
|
494
|
+
};
|
|
495
|
+
slot.replyBudget = b;
|
|
496
|
+
this.replyBudgetTotal++;
|
|
497
|
+
}
|
|
498
|
+
if (now - b.windowStart >= this.mode.timing.retransmitMs) {
|
|
499
|
+
b.windowStart = now;
|
|
500
|
+
b.n = 0;
|
|
501
|
+
}
|
|
502
|
+
if (b.n >= this.limits.maxRepliesPerRTI) return false;
|
|
503
|
+
b.n++;
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
async gracefulStop() {
|
|
507
|
+
this.drain = true;
|
|
508
|
+
await this.waitIdle();
|
|
509
|
+
this.closed = true;
|
|
510
|
+
this.sw.stop();
|
|
511
|
+
}
|
|
512
|
+
async stop() {
|
|
513
|
+
this.drain = true;
|
|
514
|
+
this.closed = true;
|
|
515
|
+
const targets = [];
|
|
516
|
+
for (const slot of this.slots.values()) for (const ps of slot.epochs.values()) for (const st of ps.calls.values()) targets.push(st);
|
|
517
|
+
const cause = statusError(14, "server stopped");
|
|
518
|
+
for (const st of targets) st.cancel(cause);
|
|
519
|
+
await this.waitIdle();
|
|
520
|
+
this.sw.stop();
|
|
521
|
+
}
|
|
522
|
+
disconnectPeer(peer, err) {
|
|
523
|
+
const slot = this.slots.get(peer);
|
|
524
|
+
if (slot === void 0) return;
|
|
525
|
+
const targets = [];
|
|
526
|
+
for (const ps of slot.epochs.values()) for (const st of ps.calls.values()) targets.push(st);
|
|
527
|
+
this.pendingResetTotal -= slot.pendingResets.size;
|
|
528
|
+
this.resetAtTotal -= slot.resetAt.size;
|
|
529
|
+
if (slot.replyBudget !== void 0) this.replyBudgetTotal--;
|
|
530
|
+
this.slots.delete(peer);
|
|
531
|
+
const cause = err === void 0 || err === null ? statusError(14, "transport closed") : statusError(14, `transport closed: ${err instanceof Error ? err.message : String(err)}`);
|
|
532
|
+
for (const st of targets) st.cancel(cause);
|
|
533
|
+
}
|
|
534
|
+
waitIdle() {
|
|
535
|
+
if (this.liveTasks === 0) return Promise.resolve();
|
|
536
|
+
return new Promise((res) => this.idleWaiters.push(res));
|
|
537
|
+
}
|
|
538
|
+
taskDone() {
|
|
539
|
+
this.liveTasks--;
|
|
540
|
+
if (this.liveTasks === 0) {
|
|
541
|
+
const ws = this.idleWaiters.splice(0);
|
|
542
|
+
for (const w of ws) w();
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
/** @internal */
|
|
546
|
+
get timing() {
|
|
547
|
+
return this.mode.timing;
|
|
548
|
+
}
|
|
549
|
+
/** @internal */
|
|
550
|
+
send(f, ctx) {
|
|
551
|
+
return Promise.resolve(this.tx.handle(f, ctx)).catch(noop);
|
|
552
|
+
}
|
|
553
|
+
/** @internal */
|
|
554
|
+
sendOrThrow(f, ctx) {
|
|
555
|
+
return Promise.resolve(this.tx.handle(f, ctx));
|
|
556
|
+
}
|
|
557
|
+
/** @internal */
|
|
558
|
+
txFor(peer) {
|
|
559
|
+
return peer === void 0 ? {} : { peer };
|
|
560
|
+
}
|
|
561
|
+
/** @internal */
|
|
562
|
+
allowReplyFor(peer, now) {
|
|
563
|
+
const slot = this.slots.get(peer);
|
|
564
|
+
if (slot === void 0) return false;
|
|
565
|
+
return this.allowReply(slot, now);
|
|
566
|
+
}
|
|
567
|
+
ensureSlot(peer) {
|
|
568
|
+
let slot = this.slots.get(peer);
|
|
569
|
+
if (slot === void 0) {
|
|
570
|
+
slot = new PeerSlot(peer);
|
|
571
|
+
this.slots.set(peer, slot);
|
|
572
|
+
}
|
|
573
|
+
return slot;
|
|
574
|
+
}
|
|
575
|
+
ensurePeer(slot, epoch, now, reliable) {
|
|
576
|
+
let ps = slot.epochs.get(epoch);
|
|
577
|
+
if (ps !== void 0) return ps;
|
|
578
|
+
const dead = [];
|
|
579
|
+
for (const p of slot.epochs.values()) if (p.liveCalls === 0) dead.push(p);
|
|
580
|
+
if (dead.length >= this.limits.maxDeadPeers) {
|
|
581
|
+
let oldest = dead[0];
|
|
582
|
+
for (const p of dead) if (p.createdAt < oldest.createdAt) oldest = p;
|
|
583
|
+
slot.epochs.delete(oldest.epoch);
|
|
584
|
+
}
|
|
585
|
+
ps = new PeerState(slot.peer, epoch, reliable, now, this.limits.maxTombstones, this.limits.maxTombstoneBytes);
|
|
586
|
+
slot.epochs.set(epoch, ps);
|
|
587
|
+
if (!reliable) this.sawUnreliable = true;
|
|
588
|
+
return ps;
|
|
589
|
+
}
|
|
590
|
+
/** @internal */
|
|
591
|
+
kickSweep() {
|
|
592
|
+
if (!this.sawUnreliable) return;
|
|
593
|
+
this.sw.kick(this.mode.timing.tickMs, () => this.sweep(nowMs()), () => this.hasWork());
|
|
594
|
+
}
|
|
595
|
+
hasWork() {
|
|
596
|
+
if (this.pendingResetTotal > 0 || this.resetAtTotal > 0 || this.replyBudgetTotal > 0) return true;
|
|
597
|
+
for (const slot of this.slots.values()) for (const ps of slot.epochs.values()) if (!ps.reliable) return true;
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
sweep(now) {
|
|
601
|
+
const t = this.mode.timing;
|
|
602
|
+
const jobs = [];
|
|
603
|
+
const lost = [];
|
|
604
|
+
for (const [peerKey, slot] of this.slots) {
|
|
605
|
+
for (const [k, pr] of slot.pendingResets) {
|
|
606
|
+
if (now < pr.due) continue;
|
|
607
|
+
const ps = slot.epochs.get(pr.epoch);
|
|
608
|
+
if (ps !== void 0 && (ps.calls.has(pr.sid) || ps.tombs.has(pr.sid))) {
|
|
609
|
+
slot.pendingResets.delete(k);
|
|
610
|
+
this.pendingResetTotal--;
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
if (!this.allowReply(slot, now)) continue;
|
|
614
|
+
slot.pendingResets.delete(k);
|
|
615
|
+
this.pendingResetTotal--;
|
|
616
|
+
jobs.push({
|
|
617
|
+
f: frame({
|
|
618
|
+
flags: 4,
|
|
619
|
+
epoch: pr.echo,
|
|
620
|
+
peerEpoch: pr.peerEcho,
|
|
621
|
+
sid: pr.sid
|
|
622
|
+
}),
|
|
623
|
+
ctx: this.txFor(slot.peer)
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
for (const [k, at] of slot.resetAt) if (now - at > t.tombstoneMs) {
|
|
627
|
+
slot.resetAt.delete(k);
|
|
628
|
+
this.resetAtTotal--;
|
|
629
|
+
}
|
|
630
|
+
if (slot.replyBudget !== void 0 && now - slot.replyBudget.windowStart > t.tombstoneMs) {
|
|
631
|
+
slot.replyBudget = void 0;
|
|
632
|
+
this.replyBudgetTotal--;
|
|
633
|
+
}
|
|
634
|
+
for (const [epoch, ps] of slot.epochs) {
|
|
635
|
+
if (ps.reliable) continue;
|
|
636
|
+
ps.cps.push({
|
|
637
|
+
at: now,
|
|
638
|
+
hwm: ps.hwm
|
|
639
|
+
});
|
|
640
|
+
while (ps.cps.length > 1 && now - ps.cps[1].at >= t.tombstoneMs) ps.cps.shift();
|
|
641
|
+
const aged = ps.hwmAged(now, t.tombstoneMs);
|
|
642
|
+
for (const [sid, tb] of ps.tombs) if (now > tb.expireAt && sid <= aged) ps.removeTomb(sid);
|
|
643
|
+
if (ps.tombOrder.length > 2 * ps.tombs.size + 16) ps.tombOrder = ps.tombOrder.filter((sid) => ps.tombs.has(sid));
|
|
644
|
+
if (ps.liveCalls > 0 && !ps.dead) {
|
|
645
|
+
if (now - ps.lastRx >= t.livenessMs) {
|
|
646
|
+
ps.dead = true;
|
|
647
|
+
for (const st of ps.calls.values()) {
|
|
648
|
+
st.suppressTerm = true;
|
|
649
|
+
lost.push(st);
|
|
650
|
+
}
|
|
651
|
+
for (const tb of ps.tombs.values()) {
|
|
652
|
+
ps.tombBytes -= tb.size;
|
|
653
|
+
tb.term = void 0;
|
|
654
|
+
tb.size = 0;
|
|
655
|
+
}
|
|
656
|
+
} else if (now - ps.lastTx >= t.probeMs && now - ps.lastPing >= t.probeMs) {
|
|
657
|
+
ps.lastPing = now;
|
|
658
|
+
ps.lastTx = now;
|
|
659
|
+
jobs.push({
|
|
660
|
+
f: frame({
|
|
661
|
+
epoch: this.epoch,
|
|
662
|
+
flags: 8,
|
|
663
|
+
peerEpoch: epoch
|
|
664
|
+
}),
|
|
665
|
+
ctx: this.txFor(slot.peer)
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
if (ps.liveCalls === 0 && ps.tombs.size === 0 && now - ps.lastRx > 2 * t.tombstoneMs) slot.epochs.delete(epoch);
|
|
670
|
+
}
|
|
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
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
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);
|
|
685
|
+
}
|
|
686
|
+
const cause = statusError(14, "peer lost");
|
|
687
|
+
for (const st of lost) st.cancel(cause);
|
|
688
|
+
for (const j of jobs) this.send(j.f, j.ctx);
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
var ServerStream = class {
|
|
692
|
+
server;
|
|
693
|
+
peer;
|
|
694
|
+
clientEpoch;
|
|
695
|
+
sid;
|
|
696
|
+
reg;
|
|
697
|
+
reliable;
|
|
698
|
+
comp;
|
|
699
|
+
maxRecv;
|
|
700
|
+
maxSend;
|
|
701
|
+
stallMs;
|
|
702
|
+
context;
|
|
703
|
+
/** @internal */ ps;
|
|
704
|
+
/** @internal */ slot;
|
|
705
|
+
/** @internal */ deadlineAt;
|
|
706
|
+
/** @internal */ deadlineTimer;
|
|
707
|
+
/** @internal */ metadata;
|
|
708
|
+
/** @internal */ suppressTerm = false;
|
|
709
|
+
/** @internal */ flowTx = new FlowSender();
|
|
710
|
+
flowRx = new FlowReceiver();
|
|
711
|
+
txSeq = new TxSeq();
|
|
712
|
+
/** @internal */ txHeader;
|
|
713
|
+
hdrSent = false;
|
|
714
|
+
hdrFlushed = false;
|
|
715
|
+
hdrFrame;
|
|
716
|
+
/** @internal */ trailerMd;
|
|
717
|
+
resp;
|
|
718
|
+
respSet = false;
|
|
719
|
+
lastRx;
|
|
720
|
+
lastTx;
|
|
721
|
+
lastProbe = 0;
|
|
722
|
+
hReplayAt = 0;
|
|
723
|
+
rxWin = new RxWindow();
|
|
724
|
+
/** @internal */ rxq;
|
|
725
|
+
rxCfg;
|
|
726
|
+
/** @internal */ rxDropped = 0;
|
|
727
|
+
/** @internal */ rxEOF = new Latch();
|
|
728
|
+
ctrl = new AbortController();
|
|
729
|
+
endLatch = new Latch();
|
|
730
|
+
/** @internal */
|
|
731
|
+
constructor(server, peer, clientEpoch, sid, reg, codec, rxCfg, reliable, comp, maxRecv, maxSend, stallMs) {
|
|
732
|
+
this.server = server;
|
|
733
|
+
this.peer = peer;
|
|
734
|
+
this.clientEpoch = clientEpoch;
|
|
735
|
+
this.sid = sid;
|
|
736
|
+
this.reg = reg;
|
|
737
|
+
this.reliable = reliable;
|
|
738
|
+
this.comp = comp;
|
|
739
|
+
this.maxRecv = maxRecv;
|
|
740
|
+
this.maxSend = maxSend;
|
|
741
|
+
this.stallMs = stallMs;
|
|
742
|
+
this.reqCodec = codec.request;
|
|
743
|
+
this.resCodec = codec.response;
|
|
744
|
+
this.rxCfg = rxCfg;
|
|
745
|
+
this.rxq = new FrameQueue(rxCfg.size);
|
|
746
|
+
this.rxWin.l = 1;
|
|
747
|
+
this.rxWin.strict = reliable;
|
|
748
|
+
if (reliable) this.flowRx.enable(rxCfg.size);
|
|
749
|
+
const n = nowMs();
|
|
750
|
+
this.lastRx = n;
|
|
751
|
+
this.lastTx = n;
|
|
752
|
+
const self = this;
|
|
753
|
+
this.context = {
|
|
754
|
+
signal: this.ctrl.signal,
|
|
755
|
+
get metadata() {
|
|
756
|
+
return self.metadata;
|
|
757
|
+
},
|
|
758
|
+
peer,
|
|
759
|
+
method: reg.desc.path,
|
|
760
|
+
get deadline() {
|
|
761
|
+
return self.deadlineAt;
|
|
762
|
+
},
|
|
763
|
+
setHeader: (md) => this.setHeader(md),
|
|
764
|
+
sendHeader: (md) => this.sendHeader(md),
|
|
765
|
+
setTrailer: (md) => {
|
|
766
|
+
if (Object.keys(md).length === 0) return;
|
|
767
|
+
try {
|
|
768
|
+
validateMetadata(md);
|
|
769
|
+
} catch {
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
this.trailerMd = metadataJoin(this.trailerMd, md);
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
/** @internal */ reqCodec;
|
|
777
|
+
/** @internal */ resCodec;
|
|
778
|
+
get signal() {
|
|
779
|
+
return this.ctrl.signal;
|
|
780
|
+
}
|
|
781
|
+
/** @internal */
|
|
782
|
+
cancel(cause) {
|
|
783
|
+
this.ctrl.abort(cause);
|
|
784
|
+
this.endLatch.trip();
|
|
785
|
+
}
|
|
786
|
+
/** @internal */
|
|
787
|
+
async handleRx(f, ctx) {
|
|
788
|
+
if (hasUnknownFlags(f) || !legalShape(shapeOf(f))) {
|
|
789
|
+
this.cancel(statusError(13, `drpc: frame carries unsupported flags 0x${f.flags.toString(16)}`));
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
if (shapeOf(f) === 16) {
|
|
793
|
+
if (this.reliable) this.flowTx.grant(windowOf(f));
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
if (isOpen(f)) {
|
|
797
|
+
if (f.seq !== 1) {
|
|
798
|
+
this.rxDropped++;
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
if (this.reliable) {
|
|
802
|
+
this.cancel(statusError(13, "reliable transport lost or reordered a frame"));
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
this.noteValidatedRx();
|
|
806
|
+
if (!isUnary(this.reg.desc) || this.hdrFrame !== void 0) this.replayH();
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
switch (this.rxWin.check(f.seq)) {
|
|
810
|
+
case 1:
|
|
811
|
+
this.noteValidatedRx();
|
|
812
|
+
return;
|
|
813
|
+
case 2: return;
|
|
814
|
+
case 3:
|
|
815
|
+
this.cancel(statusError(15, "seq window overrun: >W_fwd consecutive frames lost"));
|
|
816
|
+
return;
|
|
817
|
+
case 4:
|
|
818
|
+
this.cancel(statusError(13, "reliable transport lost or reordered a frame"));
|
|
819
|
+
return;
|
|
820
|
+
case 0: break;
|
|
821
|
+
}
|
|
822
|
+
this.noteValidatedRx();
|
|
823
|
+
if (isTerminal(f)) this.cancel(frameStatus(f));
|
|
824
|
+
else if (isHalfClose(f)) this.rxEOF.trip();
|
|
825
|
+
else if (isData(f)) {
|
|
826
|
+
if (isUnary(this.reg.desc) || !this.reg.desc.clientStreams) {
|
|
827
|
+
this.rxDropped++;
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
if (this.reliable) {
|
|
831
|
+
if (this.flowRx.active) {
|
|
832
|
+
if (!this.rxq.tryPut(f)) this.cancel(statusError(13, "drpc: peer exceeded the advertised flow-control window"));
|
|
833
|
+
} else if (!await this.rxq.putBlocking(f, this.endLatch, ctx.signal)) {
|
|
834
|
+
this.rxDropped++;
|
|
835
|
+
this.cancel(statusError(14, "transport closed during delivery"));
|
|
836
|
+
}
|
|
837
|
+
} else this.rxq.putDrop(f, this.rxCfg.policy);
|
|
838
|
+
} else this.rxDropped++;
|
|
839
|
+
}
|
|
840
|
+
noteValidatedRx() {
|
|
841
|
+
const n = nowMs();
|
|
842
|
+
this.lastRx = n;
|
|
843
|
+
if (this.ps !== void 0) this.ps.lastRx = n;
|
|
844
|
+
}
|
|
845
|
+
async recv() {
|
|
846
|
+
for (;;) {
|
|
847
|
+
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
|
+
}
|
|
853
|
+
if (this.rxEOF.tripped) return void 0;
|
|
854
|
+
if (this.ctrl.signal.aborted) throw abortCause(this.ctrl.signal);
|
|
855
|
+
await Promise.race([
|
|
856
|
+
this.rxq.readable(),
|
|
857
|
+
this.rxEOF.wait(),
|
|
858
|
+
this.endLatch.wait()
|
|
859
|
+
]);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
async *[Symbol.asyncIterator]() {
|
|
863
|
+
for (;;) {
|
|
864
|
+
const m = await this.recv();
|
|
865
|
+
if (m === void 0) return;
|
|
866
|
+
yield m;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
/** @internal */
|
|
870
|
+
transmit(f) {
|
|
871
|
+
const n = nowMs();
|
|
872
|
+
this.lastTx = n;
|
|
873
|
+
if (this.ps !== void 0) this.ps.lastTx = n;
|
|
874
|
+
return this.server.send(f, this.server.txFor(this.peer));
|
|
875
|
+
}
|
|
876
|
+
nextFrame() {
|
|
877
|
+
const f = frame({
|
|
878
|
+
epoch: this.server.epoch,
|
|
879
|
+
sid: this.sid,
|
|
880
|
+
seq: this.txSeq.next()
|
|
881
|
+
});
|
|
882
|
+
f.peerEpoch = this.clientEpoch;
|
|
883
|
+
return f;
|
|
884
|
+
}
|
|
885
|
+
attachHeader(f, force) {
|
|
886
|
+
if (this.hdrSent) return;
|
|
887
|
+
if (this.txHeader !== void 0) {
|
|
888
|
+
f.header = this.txHeader;
|
|
889
|
+
this.hdrSent = true;
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (force) {
|
|
893
|
+
f.header = {};
|
|
894
|
+
this.hdrSent = true;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
/** @internal */
|
|
898
|
+
sendH() {
|
|
899
|
+
const f = this.nextFrame();
|
|
900
|
+
if (this.reliable) f.window = this.rxCfg.size;
|
|
901
|
+
this.attachHeader(f, false);
|
|
902
|
+
if (this.hdrFrame === void 0) this.hdrFrame = f;
|
|
903
|
+
this.transmit(f).catch(noop);
|
|
904
|
+
}
|
|
905
|
+
grantWindow(n) {
|
|
906
|
+
if (this.ctrl.signal.aborted) return;
|
|
907
|
+
const g = this.flowRx.consumed(n);
|
|
908
|
+
if (g === 0) return;
|
|
909
|
+
const f = frame({
|
|
910
|
+
epoch: this.server.epoch,
|
|
911
|
+
sid: this.sid,
|
|
912
|
+
peerEpoch: this.clientEpoch,
|
|
913
|
+
flags: 16,
|
|
914
|
+
window: g
|
|
915
|
+
});
|
|
916
|
+
this.transmit(f).catch(noop);
|
|
917
|
+
}
|
|
918
|
+
replayH() {
|
|
919
|
+
const n = nowMs();
|
|
920
|
+
if (n - this.hReplayAt < this.server.timing.retransmitMs) return;
|
|
921
|
+
this.hReplayAt = n;
|
|
922
|
+
if (!this.server.allowReplyFor(this.peer, n)) return;
|
|
923
|
+
let f = this.hdrFrame;
|
|
924
|
+
if (f === void 0) {
|
|
925
|
+
f = this.nextFrame();
|
|
926
|
+
if (this.reliable) f.window = this.rxCfg.size;
|
|
927
|
+
this.attachHeader(f, false);
|
|
928
|
+
}
|
|
929
|
+
this.transmit(f).catch(noop);
|
|
930
|
+
}
|
|
931
|
+
/** @internal */
|
|
932
|
+
probeDue(now, probeMs, epoch) {
|
|
933
|
+
if (now - this.lastRx < probeMs || now - this.lastTx < probeMs || now - this.lastProbe < probeMs) return;
|
|
934
|
+
this.lastProbe = now;
|
|
935
|
+
return frame({
|
|
936
|
+
epoch,
|
|
937
|
+
sid: this.sid,
|
|
938
|
+
flags: 8,
|
|
939
|
+
peerEpoch: this.clientEpoch
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
/** @internal */
|
|
943
|
+
setHeader(md) {
|
|
944
|
+
if (Object.keys(md).length === 0) return;
|
|
945
|
+
validateMetadata(md);
|
|
946
|
+
if (this.hdrFlushed || this.hdrSent) throw illegalHeaderWrite();
|
|
947
|
+
this.txHeader = metadataJoin(this.txHeader, md);
|
|
948
|
+
}
|
|
949
|
+
/** @internal */
|
|
950
|
+
async sendHeader(md) {
|
|
951
|
+
if (md !== void 0) validateMetadata(md);
|
|
952
|
+
if (this.hdrFlushed || this.hdrSent) throw illegalHeaderWrite();
|
|
953
|
+
this.hdrFlushed = true;
|
|
954
|
+
if (md !== void 0) this.txHeader = metadataJoin(this.txHeader, md);
|
|
955
|
+
const f = this.nextFrame();
|
|
956
|
+
this.attachHeader(f, true);
|
|
957
|
+
if (this.hdrFrame === void 0) this.hdrFrame = f;
|
|
958
|
+
try {
|
|
959
|
+
await this.transmitOrThrow(f);
|
|
960
|
+
} catch (e) {
|
|
961
|
+
this.undoRefused(f, e);
|
|
962
|
+
throw toStatusError(e);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
async send(msg) {
|
|
966
|
+
const raw = this.resCodec.marshal(msg);
|
|
967
|
+
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);
|
|
972
|
+
}
|
|
973
|
+
let enc;
|
|
974
|
+
try {
|
|
975
|
+
enc = await this.encodePayload(raw);
|
|
976
|
+
} catch (e) {
|
|
977
|
+
this.flowTx.undo();
|
|
978
|
+
throw e;
|
|
979
|
+
}
|
|
980
|
+
if (this.ctrl.signal.aborted) throw abortCause(this.ctrl.signal);
|
|
981
|
+
const f = this.nextFrame();
|
|
982
|
+
f.payload = enc.bytes;
|
|
983
|
+
if (enc.compressed) f.flags |= 32;
|
|
984
|
+
this.attachHeader(f, false);
|
|
985
|
+
try {
|
|
986
|
+
await this.transmitOrThrow(f);
|
|
987
|
+
} catch (e) {
|
|
988
|
+
this.undoRefused(f, e);
|
|
989
|
+
throw e;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
transmitOrThrow(f) {
|
|
993
|
+
const n = nowMs();
|
|
994
|
+
this.lastTx = n;
|
|
995
|
+
if (this.ps !== void 0) this.ps.lastTx = n;
|
|
996
|
+
return this.server.sendOrThrow(f, this.server.txFor(this.peer));
|
|
997
|
+
}
|
|
998
|
+
undoRefused(f, err) {
|
|
999
|
+
if (!isMessageTooLarge(err)) return;
|
|
1000
|
+
this.txSeq.undo(f.seq);
|
|
1001
|
+
if (isData(f)) this.flowTx.undo();
|
|
1002
|
+
}
|
|
1003
|
+
async encodePayload(payload) {
|
|
1004
|
+
const enc = this.comp === void 0 ? rawPayload(payload) : await compressPayload(this.comp, payload);
|
|
1005
|
+
checkSendSize(enc.bytes.length, this.maxSend);
|
|
1006
|
+
return enc;
|
|
1007
|
+
}
|
|
1008
|
+
/** @internal */
|
|
1009
|
+
async decodeMessage(f) {
|
|
1010
|
+
const raw = f.payload ?? EMPTY;
|
|
1011
|
+
const payload = isCompressed(f) ? await decompressPayload(this.comp, raw, this.maxRecv) : raw;
|
|
1012
|
+
checkRecvSize(payload.length, this.maxRecv);
|
|
1013
|
+
return this.reqCodec.unmarshal(payload);
|
|
1014
|
+
}
|
|
1015
|
+
/** @internal */
|
|
1016
|
+
setResponse(resp) {
|
|
1017
|
+
if (this.respSet) throw statusError(13, "response already set");
|
|
1018
|
+
this.resp = this.resCodec.marshal(resp);
|
|
1019
|
+
this.respSet = true;
|
|
1020
|
+
}
|
|
1021
|
+
/** @internal */
|
|
1022
|
+
async terminalFrame(err) {
|
|
1023
|
+
let enc;
|
|
1024
|
+
let st = err;
|
|
1025
|
+
if (st === void 0 && this.respSet) try {
|
|
1026
|
+
enc = await this.encodePayload(this.resp ?? EMPTY);
|
|
1027
|
+
} catch (e) {
|
|
1028
|
+
st = toStatusError(e);
|
|
1029
|
+
}
|
|
1030
|
+
const f = this.nextFrame();
|
|
1031
|
+
f.flags = 2;
|
|
1032
|
+
if (this.txHeader !== void 0) {
|
|
1033
|
+
f.header = this.txHeader;
|
|
1034
|
+
this.hdrSent = true;
|
|
1035
|
+
}
|
|
1036
|
+
if (this.trailerMd !== void 0) f.trailer = this.trailerMd;
|
|
1037
|
+
if (st !== void 0) {
|
|
1038
|
+
setFrameError(f, st);
|
|
1039
|
+
f.details = detailsOf(st);
|
|
1040
|
+
return f;
|
|
1041
|
+
}
|
|
1042
|
+
if (enc !== void 0) {
|
|
1043
|
+
f.payload = enc.bytes;
|
|
1044
|
+
if (enc.compressed) f.flags |= 32;
|
|
1045
|
+
}
|
|
1046
|
+
f.code = 0;
|
|
1047
|
+
return f;
|
|
1048
|
+
}
|
|
1049
|
+
/** @internal */
|
|
1050
|
+
async transmitTerminal(f) {
|
|
1051
|
+
let err = await this.tryTransmit(f);
|
|
1052
|
+
if (err === void 0) return;
|
|
1053
|
+
if (f.details !== void 0 && f.details.length > 0) {
|
|
1054
|
+
f.details = void 0;
|
|
1055
|
+
const e2 = await this.tryTransmit(f);
|
|
1056
|
+
if (e2 === void 0) return;
|
|
1057
|
+
err = e2;
|
|
1058
|
+
}
|
|
1059
|
+
if (f.payload === void 0 && f.code === 8) return;
|
|
1060
|
+
f.payload = void 0;
|
|
1061
|
+
f.flags &= -33;
|
|
1062
|
+
f.details = void 0;
|
|
1063
|
+
setFrameError(f, statusError(8, `drpc: the terminal frame does not fit the transport: ${errText(err)}`));
|
|
1064
|
+
await this.tryTransmit(f);
|
|
1065
|
+
}
|
|
1066
|
+
async tryTransmit(f) {
|
|
1067
|
+
try {
|
|
1068
|
+
await this.transmitOrThrow(f);
|
|
1069
|
+
return;
|
|
1070
|
+
} catch (e) {
|
|
1071
|
+
return isMessageTooLarge(e) ? e : void 0;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
function illegalHeaderWrite() {
|
|
1076
|
+
return statusError(13, "drpc: sendHeader called multiple times");
|
|
1077
|
+
}
|
|
1078
|
+
//#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 };
|