@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/cjs/http2.js CHANGED
@@ -6,6 +6,7 @@ exports.warmTls = warmTls;
6
6
  exports.parseSessions = parseSessions;
7
7
  exports.extraOrders = extraOrders;
8
8
  exports.lookupApiName = lookupApiName;
9
+ exports.sharedTransport = sharedTransport;
9
10
  exports.warmTransport = warmTransport;
10
11
  const http2 = require("node:http2");
11
12
  const node_net_1 = require("node:net");
@@ -20,6 +21,7 @@ const MAX_SESSIONS = 4;
20
21
  const FIRST_SESSIONS = 2;
21
22
  const BURST_MIN = 8;
22
23
  const HOLD_MS = 10;
24
+ const STANDBY_AFTER_MS = 300;
23
25
  const LOAD_FIRST_USE_MS = 120_000;
24
26
  const CONNECT_TIMEOUT_MS = 10_000;
25
27
  const IDLE_CLOSE_MS = 10_000;
@@ -571,6 +573,7 @@ exports.tuning = {
571
573
  holdMs: HOLD_MS,
572
574
  burstMin: BURST_MIN,
573
575
  loadFirstUseMs: LOAD_FIRST_USE_MS,
576
+ standbyAfterMs: STANDBY_AFTER_MS,
574
577
  earlyPlatform: undefined,
575
578
  earlyServers: undefined,
576
579
  earlyHostsFile: undefined,
@@ -854,19 +857,21 @@ function apiLookup(asked, observe, ipv4First = v4First(), earlyOk = true) {
854
857
  const earlies = new Map();
855
858
  const MAX_EARLIES = 4;
856
859
  const EARLY_MAX_AGE_MS = 60_000;
857
- const EARLY_TIMEOUT_MS = 2_000;
858
- const LOOKUP_AT_LOAD = (() => {
860
+ const LOOKUP_FIRST_MS = 150;
861
+ const LOOKUP_TRIES = 3;
862
+ const LOOKUP_AT_MS = [0, 100, 250];
863
+ const DNS_AT_LOAD = (() => {
859
864
  try {
860
- return nodeDns()?.lookup;
865
+ return nodeDns();
861
866
  }
862
867
  catch {
863
868
  return undefined;
864
869
  }
865
870
  })();
871
+ const LOOKUP_AT_LOAD = DNS_AT_LOAD?.lookup;
866
872
  function lookupReplaced() {
867
873
  try {
868
- const d = nodeDns();
869
- return d !== undefined && d.lookup !== LOOKUP_AT_LOAD;
874
+ return DNS_AT_LOAD !== undefined && DNS_AT_LOAD.lookup !== LOOKUP_AT_LOAD;
870
875
  }
871
876
  catch {
872
877
  return true;
@@ -875,8 +880,8 @@ function lookupReplaced() {
875
880
  const EARLY_WORKER = `
876
881
  const { workerData } = require("node:worker_threads");
877
882
  const { isIPv4 } = require("node:net");
878
- const { port, host, timeoutMs, servers, hostsFile } = workerData;
879
- const done = (m) => { try { port.postMessage(m); } catch {} try { port.close(); } catch {} };
883
+ const { port, host, timeoutMs, tries, atMs, servers, hostsFile } = workerData;
884
+ const done = (m) => { m.hr = Number(process.hrtime.bigint()) / 1e6; try { port.postMessage(m); } catch {} try { port.close(); } catch {} };
880
885
  try {
881
886
  let named = false;
882
887
  const v4 = [];
@@ -890,12 +895,28 @@ try {
890
895
  } catch {}
891
896
  if (named) done(v4.length > 0 ? { host, addrs: v4, ttl: 60, at: Date.now() } : { host, none: "hosts" });
892
897
  else {
893
- const r = new (require("node:dns").Resolver)({ timeout: timeoutMs, tries: 1 });
894
- if (Array.isArray(servers) && servers.length > 0) r.setServers(servers);
895
- r.resolve4(host, { ttl: true }, (err, recs) => {
896
- if (err || !Array.isArray(recs) || recs.length === 0) return done({ host, none: (err && err.code) || "empty" });
897
- done({ host, addrs: recs.map((x) => x.address), ttl: Math.min(...recs.map((x) => x.ttl)), at: Date.now() });
898
- });
898
+ const { Resolver } = require("node:dns");
899
+ const rs = [];
900
+ const timers = [];
901
+ let fin = false, asked = 0, failed = 0;
902
+ 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); };
903
+ const gaveUp = (why) => { if (++failed < asked) return; if (asked < atMs.length) ask(); else finish({ host, none: why }); };
904
+ const ask = () => {
905
+ if (fin || asked >= atMs.length) return;
906
+ const k = asked++;
907
+ try {
908
+ const r = new Resolver({ timeout: timeoutMs, tries });
909
+ const list = Array.isArray(servers) && servers.length > 0 ? servers : r.getServers();
910
+ if (list.length > 0) r.setServers(list.map((_, i) => list[(i + k) % list.length]));
911
+ rs.push(r);
912
+ r.resolve4(host, { ttl: true }, (err, recs) => {
913
+ if (fin) return;
914
+ 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() });
915
+ gaveUp((err && err.code) || "empty");
916
+ });
917
+ } catch { gaveUp("error"); }
918
+ };
919
+ for (const ms of atMs) { if (ms <= 0) ask(); else timers.push(setTimeout(ask, ms)); }
899
920
  }
900
921
  } catch { done({ host, none: "error" }); }
901
922
  `;
@@ -926,7 +947,7 @@ function earlyLookupFor(host) {
926
947
  const worker = new node_worker_threads_1.Worker(EARLY_WORKER, {
927
948
  eval: true,
928
949
  workerData: {
929
- port: port2, host, timeoutMs: EARLY_TIMEOUT_MS,
950
+ port: port2, host, timeoutMs: LOOKUP_FIRST_MS, tries: LOOKUP_TRIES, atMs: LOOKUP_AT_MS,
930
951
  servers: exports.tuning.earlyServers ?? (Array.isArray(earlySeam().servers) ? earlySeam().servers : undefined),
931
952
  hostsFile: exports.tuning.earlyHostsFile ?? (typeof earlySeam().hostsFile === "string" ? earlySeam().hostsFile : undefined),
932
953
  },
@@ -974,6 +995,10 @@ function settleEarly(e, message) {
974
995
  else {
975
996
  e.state = "none";
976
997
  }
998
+ if (trace_js_1.TRACE_ON) {
999
+ const at = typeof m?.hr === "number" ? m.hr - (Number(process.hrtime.bigint()) / 1e6 - performance.now()) : performance.now();
1000
+ (0, trace_js_1.traceEventAt)(e.state === "answered" ? `dns:early-answer ${addrs.length}` : `dns:early-none ${typeof m?.none === "string" ? m.none : "gone"}`, at);
1001
+ }
977
1002
  safely(() => { e.port.close(); });
978
1003
  const waiters = e.waiters.splice(0);
979
1004
  for (const w of waiters)
@@ -1300,12 +1325,13 @@ function targetOf(url, known, remember) {
1300
1325
  }
1301
1326
  function fetchInit(init, deadline) {
1302
1327
  const timeout = AbortSignal.timeout(Math.max(0, Math.ceil(deadline - performance.now())));
1328
+ const dispatcher = init.dispatcher ?? init.dispatcherFor?.(init.timeoutMs);
1303
1329
  return {
1304
1330
  method: init.method,
1305
1331
  headers: init.headers,
1306
1332
  signal: init.signal === undefined ? timeout : AbortSignal.any([timeout, init.signal]),
1307
1333
  ...(init.body === undefined ? {} : { body: init.body }),
1308
- ...(init.dispatcher === undefined ? {} : { dispatcher: init.dispatcher }),
1334
+ ...(dispatcher === undefined ? {} : { dispatcher }),
1309
1335
  };
1310
1336
  }
1311
1337
  function fetchFailed(cause) {
@@ -1322,27 +1348,45 @@ class HandBack {
1322
1348
  const UNSENT = new HandBack(0, 0);
1323
1349
  const VIA_FETCH = Symbol("via-fetch");
1324
1350
  const THREAD_GRACE_MS = 1_000;
1351
+ const THREAD_SWEEP_MS = 250;
1352
+ const THREAD_MARKS = 4096;
1325
1353
  const THREAD_PHASES = ["w.got", "w.sent", "w.head", "w.end"];
1326
1354
  class TransportThread {
1327
1355
  origin;
1328
1356
  worker;
1329
1357
  port;
1358
+ acks;
1330
1359
  state = "starting";
1331
1360
  sessions = 0;
1332
1361
  limit = DEFAULT_STREAM_LIMIT;
1333
1362
  everReady = false;
1334
1363
  closedReason = "";
1335
1364
  onFail;
1365
+ startedAt = performance.now();
1366
+ startWaiters = [];
1336
1367
  inflight = new Map();
1337
1368
  nextId = 1;
1338
1369
  listening = false;
1370
+ refd = false;
1371
+ sweep;
1372
+ freeMarks = [];
1373
+ messages = 0;
1374
+ outbox = [];
1375
+ turnOpen = false;
1376
+ posts = 0;
1339
1377
  answered = 0;
1340
1378
  pace = THREAD_DEFAULTS.pace;
1341
1379
  hrOffset = Number(process.hrtime.bigint()) / 1e6 - performance.now();
1342
- constructor(origin, worker, port) {
1380
+ constructor(origin, worker, port, acks) {
1343
1381
  this.origin = origin;
1344
1382
  this.worker = worker;
1345
1383
  this.port = port;
1384
+ this.acks = acks;
1385
+ for (let i = acks.length - 1; i >= 0; i--)
1386
+ this.freeMarks.push(i);
1387
+ }
1388
+ acked(p) {
1389
+ return Atomics.load(this.acks, p.mark) === p.id;
1346
1390
  }
1347
1391
  poll() {
1348
1392
  for (;;) {
@@ -1359,12 +1403,44 @@ class TransportThread {
1359
1403
  }
1360
1404
  }
1361
1405
  ready() {
1362
- if (this.state !== "closed")
1406
+ if ((!this.listening || this.state === "starting") && this.state !== "closed")
1363
1407
  this.poll();
1364
- return this.state === "ready" && this.sessions > 0 && this.inflight.size < this.sessions * this.limit;
1408
+ return this.state === "ready" && this.sessions > 0 && this.inflight.size < this.sessions * this.limit && this.freeMarks.length > 0;
1409
+ }
1410
+ starting() {
1411
+ if (this.state === "starting")
1412
+ this.poll();
1413
+ return this.state === "starting";
1414
+ }
1415
+ whenStarted(cb) {
1416
+ if (!this.starting()) {
1417
+ queueMicrotask(cb);
1418
+ return () => { };
1419
+ }
1420
+ this.startWaiters.push(cb);
1421
+ this.listen();
1422
+ return () => {
1423
+ const i = this.startWaiters.indexOf(cb);
1424
+ if (i >= 0)
1425
+ this.startWaiters.splice(i, 1);
1426
+ };
1427
+ }
1428
+ started() {
1429
+ for (const cb of this.startWaiters.splice(0))
1430
+ safely(cb);
1431
+ }
1432
+ listen() {
1433
+ if (this.listening)
1434
+ return;
1435
+ this.listening = true;
1436
+ this.poll();
1437
+ this.port.on("message", (m) => { this.onMessage(m); });
1438
+ if (!this.refd)
1439
+ safely(() => { this.port.unref(); });
1365
1440
  }
1366
1441
  onMessage(m) {
1367
1442
  const msg = m;
1443
+ this.messages++;
1368
1444
  switch (msg?.t) {
1369
1445
  case "ready":
1370
1446
  if (this.state === "starting" || this.state === "ready") {
@@ -1384,8 +1460,16 @@ class TransportThread {
1384
1460
  if (typeof c.index === "number" && typeof c.addr === "string" && typeof c.localPort === "number")
1385
1461
  (0, trace_js_1.traceEventAt)(`thread:conn${c.index} ${c.addr} ${c.localPort}`, t);
1386
1462
  }
1463
+ if (first)
1464
+ this.started();
1387
1465
  }
1388
1466
  return;
1467
+ case "ev": {
1468
+ const e = msg;
1469
+ if (typeof e.n === "string" && typeof e.hr === "number")
1470
+ (0, trace_js_1.traceEventAt)(`thread:w-${e.n}`, e.hr - this.hrOffset);
1471
+ return;
1472
+ }
1389
1473
  case "sessions":
1390
1474
  if (typeof msg.sessions === "number")
1391
1475
  this.sessions = msg.sessions;
@@ -1396,17 +1480,12 @@ class TransportThread {
1396
1480
  if (this.closedReason === "")
1397
1481
  this.closedReason = msg.reason ?? "draining";
1398
1482
  (0, trace_js_1.traceEvent)("thread:draining");
1483
+ this.started();
1399
1484
  }
1400
1485
  return;
1401
1486
  case "closed":
1402
1487
  this.close(msg.reason ?? "closed");
1403
1488
  return;
1404
- case "ack": {
1405
- const p = this.inflight.get(msg.id ?? -1);
1406
- if (p !== undefined)
1407
- p.acked = true;
1408
- return;
1409
- }
1410
1489
  case "res":
1411
1490
  case "err":
1412
1491
  this.settle(m);
@@ -1482,6 +1561,7 @@ class TransportThread {
1482
1561
  this.port.postMessage({ t: "drain" });
1483
1562
  }
1484
1563
  catch { }
1564
+ this.started();
1485
1565
  }
1486
1566
  close(reason) {
1487
1567
  if (this.state === "closed")
@@ -1490,10 +1570,13 @@ class TransportThread {
1490
1570
  if (this.closedReason === "")
1491
1571
  this.closedReason = reason;
1492
1572
  (0, trace_js_1.traceEvent)("thread:closed");
1573
+ this.started();
1574
+ this.outbox = [];
1493
1575
  for (const [id, p] of [...this.inflight]) {
1576
+ const sent = this.acked(p);
1494
1577
  this.inflight.delete(id);
1495
1578
  this.release(p);
1496
- if (!p.acked)
1579
+ if (!sent)
1497
1580
  p.resolve(UNSENT);
1498
1581
  else if (p.replayable)
1499
1582
  p.resolve(new HandBack(0, 1));
@@ -1510,23 +1593,50 @@ class TransportThread {
1510
1593
  }
1511
1594
  }
1512
1595
  ref() {
1513
- if (!this.listening) {
1514
- this.listening = true;
1515
- this.poll();
1516
- this.port.on("message", (m) => { this.onMessage(m); });
1517
- }
1596
+ this.listen();
1597
+ if (this.refd)
1598
+ return;
1599
+ this.refd = true;
1518
1600
  safely(() => { this.port.ref(); });
1519
1601
  safely(() => { this.worker.ref(); });
1602
+ this.sweep = setInterval(() => { this.sweepBackstops(); }, THREAD_SWEEP_MS);
1603
+ safely(() => { this.sweep?.unref?.(); });
1520
1604
  }
1521
1605
  unref() {
1606
+ if (this.sweep !== undefined) {
1607
+ clearInterval(this.sweep);
1608
+ this.sweep = undefined;
1609
+ }
1610
+ if (!this.refd)
1611
+ return;
1612
+ this.refd = false;
1522
1613
  safely(() => { this.port.unref(); });
1523
1614
  safely(() => { this.worker.unref(); });
1524
1615
  }
1616
+ sweepBackstops() {
1617
+ this.poll();
1618
+ const now = performance.now();
1619
+ let late = false;
1620
+ for (const [id, p] of [...this.inflight]) {
1621
+ if (p.backstopAt > now)
1622
+ continue;
1623
+ late = true;
1624
+ this.inflight.delete(id);
1625
+ this.release(p);
1626
+ try {
1627
+ this.port.postMessage({ t: "cancel", id });
1628
+ }
1629
+ catch { }
1630
+ p.reject(timeoutReason());
1631
+ }
1632
+ if (late)
1633
+ this.close("no answer from the transport thread");
1634
+ }
1525
1635
  release(p) {
1526
1636
  if (p.signal !== undefined && p.onAbort !== undefined)
1527
1637
  p.signal.removeEventListener("abort", p.onAbort);
1528
- if (p.backstop !== undefined)
1529
- clearTimeout(p.backstop);
1638
+ Atomics.store(this.acks, p.mark, 0);
1639
+ this.freeMarks.push(p.mark);
1530
1640
  if (this.inflight.size === 0)
1531
1641
  this.unref();
1532
1642
  }
@@ -1551,8 +1661,11 @@ class TransportThread {
1551
1661
  headers["content-length"] = Buffer.byteLength(init.body, "utf8");
1552
1662
  const id = this.nextId++;
1553
1663
  const tr = init.trace;
1664
+ const place = this.freeMarks.pop();
1665
+ if (place === undefined)
1666
+ return Promise.resolve(UNSENT);
1554
1667
  return new Promise((resolve, reject) => {
1555
- const p = { resolve, reject, method: init.method, replayable, acked: false, trace: tr };
1668
+ const p = { id, resolve, reject, method: init.method, replayable, mark: place, backstopAt: performance.now() + remaining + THREAD_GRACE_MS, trace: tr };
1556
1669
  const signal = init.signal;
1557
1670
  if (signal !== undefined) {
1558
1671
  p.signal = signal;
@@ -1561,47 +1674,59 @@ class TransportThread {
1561
1674
  return;
1562
1675
  this.inflight.delete(id);
1563
1676
  this.release(p);
1564
- try {
1565
- this.port.postMessage({ t: "cancel", id });
1677
+ const queued = this.outbox.findIndex((b) => b.id === id);
1678
+ if (queued >= 0)
1679
+ this.outbox.splice(queued, 1);
1680
+ else {
1681
+ try {
1682
+ this.port.postMessage({ t: "cancel", id });
1683
+ }
1684
+ catch { }
1566
1685
  }
1567
- catch { }
1568
1686
  reject(signal.reason);
1569
1687
  };
1570
1688
  signal.addEventListener("abort", p.onAbort, { once: true });
1571
1689
  }
1572
- p.backstop = setTimeout(() => {
1573
- this.poll();
1574
- if (!this.inflight.has(id))
1575
- return;
1576
- this.inflight.delete(id);
1577
- this.release(p);
1578
- try {
1579
- this.port.postMessage({ t: "cancel", id });
1580
- }
1581
- catch { }
1582
- reject(timeoutReason());
1583
- this.close("no answer from the transport thread");
1584
- }, remaining + THREAD_GRACE_MS);
1585
- safely(() => { p.backstop?.unref?.(); });
1586
1690
  this.inflight.set(id, p);
1587
1691
  this.ref();
1588
- try {
1589
- const paced = this.pace === "all" || (this.pace === "creates" && init.method === "POST" && CREATE_PATH.test(target.path));
1590
- this.port.postMessage({ t: "req", id, headers, sensitive: ["authorization"], body: init.body, timeoutMs: remaining, replayable, paced });
1692
+ const paced = this.pace === "all" || (this.pace === "creates" && init.method === "POST" && CREATE_PATH.test(target.path));
1693
+ this.outbox.push({ id, msg: { t: "req", id, mark: place, headers, sensitive: ["authorization"], body: init.body, timeoutMs: remaining, replayable, paced }, p, resolve, trace: tr });
1694
+ if (!this.turnOpen) {
1695
+ this.turnOpen = true;
1696
+ queueMicrotask(() => { this.turnOpen = false; this.handOver(); });
1697
+ this.handOver();
1591
1698
  }
1592
- catch {
1593
- this.inflight.delete(id);
1594
- this.release(p);
1595
- resolve(UNSENT);
1596
- return;
1699
+ else if (this.outbox.length >= OUTBOX_GROUP)
1700
+ this.handOver();
1701
+ });
1702
+ }
1703
+ handOver() {
1704
+ if (this.outbox.length === 0)
1705
+ return;
1706
+ const batch = this.outbox;
1707
+ this.outbox = [];
1708
+ try {
1709
+ this.port.postMessage(batch.length === 1 ? batch[0].msg : { t: "reqs", list: batch.map((b) => b.msg) });
1710
+ this.posts++;
1711
+ }
1712
+ catch {
1713
+ for (const b of batch) {
1714
+ if (!this.inflight.has(b.id))
1715
+ continue;
1716
+ this.inflight.delete(b.id);
1717
+ this.release(b.p);
1718
+ b.resolve(UNSENT);
1597
1719
  }
1598
- if (tr !== undefined) {
1599
- (0, trace_js_1.mark)(tr, "sub");
1600
- tr.via = "thread";
1720
+ return;
1721
+ }
1722
+ for (const b of batch)
1723
+ if (b.trace !== undefined) {
1724
+ (0, trace_js_1.mark)(b.trace, "sub");
1725
+ b.trace.via = "thread";
1601
1726
  }
1602
- });
1603
1727
  }
1604
1728
  }
1729
+ const OUTBOX_GROUP = 8;
1605
1730
  let transportThread;
1606
1731
  function threadFor(origin) {
1607
1732
  const t = transportThread;
@@ -1609,6 +1734,55 @@ function threadFor(origin) {
1609
1734
  return undefined;
1610
1735
  return t.ready() && !lookupReplaced() ? t : undefined;
1611
1736
  }
1737
+ function startingThread(origin) {
1738
+ const t = transportThread;
1739
+ if (t === undefined || t.origin !== origin || lookupReplaced())
1740
+ return undefined;
1741
+ return t.starting() ? t : undefined;
1742
+ }
1743
+ let raceTraced = false;
1744
+ function firstUp(t, pool, deadline, signal) {
1745
+ const first = !raceTraced;
1746
+ if (first) {
1747
+ raceTraced = true;
1748
+ (0, trace_js_1.traceEvent)("h2:race");
1749
+ }
1750
+ const stop = new AbortController();
1751
+ const both = signal === undefined ? stop.signal : AbortSignal.any([signal, stop.signal]);
1752
+ return new Promise((resolve, reject) => {
1753
+ let done = false;
1754
+ const forget = t.whenStarted(() => {
1755
+ if (done)
1756
+ return;
1757
+ const ready = threadFor(t.origin);
1758
+ if (ready === undefined)
1759
+ return;
1760
+ done = true;
1761
+ if (first)
1762
+ (0, trace_js_1.traceEvent)("h2:race-thread");
1763
+ stop.abort();
1764
+ resolve({ thread: ready });
1765
+ });
1766
+ pool.acquire(deadline, both).then((entry) => {
1767
+ if (done) {
1768
+ if (entry !== null)
1769
+ pool.end(entry);
1770
+ return;
1771
+ }
1772
+ done = true;
1773
+ forget();
1774
+ if (first)
1775
+ (0, trace_js_1.traceEvent)("h2:race-pool");
1776
+ resolve({ entry });
1777
+ }, (err) => {
1778
+ if (done)
1779
+ return;
1780
+ done = true;
1781
+ forget();
1782
+ reject(err);
1783
+ });
1784
+ });
1785
+ }
1612
1786
  function ownFile() {
1613
1787
  const frame = (new Error().stack ?? "").split("\n")[1] ?? "";
1614
1788
  const m = /\((.*):\d+:\d+\)\s*$/.exec(frame) ?? /at (.*):\d+:\d+\s*$/.exec(frame);
@@ -1646,11 +1820,12 @@ function startTransportThread(origin, onFail) {
1646
1820
  const knobs = threadKnobs();
1647
1821
  const file = typeof seam.transportFile === "string" ? seam.transportFile : here.replace(/http2\.js$/, "transport-worker.js");
1648
1822
  const { port1, port2 } = new node_worker_threads_1.MessageChannel();
1823
+ const acks = new Int32Array(new SharedArrayBuffer(4 * THREAD_MARKS));
1649
1824
  const worker = new node_worker_threads_1.Worker(file.startsWith("file:") ? new URL(file) : file, {
1650
1825
  workerData: {
1651
- promigenceTransport: true, port: port2, origin: origin.origin, sessions: knobs.sessions, gapUs: knobs.gapUs,
1826
+ promigenceTransport: true, port: port2, acks, origin: origin.origin, sessions: knobs.sessions, gapUs: knobs.gapUs,
1652
1827
  firstUseMs: exports.tuning.loadFirstUseMs, idleMs: IDLE_CLOSE_MS, connectTimeoutMs: CONNECT_TIMEOUT_MS,
1653
- lookupTimeoutMs: EARLY_TIMEOUT_MS, trace: trace_js_1.TRACE_ON,
1828
+ lookupTimeoutMs: LOOKUP_FIRST_MS, lookupTries: LOOKUP_TRIES, lookupAtMs: LOOKUP_AT_MS, trace: trace_js_1.TRACE_ON,
1654
1829
  servers: exports.tuning.earlyServers ?? (Array.isArray(seam.servers) ? seam.servers : undefined),
1655
1830
  hostsFile: exports.tuning.earlyHostsFile ?? (typeof seam.hostsFile === "string" ? seam.hostsFile : undefined),
1656
1831
  distrustedForTest: ca_guard_js_1.__caGuardInternals.testHashes(),
@@ -1661,13 +1836,18 @@ function startTransportThread(origin, onFail) {
1661
1836
  });
1662
1837
  worker.unref();
1663
1838
  port1.unref();
1664
- const t = new TransportThread(origin.origin, worker, port1);
1839
+ const t = new TransportThread(origin.origin, worker, port1, acks);
1665
1840
  t.pace = knobs.pace;
1666
1841
  t.onFail = onFail;
1667
1842
  worker.on("error", () => { t.poll(); t.close("error"); });
1668
1843
  worker.on("exit", () => { t.poll(); t.close("exit"); });
1669
1844
  transportThread = t;
1670
1845
  (0, trace_js_1.traceEvent)("thread:start");
1846
+ (0, trace_js_1.onTraceFlush)(() => {
1847
+ t.poll();
1848
+ for (const e of earlies.values())
1849
+ pollEarly(e);
1850
+ });
1671
1851
  }
1672
1852
  catch {
1673
1853
  transportThread = undefined;
@@ -1683,6 +1863,18 @@ function knownPrefix(url) {
1683
1863
  }
1684
1864
  return undefined;
1685
1865
  }
1866
+ let lastTargetUrl;
1867
+ let lastTargetRead;
1868
+ let targetReads = 0;
1869
+ function lastTarget(url) {
1870
+ if (url === lastTargetUrl)
1871
+ return lastTargetRead;
1872
+ targetReads++;
1873
+ const target = targetOf(url, knownPrefix(url), remember);
1874
+ lastTargetUrl = url;
1875
+ lastTargetRead = target;
1876
+ return target;
1877
+ }
1686
1878
  function remember(p) {
1687
1879
  for (let i = 0; i < prefixes.length; i++)
1688
1880
  if (prefixes[i].text === p.text)
@@ -1718,7 +1910,7 @@ class Http2Transport {
1718
1910
  const deadline = performance.now() + init.timeoutMs;
1719
1911
  let target;
1720
1912
  try {
1721
- target = targetOf(url, knownPrefix(url), remember);
1913
+ target = lastTarget(url);
1722
1914
  }
1723
1915
  catch {
1724
1916
  return this.viaFetch(url, init, deadline);
@@ -1742,8 +1934,34 @@ class Http2Transport {
1742
1934
  const pool = poolFor(target.origin);
1743
1935
  if (!pool.usable())
1744
1936
  return this.viaFetch(url, init, deadline);
1937
+ let raced = false;
1745
1938
  for (;;) {
1746
- const entry = pool.tryAcquire() ?? await pool.acquire(deadline, init.signal);
1939
+ let entry = pool.tryAcquire();
1940
+ if (entry === undefined) {
1941
+ const starting = raced || resends > 0 || replays > 0 ? undefined : startingThread(target.origin);
1942
+ if (starting !== undefined) {
1943
+ raced = true;
1944
+ const won = await firstUp(starting, pool, deadline, init.signal);
1945
+ if (won.thread !== undefined) {
1946
+ if (tr !== undefined)
1947
+ (0, trace_js_1.mark)(tr, "race");
1948
+ const out = await won.thread.send(target, init, replayable, deadline);
1949
+ if (out === VIA_FETCH)
1950
+ return this.viaFetch(url, init, deadline);
1951
+ if (!(out instanceof HandBack))
1952
+ return out;
1953
+ resends = out.resends;
1954
+ replays = out.replays;
1955
+ if (tr !== undefined)
1956
+ delete tr.via;
1957
+ continue;
1958
+ }
1959
+ entry = won.entry;
1960
+ }
1961
+ else {
1962
+ entry = await pool.acquire(deadline, init.signal);
1963
+ }
1964
+ }
1747
1965
  if (tr !== undefined)
1748
1966
  (0, trace_js_1.mark)(tr, "acq");
1749
1967
  if (entry === null)
@@ -1779,6 +1997,15 @@ class Http2Transport {
1779
1997
  }
1780
1998
  }
1781
1999
  exports.Http2Transport = Http2Transport;
2000
+ let sharedFallback;
2001
+ let shared;
2002
+ function sharedTransport(fallback) {
2003
+ if (shared === undefined || sharedFallback !== fallback) {
2004
+ shared = new Http2Transport(fallback);
2005
+ sharedFallback = fallback;
2006
+ }
2007
+ return shared;
2008
+ }
1782
2009
  function warmTransport() {
1783
2010
  const res = toResponse(200, { "content-type": "application/json" }, Buffer.from("{}"), "POST");
1784
2011
  res.header("server-timing");
@@ -1791,6 +2018,9 @@ function warmTransport() {
1791
2018
  exchange({ end() { } }, undefined, { origin: none, host: none, path: none }, undefined, 0).catch(() => { });
1792
2019
  }
1793
2020
  exports.__http2Internals = {
2021
+ targetReads() {
2022
+ return targetReads;
2023
+ },
1794
2024
  pools() {
1795
2025
  return [...pools.values()].map((p) => ({
1796
2026
  origin: p.origin.origin,
@@ -1831,11 +2061,17 @@ exports.__http2Internals = {
1831
2061
  if (t === undefined)
1832
2062
  return undefined;
1833
2063
  t.poll();
1834
- return { state: t.state, sessions: t.sessions, limit: t.limit, answered: t.answered, everReady: t.everReady, closedReason: t.closedReason };
2064
+ 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 };
1835
2065
  },
1836
2066
  threadKnobs(env) {
1837
2067
  return threadKnobs(env);
1838
2068
  },
2069
+ wedgeThreadForTest() {
2070
+ try {
2071
+ transportThread?.port.postMessage({ t: "wedge-for-test" });
2072
+ }
2073
+ catch { }
2074
+ },
1839
2075
  crashThreadForTest() {
1840
2076
  try {
1841
2077
  transportThread?.port.postMessage({ t: "crash-for-test" });
@@ -1874,6 +2110,15 @@ function isMainThread() {
1874
2110
  return false;
1875
2111
  }
1876
2112
  }
2113
+ function standby(url) {
2114
+ try {
2115
+ (0, trace_js_1.traceEvent)("h2:standby");
2116
+ warmTls();
2117
+ poolFor(url.origin).warm(exports.tuning.loadFirstUseMs);
2118
+ }
2119
+ catch {
2120
+ }
2121
+ }
1877
2122
  function connectAtLoad() {
1878
2123
  try {
1879
2124
  if (!isNode() || Number(process.versions.node.split(".")[0]) < 22)
@@ -1894,6 +2139,8 @@ function connectAtLoad() {
1894
2139
  if (url.protocol === "https:" && !(0, dispatcher_js_1.customDispatcher)() && earlyPlatform() && v4First() && !threadOff && earlySeam().transportThread !== false) {
1895
2140
  startTransportThread(url, () => { if (loadTurnRan)
1896
2141
  warmAtLoad(); });
2142
+ if ((0, node_net_1.isIP)(host) === 0)
2143
+ earlyLookupFor(host);
1897
2144
  }
1898
2145
  else if (url.protocol === "https:" && !(0, dispatcher_js_1.customDispatcher)() && (0, node_net_1.isIP)(host) === 0 && earlyPlatform() && v4First())
1899
2146
  earlyLookupFor(host);
@@ -1912,10 +2159,27 @@ function connectAtLoad() {
1912
2159
  const url = new URL(raw ? raw : core_js_1.DEFAULT_API_URL);
1913
2160
  if (url.protocol !== "https:" || (0, dispatcher_js_1.customDispatcher)())
1914
2161
  return;
1915
- if (transportThread !== undefined && transportThread.origin === url.origin) {
1916
- transportThread.poll();
1917
- if (transportThread.state !== "closed" || transportThread.everReady)
2162
+ const t = transportThread;
2163
+ if (t !== undefined && t.origin === url.origin) {
2164
+ t.poll();
2165
+ if (t.everReady)
1918
2166
  return;
2167
+ if (t.state !== "closed") {
2168
+ const check = () => {
2169
+ t.poll();
2170
+ if (t.everReady || t.state === "closed")
2171
+ return;
2172
+ const wait = t.startedAt + exports.tuning.standbyAfterMs - performance.now();
2173
+ if (wait > 0) {
2174
+ const timer = setTimeout(check, wait);
2175
+ timer.unref?.();
2176
+ return;
2177
+ }
2178
+ standby(url);
2179
+ };
2180
+ check();
2181
+ return;
2182
+ }
1919
2183
  }
1920
2184
  warmTls();
1921
2185
  poolFor(url.origin).warm(exports.tuning.loadFirstUseMs);