@blaxel/core 0.3.12-preview.254 → 0.3.12-preview.256
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/.tsbuildinfo +1 -1
- package/dist/cjs/common/h2fetch.js +7 -0
- package/dist/cjs/common/h2pool.js +19 -21
- package/dist/cjs/common/h2stats.js +118 -0
- package/dist/cjs/common/sentry.js +163 -2
- package/dist/cjs/common/sentry.test.js +165 -10
- package/dist/cjs/common/settings.js +2 -2
- package/dist/cjs/types/common/h2pool.d.ts +1 -0
- package/dist/cjs/types/common/h2stats.d.ts +17 -0
- package/dist/cjs/types/common/sentry.d.ts +4 -0
- package/dist/cjs-browser/.tsbuildinfo +1 -1
- package/dist/cjs-browser/common/h2stats.js +118 -0
- package/dist/cjs-browser/common/sentry.js +163 -2
- package/dist/cjs-browser/common/sentry.test.js +165 -10
- package/dist/cjs-browser/common/settings.js +2 -2
- package/dist/cjs-browser/types/common/h2pool.d.ts +1 -0
- package/dist/cjs-browser/types/common/h2stats.d.ts +17 -0
- package/dist/cjs-browser/types/common/sentry.d.ts +4 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/common/h2fetch.js +7 -0
- package/dist/esm/common/h2pool.js +19 -21
- package/dist/esm/common/h2stats.js +112 -0
- package/dist/esm/common/sentry.js +162 -2
- package/dist/esm/common/sentry.test.js +165 -10
- package/dist/esm/common/settings.js +2 -2
- package/dist/esm-browser/.tsbuildinfo +1 -1
- package/dist/esm-browser/common/h2stats.js +112 -0
- package/dist/esm-browser/common/sentry.js +162 -2
- package/dist/esm-browser/common/sentry.test.js +165 -10
- package/dist/esm-browser/common/settings.js +2 -2
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import http2 from "http2";
|
|
2
2
|
import { settings } from "./settings.js";
|
|
3
3
|
import { refH2SessionForActiveRequest } from "./h2ref.js";
|
|
4
|
+
import { recordH2Fallback } from "./h2stats.js";
|
|
4
5
|
// Statuses the Response constructor refuses to pair with a body. Handing it one
|
|
5
6
|
// throws, so a 204 (e.g. from a DELETE) would blow up instead of resolving.
|
|
6
7
|
const NULL_BODY_STATUSES = new Set([101, 103, 204, 205, 304]);
|
|
@@ -158,6 +159,7 @@ export async function withUploadSlot(domain, fn) {
|
|
|
158
159
|
export function createH2Fetch(session) {
|
|
159
160
|
return (input) => {
|
|
160
161
|
if (session.closed || session.destroyed) {
|
|
162
|
+
recordH2Fallback(new URL(input.url).hostname, "session-unusable");
|
|
161
163
|
return globalThis.fetch(input);
|
|
162
164
|
}
|
|
163
165
|
return _h2Request(session, input);
|
|
@@ -223,6 +225,7 @@ async function h2GatewayAttempt(pool, domain, send, fallback) {
|
|
|
223
225
|
let h2RequestCreated = false;
|
|
224
226
|
try {
|
|
225
227
|
return await send(session, {
|
|
228
|
+
domain,
|
|
226
229
|
onH2RequestCreated: () => {
|
|
227
230
|
h2RequestCreated = true;
|
|
228
231
|
},
|
|
@@ -241,6 +244,7 @@ async function h2GatewayAttempt(pool, domain, send, fallback) {
|
|
|
241
244
|
// No usable session: free the slot before falling back over a different
|
|
242
245
|
// connection (no stream opens on the shared session).
|
|
243
246
|
rel();
|
|
247
|
+
recordH2Fallback(domain, "no-session");
|
|
244
248
|
return await fallback();
|
|
245
249
|
}
|
|
246
250
|
catch (err) {
|
|
@@ -280,6 +284,7 @@ function h2RequestDirectInternal(session, url, init, options) {
|
|
|
280
284
|
// Pre-flight fallback (session unusable): no stream opens on the shared
|
|
281
285
|
// session, so free any held slot before going over globalThis.fetch.
|
|
282
286
|
options?.releaseSlot?.();
|
|
287
|
+
recordH2Fallback(options?.domain ?? new URL(url).hostname, "session-unusable");
|
|
283
288
|
return globalThis.fetch(url, init);
|
|
284
289
|
}
|
|
285
290
|
const parsed = new URL(url);
|
|
@@ -322,6 +327,7 @@ function h2RequestDirectInternal(session, url, init, options) {
|
|
|
322
327
|
// nothing has been sent on the wire yet). No stream opens on the shared
|
|
323
328
|
// session, so free any held slot before falling back.
|
|
324
329
|
options?.releaseSlot?.();
|
|
330
|
+
recordH2Fallback(options?.domain ?? parsed.hostname, "unsupported-body");
|
|
325
331
|
return globalThis.fetch(url, init);
|
|
326
332
|
}
|
|
327
333
|
if (!h2Headers["content-length"]) {
|
|
@@ -401,6 +407,7 @@ function _h2Send(session, h2Headers, body, signal, fallbackUrl, fallbackInit, op
|
|
|
401
407
|
// H2 frames were sent. No stream opened on the shared session, so free
|
|
402
408
|
// the slot before retrying over globalThis.fetch.
|
|
403
409
|
releaseSlot();
|
|
410
|
+
recordH2Fallback(options?.domain ?? new URL(fallbackUrl).hostname, session.closed || session.destroyed ? "session-unusable" : "request-rejected");
|
|
404
411
|
globalThis.fetch(fallbackUrl, fallbackInit).then(resolve, reject);
|
|
405
412
|
return;
|
|
406
413
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { recordH2EstablishFailure } from "./h2stats.js";
|
|
1
2
|
const DEFAULT_MAX_IDLE_MS = 5_000;
|
|
2
3
|
const DEFAULT_PING_TIMEOUT_MS = 500;
|
|
3
4
|
/**
|
|
@@ -39,6 +40,22 @@ export class H2Pool {
|
|
|
39
40
|
this.attachEvictionListeners(domain, session);
|
|
40
41
|
return session;
|
|
41
42
|
}
|
|
43
|
+
startEstablish(domain) {
|
|
44
|
+
const pending = this.establish(domain)
|
|
45
|
+
.then((session) => {
|
|
46
|
+
this.cache(domain, session);
|
|
47
|
+
return session;
|
|
48
|
+
})
|
|
49
|
+
.catch((error) => {
|
|
50
|
+
recordH2EstablishFailure(domain, error);
|
|
51
|
+
return null;
|
|
52
|
+
})
|
|
53
|
+
.finally(() => {
|
|
54
|
+
this.inflight.delete(domain);
|
|
55
|
+
});
|
|
56
|
+
this.inflight.set(domain, pending);
|
|
57
|
+
return pending;
|
|
58
|
+
}
|
|
42
59
|
attachEvictionListeners(domain, session) {
|
|
43
60
|
const evict = () => {
|
|
44
61
|
// Only evict if this specific session is still the cached one.
|
|
@@ -157,16 +174,7 @@ export class H2Pool {
|
|
|
157
174
|
return;
|
|
158
175
|
if (this.inflight.has(domain))
|
|
159
176
|
return;
|
|
160
|
-
|
|
161
|
-
.then((session) => {
|
|
162
|
-
this.cache(domain, session);
|
|
163
|
-
return session;
|
|
164
|
-
})
|
|
165
|
-
.catch(() => null)
|
|
166
|
-
.finally(() => {
|
|
167
|
-
this.inflight.delete(domain);
|
|
168
|
-
});
|
|
169
|
-
this.inflight.set(domain, p);
|
|
177
|
+
void this.startEstablish(domain);
|
|
170
178
|
}
|
|
171
179
|
/**
|
|
172
180
|
* Synchronous cache check. Returns a live cached session or `null`.
|
|
@@ -214,17 +222,7 @@ export class H2Pool {
|
|
|
214
222
|
const freshCached = this.tryGet(domain);
|
|
215
223
|
if (freshCached)
|
|
216
224
|
return freshCached;
|
|
217
|
-
|
|
218
|
-
.then((session) => {
|
|
219
|
-
this.cache(domain, session);
|
|
220
|
-
return session;
|
|
221
|
-
})
|
|
222
|
-
.catch(() => null)
|
|
223
|
-
.finally(() => {
|
|
224
|
-
this.inflight.delete(domain);
|
|
225
|
-
});
|
|
226
|
-
this.inflight.set(domain, p);
|
|
227
|
-
return p;
|
|
225
|
+
return this.startEstablish(domain);
|
|
228
226
|
}
|
|
229
227
|
/** Close all sessions (for cleanup). */
|
|
230
228
|
closeAll() {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { logger } from "./logger.js";
|
|
2
|
+
import { reportH2TransportDegradation } from "./sentry.js";
|
|
3
|
+
const emptyReasons = () => ({
|
|
4
|
+
"no-session": 0,
|
|
5
|
+
"request-rejected": 0,
|
|
6
|
+
"session-unusable": 0,
|
|
7
|
+
"unsupported-body": 0,
|
|
8
|
+
});
|
|
9
|
+
const MAX_TRACKED_DOMAINS = 100;
|
|
10
|
+
const H2_DEBUG_STATS_SYMBOL = Symbol.for("blaxel.h2stats");
|
|
11
|
+
const h2DebugStatsEnabled = typeof process !== "undefined" && process.env?.BL_H2_DEBUG_STATS === "1";
|
|
12
|
+
function publishDebugSnapshot(snapshot) {
|
|
13
|
+
if (!h2DebugStatsEnabled)
|
|
14
|
+
return;
|
|
15
|
+
try {
|
|
16
|
+
globalThis[H2_DEBUG_STATS_SYMBOL] = snapshot;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Debug diagnostics must never change transport behavior.
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const emptyDomainStats = () => ({
|
|
23
|
+
establishFailures: 0,
|
|
24
|
+
fetchFallbacks: 0,
|
|
25
|
+
fallbacksByReason: emptyReasons(),
|
|
26
|
+
});
|
|
27
|
+
class H2TransportStatsStore {
|
|
28
|
+
totals = emptyDomainStats();
|
|
29
|
+
byDomain = new Map();
|
|
30
|
+
snapshot() {
|
|
31
|
+
return {
|
|
32
|
+
...this.clone(this.totals),
|
|
33
|
+
byDomain: Object.fromEntries([...this.byDomain].map(([domain, stats]) => [domain, this.clone(stats)])),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
reset() {
|
|
37
|
+
this.totals = emptyDomainStats();
|
|
38
|
+
this.byDomain.clear();
|
|
39
|
+
publishDebugSnapshot(this.snapshot());
|
|
40
|
+
}
|
|
41
|
+
/** @internal */
|
|
42
|
+
recordEstablishFailure(domain, error) {
|
|
43
|
+
this.totals.establishFailures++;
|
|
44
|
+
this.forDomain(domain).establishFailures++;
|
|
45
|
+
try {
|
|
46
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
47
|
+
logger.debug(`H2 session establishment failed for ${domain}: ${message}`);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Diagnostics must never change transport behavior.
|
|
51
|
+
}
|
|
52
|
+
reportH2TransportDegradation(domain, "establish-failure");
|
|
53
|
+
publishDebugSnapshot(this.snapshot());
|
|
54
|
+
}
|
|
55
|
+
/** @internal */
|
|
56
|
+
recordFallback(domain, reason) {
|
|
57
|
+
this.totals.fetchFallbacks++;
|
|
58
|
+
this.totals.fallbacksByReason[reason]++;
|
|
59
|
+
const stats = this.forDomain(domain);
|
|
60
|
+
stats.fetchFallbacks++;
|
|
61
|
+
stats.fallbacksByReason[reason]++;
|
|
62
|
+
try {
|
|
63
|
+
logger.debug(`H2 transport falling back to fetch for ${domain}: ${reason}`);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Diagnostics must never change transport behavior.
|
|
67
|
+
}
|
|
68
|
+
reportH2TransportDegradation(domain, reason);
|
|
69
|
+
publishDebugSnapshot(this.snapshot());
|
|
70
|
+
}
|
|
71
|
+
forDomain(domain) {
|
|
72
|
+
let stats = this.byDomain.get(domain);
|
|
73
|
+
if (stats) {
|
|
74
|
+
this.byDomain.delete(domain);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
if (this.byDomain.size >= MAX_TRACKED_DOMAINS) {
|
|
78
|
+
const oldest = this.byDomain.keys().next().value;
|
|
79
|
+
if (oldest !== undefined)
|
|
80
|
+
this.byDomain.delete(oldest);
|
|
81
|
+
}
|
|
82
|
+
stats = emptyDomainStats();
|
|
83
|
+
}
|
|
84
|
+
this.byDomain.set(domain, stats);
|
|
85
|
+
return stats;
|
|
86
|
+
}
|
|
87
|
+
clone(stats) {
|
|
88
|
+
return {
|
|
89
|
+
establishFailures: stats.establishFailures,
|
|
90
|
+
fetchFallbacks: stats.fetchFallbacks,
|
|
91
|
+
fallbacksByReason: { ...stats.fallbacksByReason },
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const store = new H2TransportStatsStore();
|
|
96
|
+
publishDebugSnapshot(store.snapshot());
|
|
97
|
+
/** @internal */
|
|
98
|
+
export function snapshotH2TransportStats() {
|
|
99
|
+
return store.snapshot();
|
|
100
|
+
}
|
|
101
|
+
/** @internal */
|
|
102
|
+
export function resetH2TransportStats() {
|
|
103
|
+
store.reset();
|
|
104
|
+
}
|
|
105
|
+
/** @internal */
|
|
106
|
+
export function recordH2EstablishFailure(domain, error) {
|
|
107
|
+
store.recordEstablishFailure(domain, error);
|
|
108
|
+
}
|
|
109
|
+
/** @internal */
|
|
110
|
+
export function recordH2Fallback(domain, reason) {
|
|
111
|
+
store.recordFallback(domain, reason);
|
|
112
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { crypto } from "./node.js";
|
|
1
2
|
import { settings } from "./settings.js";
|
|
2
3
|
const PACKAGE_LAYOUT_MARKERS = [
|
|
3
4
|
"/src/common/sentry.ts",
|
|
@@ -24,7 +25,11 @@ const SAFE_ERROR_NAMES = new Set([
|
|
|
24
25
|
"AggregateError",
|
|
25
26
|
]);
|
|
26
27
|
const MAX_IN_FLIGHT_EVENTS = 20;
|
|
28
|
+
const MAX_ACTIVE_H2_ROLLUPS = 20;
|
|
29
|
+
const MAX_CACHED_DOMAIN_TAGS = 100;
|
|
30
|
+
const MAX_H2_EVENTS_PER_PROCESS = 100;
|
|
27
31
|
const DELIVERY_TIMEOUT_MS = 500;
|
|
32
|
+
const H2_ROLLUP_WINDOW_MS = 60_000;
|
|
28
33
|
const SAFE_FILENAME_SEGMENT = /^[A-Za-z0-9._-]+$/;
|
|
29
34
|
let sentryInitialized = false;
|
|
30
35
|
let handlersRegistered = false;
|
|
@@ -32,6 +37,9 @@ let sentryConfig = null;
|
|
|
32
37
|
let flushPromise = null;
|
|
33
38
|
const capturedExceptions = new WeakSet();
|
|
34
39
|
const inFlightDeliveries = new Set();
|
|
40
|
+
const domainTagCache = new Map();
|
|
41
|
+
const h2DegradationRollups = new Map();
|
|
42
|
+
let h2EventsEmitted = 0;
|
|
35
43
|
/**
|
|
36
44
|
* Normalize stack filenames without resolving or exposing host filesystem data.
|
|
37
45
|
*/
|
|
@@ -204,6 +212,91 @@ function errorToSentryEvent(error, ownedFrames) {
|
|
|
204
212
|
},
|
|
205
213
|
};
|
|
206
214
|
}
|
|
215
|
+
function runtimeTag() {
|
|
216
|
+
const host = globalThis;
|
|
217
|
+
if (host.process?.versions?.bun)
|
|
218
|
+
return `bun/${host.process.versions.bun}`;
|
|
219
|
+
if (host.Deno?.version?.deno)
|
|
220
|
+
return `deno/${host.Deno.version.deno}`;
|
|
221
|
+
if (host.process?.versions?.node)
|
|
222
|
+
return `node/${host.process.versions.node}`;
|
|
223
|
+
return "browser";
|
|
224
|
+
}
|
|
225
|
+
function normalizeDomain(domain) {
|
|
226
|
+
const normalized = domain.trim().toLowerCase().replace(/\.$/, "");
|
|
227
|
+
return normalized.length > 0 ? normalized : null;
|
|
228
|
+
}
|
|
229
|
+
function blaxelEdgeDomainTag(domain) {
|
|
230
|
+
const edgeSuffixes = [
|
|
231
|
+
{ environment: "prod", suffix: ".bl.run" },
|
|
232
|
+
{ environment: "dev", suffix: ".runv2.blaxel.dev" },
|
|
233
|
+
];
|
|
234
|
+
for (const { environment, suffix } of edgeSuffixes) {
|
|
235
|
+
if (!domain.endsWith(suffix))
|
|
236
|
+
continue;
|
|
237
|
+
const region = domain.slice(0, -suffix.length).split(".").at(-1);
|
|
238
|
+
if (region && /^[a-z0-9-]+$/.test(region)) {
|
|
239
|
+
return `blaxel-edge:${environment}:${region}`;
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
function uncachedDomainTag(domain) {
|
|
246
|
+
const stableBlaxelDomains = {
|
|
247
|
+
"api.blaxel.ai": "blaxel-api:prod",
|
|
248
|
+
"api.blaxel.dev": "blaxel-api:dev",
|
|
249
|
+
"run.blaxel.ai": "blaxel-run:prod",
|
|
250
|
+
"run.blaxel.dev": "blaxel-run:dev",
|
|
251
|
+
};
|
|
252
|
+
const stableTag = stableBlaxelDomains[domain];
|
|
253
|
+
if (stableTag)
|
|
254
|
+
return stableTag;
|
|
255
|
+
const edgeTag = blaxelEdgeDomainTag(domain);
|
|
256
|
+
if (edgeTag)
|
|
257
|
+
return edgeTag;
|
|
258
|
+
if (!crypto)
|
|
259
|
+
return null;
|
|
260
|
+
try {
|
|
261
|
+
return `sha256:${crypto.createHash("sha256").update(domain).digest("hex")}`;
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function domainToTag(domain) {
|
|
268
|
+
if (domainTagCache.has(domain))
|
|
269
|
+
return domainTagCache.get(domain) ?? null;
|
|
270
|
+
const domainTag = uncachedDomainTag(domain);
|
|
271
|
+
if (domainTagCache.size >= MAX_CACHED_DOMAIN_TAGS) {
|
|
272
|
+
const oldestDomain = domainTagCache.keys().next().value;
|
|
273
|
+
if (oldestDomain !== undefined)
|
|
274
|
+
domainTagCache.delete(oldestDomain);
|
|
275
|
+
}
|
|
276
|
+
domainTagCache.set(domain, domainTag);
|
|
277
|
+
return domainTag;
|
|
278
|
+
}
|
|
279
|
+
function h2DegradationToSentryEvent(reason, domainTag, count) {
|
|
280
|
+
return {
|
|
281
|
+
event_id: generateEventId(),
|
|
282
|
+
timestamp: Date.now() / 1000,
|
|
283
|
+
platform: "javascript",
|
|
284
|
+
level: "warning",
|
|
285
|
+
environment: settings.env,
|
|
286
|
+
release: `sdk-typescript@${settings.version}`,
|
|
287
|
+
message: `h2 transport degradation: ${reason}`,
|
|
288
|
+
fingerprint: ["h2-degradation", reason, domainTag],
|
|
289
|
+
tags: {
|
|
290
|
+
"blaxel.version": settings.version,
|
|
291
|
+
"blaxel.commit": settings.commit,
|
|
292
|
+
"blaxel.error_source": "h2-transport-degradation",
|
|
293
|
+
"blaxel.runtime": runtimeTag(),
|
|
294
|
+
reason,
|
|
295
|
+
domainTag,
|
|
296
|
+
},
|
|
297
|
+
extra: { count },
|
|
298
|
+
};
|
|
299
|
+
}
|
|
207
300
|
async function sendToSentry(event) {
|
|
208
301
|
if (!sentryConfig)
|
|
209
302
|
return;
|
|
@@ -245,12 +338,71 @@ async function sendToSentry(event) {
|
|
|
245
338
|
}
|
|
246
339
|
function scheduleDelivery(event) {
|
|
247
340
|
if (inFlightDeliveries.size >= MAX_IN_FLIGHT_EVENTS)
|
|
248
|
-
return;
|
|
341
|
+
return false;
|
|
249
342
|
// Contain rejections from setup that occurs before sendToSentry's internal
|
|
250
343
|
// fetch guard (for example, a host-provided AbortController implementation).
|
|
251
344
|
const delivery = sendToSentry(event).catch(() => undefined);
|
|
252
345
|
inFlightDeliveries.add(delivery);
|
|
253
346
|
void delivery.then(() => inFlightDeliveries.delete(delivery));
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
function emitH2DegradationRollup(key) {
|
|
350
|
+
const rollup = h2DegradationRollups.get(key);
|
|
351
|
+
if (!rollup)
|
|
352
|
+
return;
|
|
353
|
+
h2DegradationRollups.delete(key);
|
|
354
|
+
clearTimeout(rollup.timer);
|
|
355
|
+
if (h2EventsEmitted >= MAX_H2_EVENTS_PER_PROCESS)
|
|
356
|
+
return;
|
|
357
|
+
if (scheduleDelivery(h2DegradationToSentryEvent(rollup.reason, rollup.domainTag, rollup.count))) {
|
|
358
|
+
h2EventsEmitted++;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function flushH2DegradationRollups() {
|
|
362
|
+
for (const key of [...h2DegradationRollups.keys()]) {
|
|
363
|
+
emitH2DegradationRollup(key);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/** @internal */
|
|
367
|
+
export function reportH2TransportDegradation(domain, reason) {
|
|
368
|
+
if (!sentryInitialized || !sentryConfig)
|
|
369
|
+
return;
|
|
370
|
+
try {
|
|
371
|
+
if (h2EventsEmitted >= MAX_H2_EVENTS_PER_PROCESS)
|
|
372
|
+
return;
|
|
373
|
+
const normalizedDomain = normalizeDomain(domain);
|
|
374
|
+
if (!normalizedDomain)
|
|
375
|
+
return;
|
|
376
|
+
const domainTag = domainToTag(normalizedDomain);
|
|
377
|
+
if (!domainTag)
|
|
378
|
+
return;
|
|
379
|
+
const key = `${reason}\0${domainTag}`;
|
|
380
|
+
const existing = h2DegradationRollups.get(key);
|
|
381
|
+
if (existing) {
|
|
382
|
+
existing.count++;
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (h2DegradationRollups.size >= MAX_ACTIVE_H2_ROLLUPS)
|
|
386
|
+
return;
|
|
387
|
+
const timer = setTimeout(() => {
|
|
388
|
+
try {
|
|
389
|
+
emitH2DegradationRollup(key);
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
// Telemetry must never change transport behavior.
|
|
393
|
+
}
|
|
394
|
+
}, H2_ROLLUP_WINDOW_MS);
|
|
395
|
+
timer.unref?.();
|
|
396
|
+
h2DegradationRollups.set(key, {
|
|
397
|
+
count: 1,
|
|
398
|
+
domainTag,
|
|
399
|
+
reason,
|
|
400
|
+
timer,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
catch {
|
|
404
|
+
// Telemetry must never change transport behavior.
|
|
405
|
+
}
|
|
254
406
|
}
|
|
255
407
|
function captureException(error) {
|
|
256
408
|
if (!sentryInitialized || !sentryConfig || capturedExceptions.has(error)) {
|
|
@@ -318,7 +470,15 @@ export function initSentry() {
|
|
|
318
470
|
* Events are sent exactly once; flush never re-enqueues them.
|
|
319
471
|
*/
|
|
320
472
|
export async function flushSentry(timeout = DELIVERY_TIMEOUT_MS) {
|
|
321
|
-
if (!sentryInitialized
|
|
473
|
+
if (!sentryInitialized)
|
|
474
|
+
return;
|
|
475
|
+
try {
|
|
476
|
+
flushH2DegradationRollups();
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
// Flushing telemetry must never affect application shutdown.
|
|
480
|
+
}
|
|
481
|
+
if (inFlightDeliveries.size === 0)
|
|
322
482
|
return;
|
|
323
483
|
if (flushPromise) {
|
|
324
484
|
await flushPromise;
|