@promigence/sdk 0.1.0-beta.12 → 0.1.0-beta.14
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/caller.js +10 -0
- package/dist/cjs/caller.js +10 -0
- package/dist/cjs/client.d.ts +16 -6
- package/dist/cjs/client.js +1180 -1129
- package/dist/cjs/core.d.ts +1 -1
- package/dist/cjs/core.js +1 -1
- package/dist/cjs/dispatcher.d.ts +2 -0
- package/dist/cjs/dispatcher.js +5 -0
- package/dist/cjs/http2.d.ts +11 -0
- package/dist/cjs/http2.js +335 -71
- package/dist/cjs/keyed.d.ts +6 -0
- package/dist/cjs/keyed.js +59 -0
- package/dist/cjs/proxy.js +11 -0
- package/dist/cjs/sandbox.d.ts +3 -1
- package/dist/cjs/sandbox.js +33 -20
- package/dist/cjs/trace.d.ts +2 -0
- package/dist/cjs/trace.js +17 -0
- package/dist/cjs/transport-worker.js +83 -14
- package/dist/client.d.ts +16 -6
- package/dist/client.js +1179 -1127
- package/dist/core.d.ts +1 -1
- package/dist/core.js +1 -1
- package/dist/dispatcher.d.ts +2 -0
- package/dist/dispatcher.js +5 -0
- package/dist/http2.d.ts +11 -0
- package/dist/http2.js +335 -72
- package/dist/keyed.d.ts +6 -0
- package/dist/keyed.js +55 -0
- package/dist/proxy.js +11 -0
- package/dist/sandbox.d.ts +3 -1
- package/dist/sandbox.js +33 -20
- package/dist/trace.d.ts +2 -0
- package/dist/trace.js +16 -0
- package/dist/transport-worker.js +83 -14
- package/package.json +1 -1
package/dist/http2.js
CHANGED
|
@@ -6,11 +6,12 @@ import * as zlib from "node:zlib";
|
|
|
6
6
|
import { __caGuardInternals, guardCertChain } from "./ca-guard.js";
|
|
7
7
|
import { DEFAULT_API_URL } from "./core.js";
|
|
8
8
|
import { customDispatcher } from "./dispatcher.js";
|
|
9
|
-
import { mark, TRACE_ON, traceEvent, traceEventAt } from "./trace.js";
|
|
9
|
+
import { mark, onTraceFlush, TRACE_ON, traceEvent, traceEventAt } from "./trace.js";
|
|
10
10
|
const MAX_SESSIONS = 4;
|
|
11
11
|
const FIRST_SESSIONS = 2;
|
|
12
12
|
const BURST_MIN = 8;
|
|
13
13
|
const HOLD_MS = 10;
|
|
14
|
+
const STANDBY_AFTER_MS = 300;
|
|
14
15
|
const LOAD_FIRST_USE_MS = 120_000;
|
|
15
16
|
const CONNECT_TIMEOUT_MS = 10_000;
|
|
16
17
|
const IDLE_CLOSE_MS = 10_000;
|
|
@@ -561,6 +562,7 @@ export const tuning = {
|
|
|
561
562
|
holdMs: HOLD_MS,
|
|
562
563
|
burstMin: BURST_MIN,
|
|
563
564
|
loadFirstUseMs: LOAD_FIRST_USE_MS,
|
|
565
|
+
standbyAfterMs: STANDBY_AFTER_MS,
|
|
564
566
|
earlyPlatform: undefined,
|
|
565
567
|
earlyServers: undefined,
|
|
566
568
|
earlyHostsFile: undefined,
|
|
@@ -844,19 +846,21 @@ function apiLookup(asked, observe, ipv4First = v4First(), earlyOk = true) {
|
|
|
844
846
|
const earlies = new Map();
|
|
845
847
|
const MAX_EARLIES = 4;
|
|
846
848
|
const EARLY_MAX_AGE_MS = 60_000;
|
|
847
|
-
const
|
|
848
|
-
const
|
|
849
|
+
const LOOKUP_FIRST_MS = 150;
|
|
850
|
+
const LOOKUP_TRIES = 3;
|
|
851
|
+
const LOOKUP_AT_MS = [0, 100, 250];
|
|
852
|
+
const DNS_AT_LOAD = (() => {
|
|
849
853
|
try {
|
|
850
|
-
return nodeDns()
|
|
854
|
+
return nodeDns();
|
|
851
855
|
}
|
|
852
856
|
catch {
|
|
853
857
|
return undefined;
|
|
854
858
|
}
|
|
855
859
|
})();
|
|
860
|
+
const LOOKUP_AT_LOAD = DNS_AT_LOAD?.lookup;
|
|
856
861
|
function lookupReplaced() {
|
|
857
862
|
try {
|
|
858
|
-
|
|
859
|
-
return d !== undefined && d.lookup !== LOOKUP_AT_LOAD;
|
|
863
|
+
return DNS_AT_LOAD !== undefined && DNS_AT_LOAD.lookup !== LOOKUP_AT_LOAD;
|
|
860
864
|
}
|
|
861
865
|
catch {
|
|
862
866
|
return true;
|
|
@@ -865,8 +869,8 @@ function lookupReplaced() {
|
|
|
865
869
|
const EARLY_WORKER = `
|
|
866
870
|
const { workerData } = require("node:worker_threads");
|
|
867
871
|
const { isIPv4 } = require("node:net");
|
|
868
|
-
const { port, host, timeoutMs, servers, hostsFile } = workerData;
|
|
869
|
-
const done = (m) => { try { port.postMessage(m); } catch {} try { port.close(); } catch {} };
|
|
872
|
+
const { port, host, timeoutMs, tries, atMs, servers, hostsFile } = workerData;
|
|
873
|
+
const done = (m) => { m.hr = Number(process.hrtime.bigint()) / 1e6; try { port.postMessage(m); } catch {} try { port.close(); } catch {} };
|
|
870
874
|
try {
|
|
871
875
|
let named = false;
|
|
872
876
|
const v4 = [];
|
|
@@ -880,12 +884,28 @@ try {
|
|
|
880
884
|
} catch {}
|
|
881
885
|
if (named) done(v4.length > 0 ? { host, addrs: v4, ttl: 60, at: Date.now() } : { host, none: "hosts" });
|
|
882
886
|
else {
|
|
883
|
-
const
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
});
|
|
887
|
+
const { Resolver } = require("node:dns");
|
|
888
|
+
const rs = [];
|
|
889
|
+
const timers = [];
|
|
890
|
+
let fin = false, asked = 0, failed = 0;
|
|
891
|
+
const finish = (m) => { if (fin) return; fin = true; for (const t of timers) clearTimeout(t); for (const r of rs) { try { r.cancel(); } catch {} } done(m); };
|
|
892
|
+
const gaveUp = (why) => { if (++failed < asked) return; if (asked < atMs.length) ask(); else finish({ host, none: why }); };
|
|
893
|
+
const ask = () => {
|
|
894
|
+
if (fin || asked >= atMs.length) return;
|
|
895
|
+
const k = asked++;
|
|
896
|
+
try {
|
|
897
|
+
const r = new Resolver({ timeout: timeoutMs, tries });
|
|
898
|
+
const list = Array.isArray(servers) && servers.length > 0 ? servers : r.getServers();
|
|
899
|
+
if (list.length > 0) r.setServers(list.map((_, i) => list[(i + k) % list.length]));
|
|
900
|
+
rs.push(r);
|
|
901
|
+
r.resolve4(host, { ttl: true }, (err, recs) => {
|
|
902
|
+
if (fin) return;
|
|
903
|
+
if (!err && Array.isArray(recs) && recs.length > 0) return finish({ host, addrs: recs.map((x) => x.address), ttl: Math.min(...recs.map((x) => x.ttl)), at: Date.now() });
|
|
904
|
+
gaveUp((err && err.code) || "empty");
|
|
905
|
+
});
|
|
906
|
+
} catch { gaveUp("error"); }
|
|
907
|
+
};
|
|
908
|
+
for (const ms of atMs) { if (ms <= 0) ask(); else timers.push(setTimeout(ask, ms)); }
|
|
889
909
|
}
|
|
890
910
|
} catch { done({ host, none: "error" }); }
|
|
891
911
|
`;
|
|
@@ -916,7 +936,7 @@ function earlyLookupFor(host) {
|
|
|
916
936
|
const worker = new Worker(EARLY_WORKER, {
|
|
917
937
|
eval: true,
|
|
918
938
|
workerData: {
|
|
919
|
-
port: port2, host, timeoutMs:
|
|
939
|
+
port: port2, host, timeoutMs: LOOKUP_FIRST_MS, tries: LOOKUP_TRIES, atMs: LOOKUP_AT_MS,
|
|
920
940
|
servers: tuning.earlyServers ?? (Array.isArray(earlySeam().servers) ? earlySeam().servers : undefined),
|
|
921
941
|
hostsFile: tuning.earlyHostsFile ?? (typeof earlySeam().hostsFile === "string" ? earlySeam().hostsFile : undefined),
|
|
922
942
|
},
|
|
@@ -964,6 +984,10 @@ function settleEarly(e, message) {
|
|
|
964
984
|
else {
|
|
965
985
|
e.state = "none";
|
|
966
986
|
}
|
|
987
|
+
if (TRACE_ON) {
|
|
988
|
+
const at = typeof m?.hr === "number" ? m.hr - (Number(process.hrtime.bigint()) / 1e6 - performance.now()) : performance.now();
|
|
989
|
+
traceEventAt(e.state === "answered" ? `dns:early-answer ${addrs.length}` : `dns:early-none ${typeof m?.none === "string" ? m.none : "gone"}`, at);
|
|
990
|
+
}
|
|
967
991
|
safely(() => { e.port.close(); });
|
|
968
992
|
const waiters = e.waiters.splice(0);
|
|
969
993
|
for (const w of waiters)
|
|
@@ -1290,12 +1314,13 @@ function targetOf(url, known, remember) {
|
|
|
1290
1314
|
}
|
|
1291
1315
|
function fetchInit(init, deadline) {
|
|
1292
1316
|
const timeout = AbortSignal.timeout(Math.max(0, Math.ceil(deadline - performance.now())));
|
|
1317
|
+
const dispatcher = init.dispatcher ?? init.dispatcherFor?.(init.timeoutMs);
|
|
1293
1318
|
return {
|
|
1294
1319
|
method: init.method,
|
|
1295
1320
|
headers: init.headers,
|
|
1296
1321
|
signal: init.signal === undefined ? timeout : AbortSignal.any([timeout, init.signal]),
|
|
1297
1322
|
...(init.body === undefined ? {} : { body: init.body }),
|
|
1298
|
-
...(
|
|
1323
|
+
...(dispatcher === undefined ? {} : { dispatcher }),
|
|
1299
1324
|
};
|
|
1300
1325
|
}
|
|
1301
1326
|
function fetchFailed(cause) {
|
|
@@ -1312,27 +1337,45 @@ class HandBack {
|
|
|
1312
1337
|
const UNSENT = new HandBack(0, 0);
|
|
1313
1338
|
const VIA_FETCH = Symbol("via-fetch");
|
|
1314
1339
|
const THREAD_GRACE_MS = 1_000;
|
|
1340
|
+
const THREAD_SWEEP_MS = 250;
|
|
1341
|
+
const THREAD_MARKS = 4096;
|
|
1315
1342
|
const THREAD_PHASES = ["w.got", "w.sent", "w.head", "w.end"];
|
|
1316
1343
|
class TransportThread {
|
|
1317
1344
|
origin;
|
|
1318
1345
|
worker;
|
|
1319
1346
|
port;
|
|
1347
|
+
acks;
|
|
1320
1348
|
state = "starting";
|
|
1321
1349
|
sessions = 0;
|
|
1322
1350
|
limit = DEFAULT_STREAM_LIMIT;
|
|
1323
1351
|
everReady = false;
|
|
1324
1352
|
closedReason = "";
|
|
1325
1353
|
onFail;
|
|
1354
|
+
startedAt = performance.now();
|
|
1355
|
+
startWaiters = [];
|
|
1326
1356
|
inflight = new Map();
|
|
1327
1357
|
nextId = 1;
|
|
1328
1358
|
listening = false;
|
|
1359
|
+
refd = false;
|
|
1360
|
+
sweep;
|
|
1361
|
+
freeMarks = [];
|
|
1362
|
+
messages = 0;
|
|
1363
|
+
outbox = [];
|
|
1364
|
+
turnOpen = false;
|
|
1365
|
+
posts = 0;
|
|
1329
1366
|
answered = 0;
|
|
1330
1367
|
pace = THREAD_DEFAULTS.pace;
|
|
1331
1368
|
hrOffset = Number(process.hrtime.bigint()) / 1e6 - performance.now();
|
|
1332
|
-
constructor(origin, worker, port) {
|
|
1369
|
+
constructor(origin, worker, port, acks) {
|
|
1333
1370
|
this.origin = origin;
|
|
1334
1371
|
this.worker = worker;
|
|
1335
1372
|
this.port = port;
|
|
1373
|
+
this.acks = acks;
|
|
1374
|
+
for (let i = acks.length - 1; i >= 0; i--)
|
|
1375
|
+
this.freeMarks.push(i);
|
|
1376
|
+
}
|
|
1377
|
+
acked(p) {
|
|
1378
|
+
return Atomics.load(this.acks, p.mark) === p.id;
|
|
1336
1379
|
}
|
|
1337
1380
|
poll() {
|
|
1338
1381
|
for (;;) {
|
|
@@ -1349,12 +1392,44 @@ class TransportThread {
|
|
|
1349
1392
|
}
|
|
1350
1393
|
}
|
|
1351
1394
|
ready() {
|
|
1352
|
-
if (this.state !== "closed")
|
|
1395
|
+
if ((!this.listening || this.state === "starting") && this.state !== "closed")
|
|
1353
1396
|
this.poll();
|
|
1354
|
-
return this.state === "ready" && this.sessions > 0 && this.inflight.size < this.sessions * this.limit;
|
|
1397
|
+
return this.state === "ready" && this.sessions > 0 && this.inflight.size < this.sessions * this.limit && this.freeMarks.length > 0;
|
|
1398
|
+
}
|
|
1399
|
+
starting() {
|
|
1400
|
+
if (this.state === "starting")
|
|
1401
|
+
this.poll();
|
|
1402
|
+
return this.state === "starting";
|
|
1403
|
+
}
|
|
1404
|
+
whenStarted(cb) {
|
|
1405
|
+
if (!this.starting()) {
|
|
1406
|
+
queueMicrotask(cb);
|
|
1407
|
+
return () => { };
|
|
1408
|
+
}
|
|
1409
|
+
this.startWaiters.push(cb);
|
|
1410
|
+
this.listen();
|
|
1411
|
+
return () => {
|
|
1412
|
+
const i = this.startWaiters.indexOf(cb);
|
|
1413
|
+
if (i >= 0)
|
|
1414
|
+
this.startWaiters.splice(i, 1);
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
started() {
|
|
1418
|
+
for (const cb of this.startWaiters.splice(0))
|
|
1419
|
+
safely(cb);
|
|
1420
|
+
}
|
|
1421
|
+
listen() {
|
|
1422
|
+
if (this.listening)
|
|
1423
|
+
return;
|
|
1424
|
+
this.listening = true;
|
|
1425
|
+
this.poll();
|
|
1426
|
+
this.port.on("message", (m) => { this.onMessage(m); });
|
|
1427
|
+
if (!this.refd)
|
|
1428
|
+
safely(() => { this.port.unref(); });
|
|
1355
1429
|
}
|
|
1356
1430
|
onMessage(m) {
|
|
1357
1431
|
const msg = m;
|
|
1432
|
+
this.messages++;
|
|
1358
1433
|
switch (msg?.t) {
|
|
1359
1434
|
case "ready":
|
|
1360
1435
|
if (this.state === "starting" || this.state === "ready") {
|
|
@@ -1374,8 +1449,16 @@ class TransportThread {
|
|
|
1374
1449
|
if (typeof c.index === "number" && typeof c.addr === "string" && typeof c.localPort === "number")
|
|
1375
1450
|
traceEventAt(`thread:conn${c.index} ${c.addr} ${c.localPort}`, t);
|
|
1376
1451
|
}
|
|
1452
|
+
if (first)
|
|
1453
|
+
this.started();
|
|
1377
1454
|
}
|
|
1378
1455
|
return;
|
|
1456
|
+
case "ev": {
|
|
1457
|
+
const e = msg;
|
|
1458
|
+
if (typeof e.n === "string" && typeof e.hr === "number")
|
|
1459
|
+
traceEventAt(`thread:w-${e.n}`, e.hr - this.hrOffset);
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1379
1462
|
case "sessions":
|
|
1380
1463
|
if (typeof msg.sessions === "number")
|
|
1381
1464
|
this.sessions = msg.sessions;
|
|
@@ -1386,17 +1469,12 @@ class TransportThread {
|
|
|
1386
1469
|
if (this.closedReason === "")
|
|
1387
1470
|
this.closedReason = msg.reason ?? "draining";
|
|
1388
1471
|
traceEvent("thread:draining");
|
|
1472
|
+
this.started();
|
|
1389
1473
|
}
|
|
1390
1474
|
return;
|
|
1391
1475
|
case "closed":
|
|
1392
1476
|
this.close(msg.reason ?? "closed");
|
|
1393
1477
|
return;
|
|
1394
|
-
case "ack": {
|
|
1395
|
-
const p = this.inflight.get(msg.id ?? -1);
|
|
1396
|
-
if (p !== undefined)
|
|
1397
|
-
p.acked = true;
|
|
1398
|
-
return;
|
|
1399
|
-
}
|
|
1400
1478
|
case "res":
|
|
1401
1479
|
case "err":
|
|
1402
1480
|
this.settle(m);
|
|
@@ -1472,6 +1550,7 @@ class TransportThread {
|
|
|
1472
1550
|
this.port.postMessage({ t: "drain" });
|
|
1473
1551
|
}
|
|
1474
1552
|
catch { }
|
|
1553
|
+
this.started();
|
|
1475
1554
|
}
|
|
1476
1555
|
close(reason) {
|
|
1477
1556
|
if (this.state === "closed")
|
|
@@ -1480,10 +1559,13 @@ class TransportThread {
|
|
|
1480
1559
|
if (this.closedReason === "")
|
|
1481
1560
|
this.closedReason = reason;
|
|
1482
1561
|
traceEvent("thread:closed");
|
|
1562
|
+
this.started();
|
|
1563
|
+
this.outbox = [];
|
|
1483
1564
|
for (const [id, p] of [...this.inflight]) {
|
|
1565
|
+
const sent = this.acked(p);
|
|
1484
1566
|
this.inflight.delete(id);
|
|
1485
1567
|
this.release(p);
|
|
1486
|
-
if (!
|
|
1568
|
+
if (!sent)
|
|
1487
1569
|
p.resolve(UNSENT);
|
|
1488
1570
|
else if (p.replayable)
|
|
1489
1571
|
p.resolve(new HandBack(0, 1));
|
|
@@ -1500,23 +1582,50 @@ class TransportThread {
|
|
|
1500
1582
|
}
|
|
1501
1583
|
}
|
|
1502
1584
|
ref() {
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
}
|
|
1585
|
+
this.listen();
|
|
1586
|
+
if (this.refd)
|
|
1587
|
+
return;
|
|
1588
|
+
this.refd = true;
|
|
1508
1589
|
safely(() => { this.port.ref(); });
|
|
1509
1590
|
safely(() => { this.worker.ref(); });
|
|
1591
|
+
this.sweep = setInterval(() => { this.sweepBackstops(); }, THREAD_SWEEP_MS);
|
|
1592
|
+
safely(() => { this.sweep?.unref?.(); });
|
|
1510
1593
|
}
|
|
1511
1594
|
unref() {
|
|
1595
|
+
if (this.sweep !== undefined) {
|
|
1596
|
+
clearInterval(this.sweep);
|
|
1597
|
+
this.sweep = undefined;
|
|
1598
|
+
}
|
|
1599
|
+
if (!this.refd)
|
|
1600
|
+
return;
|
|
1601
|
+
this.refd = false;
|
|
1512
1602
|
safely(() => { this.port.unref(); });
|
|
1513
1603
|
safely(() => { this.worker.unref(); });
|
|
1514
1604
|
}
|
|
1605
|
+
sweepBackstops() {
|
|
1606
|
+
this.poll();
|
|
1607
|
+
const now = performance.now();
|
|
1608
|
+
let late = false;
|
|
1609
|
+
for (const [id, p] of [...this.inflight]) {
|
|
1610
|
+
if (p.backstopAt > now)
|
|
1611
|
+
continue;
|
|
1612
|
+
late = true;
|
|
1613
|
+
this.inflight.delete(id);
|
|
1614
|
+
this.release(p);
|
|
1615
|
+
try {
|
|
1616
|
+
this.port.postMessage({ t: "cancel", id });
|
|
1617
|
+
}
|
|
1618
|
+
catch { }
|
|
1619
|
+
p.reject(timeoutReason());
|
|
1620
|
+
}
|
|
1621
|
+
if (late)
|
|
1622
|
+
this.close("no answer from the transport thread");
|
|
1623
|
+
}
|
|
1515
1624
|
release(p) {
|
|
1516
1625
|
if (p.signal !== undefined && p.onAbort !== undefined)
|
|
1517
1626
|
p.signal.removeEventListener("abort", p.onAbort);
|
|
1518
|
-
|
|
1519
|
-
|
|
1627
|
+
Atomics.store(this.acks, p.mark, 0);
|
|
1628
|
+
this.freeMarks.push(p.mark);
|
|
1520
1629
|
if (this.inflight.size === 0)
|
|
1521
1630
|
this.unref();
|
|
1522
1631
|
}
|
|
@@ -1541,8 +1650,11 @@ class TransportThread {
|
|
|
1541
1650
|
headers["content-length"] = Buffer.byteLength(init.body, "utf8");
|
|
1542
1651
|
const id = this.nextId++;
|
|
1543
1652
|
const tr = init.trace;
|
|
1653
|
+
const place = this.freeMarks.pop();
|
|
1654
|
+
if (place === undefined)
|
|
1655
|
+
return Promise.resolve(UNSENT);
|
|
1544
1656
|
return new Promise((resolve, reject) => {
|
|
1545
|
-
const p = { resolve, reject, method: init.method, replayable,
|
|
1657
|
+
const p = { id, resolve, reject, method: init.method, replayable, mark: place, backstopAt: performance.now() + remaining + THREAD_GRACE_MS, trace: tr };
|
|
1546
1658
|
const signal = init.signal;
|
|
1547
1659
|
if (signal !== undefined) {
|
|
1548
1660
|
p.signal = signal;
|
|
@@ -1551,47 +1663,59 @@ class TransportThread {
|
|
|
1551
1663
|
return;
|
|
1552
1664
|
this.inflight.delete(id);
|
|
1553
1665
|
this.release(p);
|
|
1554
|
-
|
|
1555
|
-
|
|
1666
|
+
const queued = this.outbox.findIndex((b) => b.id === id);
|
|
1667
|
+
if (queued >= 0)
|
|
1668
|
+
this.outbox.splice(queued, 1);
|
|
1669
|
+
else {
|
|
1670
|
+
try {
|
|
1671
|
+
this.port.postMessage({ t: "cancel", id });
|
|
1672
|
+
}
|
|
1673
|
+
catch { }
|
|
1556
1674
|
}
|
|
1557
|
-
catch { }
|
|
1558
1675
|
reject(signal.reason);
|
|
1559
1676
|
};
|
|
1560
1677
|
signal.addEventListener("abort", p.onAbort, { once: true });
|
|
1561
1678
|
}
|
|
1562
|
-
p.backstop = setTimeout(() => {
|
|
1563
|
-
this.poll();
|
|
1564
|
-
if (!this.inflight.has(id))
|
|
1565
|
-
return;
|
|
1566
|
-
this.inflight.delete(id);
|
|
1567
|
-
this.release(p);
|
|
1568
|
-
try {
|
|
1569
|
-
this.port.postMessage({ t: "cancel", id });
|
|
1570
|
-
}
|
|
1571
|
-
catch { }
|
|
1572
|
-
reject(timeoutReason());
|
|
1573
|
-
this.close("no answer from the transport thread");
|
|
1574
|
-
}, remaining + THREAD_GRACE_MS);
|
|
1575
|
-
safely(() => { p.backstop?.unref?.(); });
|
|
1576
1679
|
this.inflight.set(id, p);
|
|
1577
1680
|
this.ref();
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1681
|
+
const paced = this.pace === "all" || (this.pace === "creates" && init.method === "POST" && CREATE_PATH.test(target.path));
|
|
1682
|
+
this.outbox.push({ id, msg: { t: "req", id, mark: place, headers, sensitive: ["authorization"], body: init.body, timeoutMs: remaining, replayable, paced }, p, resolve, trace: tr });
|
|
1683
|
+
if (!this.turnOpen) {
|
|
1684
|
+
this.turnOpen = true;
|
|
1685
|
+
queueMicrotask(() => { this.turnOpen = false; this.handOver(); });
|
|
1686
|
+
this.handOver();
|
|
1581
1687
|
}
|
|
1582
|
-
|
|
1583
|
-
this.
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1688
|
+
else if (this.outbox.length >= OUTBOX_GROUP)
|
|
1689
|
+
this.handOver();
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
handOver() {
|
|
1693
|
+
if (this.outbox.length === 0)
|
|
1694
|
+
return;
|
|
1695
|
+
const batch = this.outbox;
|
|
1696
|
+
this.outbox = [];
|
|
1697
|
+
try {
|
|
1698
|
+
this.port.postMessage(batch.length === 1 ? batch[0].msg : { t: "reqs", list: batch.map((b) => b.msg) });
|
|
1699
|
+
this.posts++;
|
|
1700
|
+
}
|
|
1701
|
+
catch {
|
|
1702
|
+
for (const b of batch) {
|
|
1703
|
+
if (!this.inflight.has(b.id))
|
|
1704
|
+
continue;
|
|
1705
|
+
this.inflight.delete(b.id);
|
|
1706
|
+
this.release(b.p);
|
|
1707
|
+
b.resolve(UNSENT);
|
|
1587
1708
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
for (const b of batch)
|
|
1712
|
+
if (b.trace !== undefined) {
|
|
1713
|
+
mark(b.trace, "sub");
|
|
1714
|
+
b.trace.via = "thread";
|
|
1591
1715
|
}
|
|
1592
|
-
});
|
|
1593
1716
|
}
|
|
1594
1717
|
}
|
|
1718
|
+
const OUTBOX_GROUP = 8;
|
|
1595
1719
|
let transportThread;
|
|
1596
1720
|
function threadFor(origin) {
|
|
1597
1721
|
const t = transportThread;
|
|
@@ -1599,6 +1723,55 @@ function threadFor(origin) {
|
|
|
1599
1723
|
return undefined;
|
|
1600
1724
|
return t.ready() && !lookupReplaced() ? t : undefined;
|
|
1601
1725
|
}
|
|
1726
|
+
function startingThread(origin) {
|
|
1727
|
+
const t = transportThread;
|
|
1728
|
+
if (t === undefined || t.origin !== origin || lookupReplaced())
|
|
1729
|
+
return undefined;
|
|
1730
|
+
return t.starting() ? t : undefined;
|
|
1731
|
+
}
|
|
1732
|
+
let raceTraced = false;
|
|
1733
|
+
function firstUp(t, pool, deadline, signal) {
|
|
1734
|
+
const first = !raceTraced;
|
|
1735
|
+
if (first) {
|
|
1736
|
+
raceTraced = true;
|
|
1737
|
+
traceEvent("h2:race");
|
|
1738
|
+
}
|
|
1739
|
+
const stop = new AbortController();
|
|
1740
|
+
const both = signal === undefined ? stop.signal : AbortSignal.any([signal, stop.signal]);
|
|
1741
|
+
return new Promise((resolve, reject) => {
|
|
1742
|
+
let done = false;
|
|
1743
|
+
const forget = t.whenStarted(() => {
|
|
1744
|
+
if (done)
|
|
1745
|
+
return;
|
|
1746
|
+
const ready = threadFor(t.origin);
|
|
1747
|
+
if (ready === undefined)
|
|
1748
|
+
return;
|
|
1749
|
+
done = true;
|
|
1750
|
+
if (first)
|
|
1751
|
+
traceEvent("h2:race-thread");
|
|
1752
|
+
stop.abort();
|
|
1753
|
+
resolve({ thread: ready });
|
|
1754
|
+
});
|
|
1755
|
+
pool.acquire(deadline, both).then((entry) => {
|
|
1756
|
+
if (done) {
|
|
1757
|
+
if (entry !== null)
|
|
1758
|
+
pool.end(entry);
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
done = true;
|
|
1762
|
+
forget();
|
|
1763
|
+
if (first)
|
|
1764
|
+
traceEvent("h2:race-pool");
|
|
1765
|
+
resolve({ entry });
|
|
1766
|
+
}, (err) => {
|
|
1767
|
+
if (done)
|
|
1768
|
+
return;
|
|
1769
|
+
done = true;
|
|
1770
|
+
forget();
|
|
1771
|
+
reject(err);
|
|
1772
|
+
});
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1602
1775
|
function ownFile() {
|
|
1603
1776
|
const frame = (new Error().stack ?? "").split("\n")[1] ?? "";
|
|
1604
1777
|
const m = /\((.*):\d+:\d+\)\s*$/.exec(frame) ?? /at (.*):\d+:\d+\s*$/.exec(frame);
|
|
@@ -1636,11 +1809,12 @@ function startTransportThread(origin, onFail) {
|
|
|
1636
1809
|
const knobs = threadKnobs();
|
|
1637
1810
|
const file = typeof seam.transportFile === "string" ? seam.transportFile : here.replace(/http2\.js$/, "transport-worker.js");
|
|
1638
1811
|
const { port1, port2 } = new MessageChannel();
|
|
1812
|
+
const acks = new Int32Array(new SharedArrayBuffer(4 * THREAD_MARKS));
|
|
1639
1813
|
const worker = new Worker(file.startsWith("file:") ? new URL(file) : file, {
|
|
1640
1814
|
workerData: {
|
|
1641
|
-
promigenceTransport: true, port: port2, origin: origin.origin, sessions: knobs.sessions, gapUs: knobs.gapUs,
|
|
1815
|
+
promigenceTransport: true, port: port2, acks, origin: origin.origin, sessions: knobs.sessions, gapUs: knobs.gapUs,
|
|
1642
1816
|
firstUseMs: tuning.loadFirstUseMs, idleMs: IDLE_CLOSE_MS, connectTimeoutMs: CONNECT_TIMEOUT_MS,
|
|
1643
|
-
lookupTimeoutMs:
|
|
1817
|
+
lookupTimeoutMs: LOOKUP_FIRST_MS, lookupTries: LOOKUP_TRIES, lookupAtMs: LOOKUP_AT_MS, trace: TRACE_ON,
|
|
1644
1818
|
servers: tuning.earlyServers ?? (Array.isArray(seam.servers) ? seam.servers : undefined),
|
|
1645
1819
|
hostsFile: tuning.earlyHostsFile ?? (typeof seam.hostsFile === "string" ? seam.hostsFile : undefined),
|
|
1646
1820
|
distrustedForTest: __caGuardInternals.testHashes(),
|
|
@@ -1651,13 +1825,18 @@ function startTransportThread(origin, onFail) {
|
|
|
1651
1825
|
});
|
|
1652
1826
|
worker.unref();
|
|
1653
1827
|
port1.unref();
|
|
1654
|
-
const t = new TransportThread(origin.origin, worker, port1);
|
|
1828
|
+
const t = new TransportThread(origin.origin, worker, port1, acks);
|
|
1655
1829
|
t.pace = knobs.pace;
|
|
1656
1830
|
t.onFail = onFail;
|
|
1657
1831
|
worker.on("error", () => { t.poll(); t.close("error"); });
|
|
1658
1832
|
worker.on("exit", () => { t.poll(); t.close("exit"); });
|
|
1659
1833
|
transportThread = t;
|
|
1660
1834
|
traceEvent("thread:start");
|
|
1835
|
+
onTraceFlush(() => {
|
|
1836
|
+
t.poll();
|
|
1837
|
+
for (const e of earlies.values())
|
|
1838
|
+
pollEarly(e);
|
|
1839
|
+
});
|
|
1661
1840
|
}
|
|
1662
1841
|
catch {
|
|
1663
1842
|
transportThread = undefined;
|
|
@@ -1673,6 +1852,18 @@ function knownPrefix(url) {
|
|
|
1673
1852
|
}
|
|
1674
1853
|
return undefined;
|
|
1675
1854
|
}
|
|
1855
|
+
let lastTargetUrl;
|
|
1856
|
+
let lastTargetRead;
|
|
1857
|
+
let targetReads = 0;
|
|
1858
|
+
function lastTarget(url) {
|
|
1859
|
+
if (url === lastTargetUrl)
|
|
1860
|
+
return lastTargetRead;
|
|
1861
|
+
targetReads++;
|
|
1862
|
+
const target = targetOf(url, knownPrefix(url), remember);
|
|
1863
|
+
lastTargetUrl = url;
|
|
1864
|
+
lastTargetRead = target;
|
|
1865
|
+
return target;
|
|
1866
|
+
}
|
|
1676
1867
|
function remember(p) {
|
|
1677
1868
|
for (let i = 0; i < prefixes.length; i++)
|
|
1678
1869
|
if (prefixes[i].text === p.text)
|
|
@@ -1708,7 +1899,7 @@ export class Http2Transport {
|
|
|
1708
1899
|
const deadline = performance.now() + init.timeoutMs;
|
|
1709
1900
|
let target;
|
|
1710
1901
|
try {
|
|
1711
|
-
target =
|
|
1902
|
+
target = lastTarget(url);
|
|
1712
1903
|
}
|
|
1713
1904
|
catch {
|
|
1714
1905
|
return this.viaFetch(url, init, deadline);
|
|
@@ -1732,8 +1923,34 @@ export class Http2Transport {
|
|
|
1732
1923
|
const pool = poolFor(target.origin);
|
|
1733
1924
|
if (!pool.usable())
|
|
1734
1925
|
return this.viaFetch(url, init, deadline);
|
|
1926
|
+
let raced = false;
|
|
1735
1927
|
for (;;) {
|
|
1736
|
-
|
|
1928
|
+
let entry = pool.tryAcquire();
|
|
1929
|
+
if (entry === undefined) {
|
|
1930
|
+
const starting = raced || resends > 0 || replays > 0 ? undefined : startingThread(target.origin);
|
|
1931
|
+
if (starting !== undefined) {
|
|
1932
|
+
raced = true;
|
|
1933
|
+
const won = await firstUp(starting, pool, deadline, init.signal);
|
|
1934
|
+
if (won.thread !== undefined) {
|
|
1935
|
+
if (tr !== undefined)
|
|
1936
|
+
mark(tr, "race");
|
|
1937
|
+
const out = await won.thread.send(target, init, replayable, deadline);
|
|
1938
|
+
if (out === VIA_FETCH)
|
|
1939
|
+
return this.viaFetch(url, init, deadline);
|
|
1940
|
+
if (!(out instanceof HandBack))
|
|
1941
|
+
return out;
|
|
1942
|
+
resends = out.resends;
|
|
1943
|
+
replays = out.replays;
|
|
1944
|
+
if (tr !== undefined)
|
|
1945
|
+
delete tr.via;
|
|
1946
|
+
continue;
|
|
1947
|
+
}
|
|
1948
|
+
entry = won.entry;
|
|
1949
|
+
}
|
|
1950
|
+
else {
|
|
1951
|
+
entry = await pool.acquire(deadline, init.signal);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1737
1954
|
if (tr !== undefined)
|
|
1738
1955
|
mark(tr, "acq");
|
|
1739
1956
|
if (entry === null)
|
|
@@ -1768,6 +1985,15 @@ export class Http2Transport {
|
|
|
1768
1985
|
}
|
|
1769
1986
|
}
|
|
1770
1987
|
}
|
|
1988
|
+
let sharedFallback;
|
|
1989
|
+
let shared;
|
|
1990
|
+
export function sharedTransport(fallback) {
|
|
1991
|
+
if (shared === undefined || sharedFallback !== fallback) {
|
|
1992
|
+
shared = new Http2Transport(fallback);
|
|
1993
|
+
sharedFallback = fallback;
|
|
1994
|
+
}
|
|
1995
|
+
return shared;
|
|
1996
|
+
}
|
|
1771
1997
|
export function warmTransport() {
|
|
1772
1998
|
const res = toResponse(200, { "content-type": "application/json" }, Buffer.from("{}"), "POST");
|
|
1773
1999
|
res.header("server-timing");
|
|
@@ -1780,6 +2006,9 @@ export function warmTransport() {
|
|
|
1780
2006
|
exchange({ end() { } }, undefined, { origin: none, host: none, path: none }, undefined, 0).catch(() => { });
|
|
1781
2007
|
}
|
|
1782
2008
|
export const __http2Internals = {
|
|
2009
|
+
targetReads() {
|
|
2010
|
+
return targetReads;
|
|
2011
|
+
},
|
|
1783
2012
|
pools() {
|
|
1784
2013
|
return [...pools.values()].map((p) => ({
|
|
1785
2014
|
origin: p.origin.origin,
|
|
@@ -1820,11 +2049,17 @@ export const __http2Internals = {
|
|
|
1820
2049
|
if (t === undefined)
|
|
1821
2050
|
return undefined;
|
|
1822
2051
|
t.poll();
|
|
1823
|
-
return { state: t.state, sessions: t.sessions, limit: t.limit, answered: t.answered, everReady: t.everReady, closedReason: t.closedReason };
|
|
2052
|
+
return { state: t.state, sessions: t.sessions, limit: t.limit, answered: t.answered, everReady: t.everReady, closedReason: t.closedReason, messages: t.messages, posts: t.posts, startedAt: t.startedAt };
|
|
1824
2053
|
},
|
|
1825
2054
|
threadKnobs(env) {
|
|
1826
2055
|
return threadKnobs(env);
|
|
1827
2056
|
},
|
|
2057
|
+
wedgeThreadForTest() {
|
|
2058
|
+
try {
|
|
2059
|
+
transportThread?.port.postMessage({ t: "wedge-for-test" });
|
|
2060
|
+
}
|
|
2061
|
+
catch { }
|
|
2062
|
+
},
|
|
1828
2063
|
crashThreadForTest() {
|
|
1829
2064
|
try {
|
|
1830
2065
|
transportThread?.port.postMessage({ t: "crash-for-test" });
|
|
@@ -1863,6 +2098,15 @@ function isMainThread() {
|
|
|
1863
2098
|
return false;
|
|
1864
2099
|
}
|
|
1865
2100
|
}
|
|
2101
|
+
function standby(url) {
|
|
2102
|
+
try {
|
|
2103
|
+
traceEvent("h2:standby");
|
|
2104
|
+
warmTls();
|
|
2105
|
+
poolFor(url.origin).warm(tuning.loadFirstUseMs);
|
|
2106
|
+
}
|
|
2107
|
+
catch {
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
1866
2110
|
function connectAtLoad() {
|
|
1867
2111
|
try {
|
|
1868
2112
|
if (!isNode() || Number(process.versions.node.split(".")[0]) < 22)
|
|
@@ -1883,6 +2127,8 @@ function connectAtLoad() {
|
|
|
1883
2127
|
if (url.protocol === "https:" && !customDispatcher() && earlyPlatform() && v4First() && !threadOff && earlySeam().transportThread !== false) {
|
|
1884
2128
|
startTransportThread(url, () => { if (loadTurnRan)
|
|
1885
2129
|
warmAtLoad(); });
|
|
2130
|
+
if (isIP(host) === 0)
|
|
2131
|
+
earlyLookupFor(host);
|
|
1886
2132
|
}
|
|
1887
2133
|
else if (url.protocol === "https:" && !customDispatcher() && isIP(host) === 0 && earlyPlatform() && v4First())
|
|
1888
2134
|
earlyLookupFor(host);
|
|
@@ -1901,10 +2147,27 @@ function connectAtLoad() {
|
|
|
1901
2147
|
const url = new URL(raw ? raw : DEFAULT_API_URL);
|
|
1902
2148
|
if (url.protocol !== "https:" || customDispatcher())
|
|
1903
2149
|
return;
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
2150
|
+
const t = transportThread;
|
|
2151
|
+
if (t !== undefined && t.origin === url.origin) {
|
|
2152
|
+
t.poll();
|
|
2153
|
+
if (t.everReady)
|
|
1907
2154
|
return;
|
|
2155
|
+
if (t.state !== "closed") {
|
|
2156
|
+
const check = () => {
|
|
2157
|
+
t.poll();
|
|
2158
|
+
if (t.everReady || t.state === "closed")
|
|
2159
|
+
return;
|
|
2160
|
+
const wait = t.startedAt + tuning.standbyAfterMs - performance.now();
|
|
2161
|
+
if (wait > 0) {
|
|
2162
|
+
const timer = setTimeout(check, wait);
|
|
2163
|
+
timer.unref?.();
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2166
|
+
standby(url);
|
|
2167
|
+
};
|
|
2168
|
+
check();
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
1908
2171
|
}
|
|
1909
2172
|
warmTls();
|
|
1910
2173
|
poolFor(url.origin).warm(tuning.loadFirstUseMs);
|