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