Skip to content

Commit 7414e62

Browse files
committed
Add rate limits
1 parent 1ac4549 commit 7414e62

File tree

6 files changed

+48
-33
lines changed

6 files changed

+48
-33
lines changed

dev-packages/node-integration-tests/suites/thread-blocked-native/basic-multiple.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ setTimeout(() => {
1111
Sentry.init({
1212
dsn: process.env.SENTRY_DSN,
1313
release: '1.0',
14-
integrations: [eventLoopBlockIntegration({ maxBlockedEvents: 2 })],
14+
integrations: [eventLoopBlockIntegration({ maxEventsPerHour: 2 })],
1515
});
1616

1717
setTimeout(() => {

dev-packages/node-integration-tests/suites/thread-blocked-native/stop-and-start.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ function longWorkIgnored() {
2929
}
3030

3131
setTimeout(() => {
32-
threadBlocked.stopWorker();
32+
threadBlocked.stop();
3333

3434
setTimeout(() => {
3535
longWorkIgnored();
3636

3737
setTimeout(() => {
38-
threadBlocked.startWorker();
38+
threadBlocked.start();
3939

4040
setTimeout(() => {
4141
longWork();

dev-packages/node-integration-tests/suites/thread-blocked-native/test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ describe('Thread Blocked Native', { timeout: 30_000 }, () => {
122122
.completed();
123123
});
124124

125-
test('multiple events via maxBlockedEvents', async () => {
125+
test('multiple events via maxEventsPerHour', async () => {
126126
await createRunner(__dirname, 'basic-multiple.mjs')
127127
.withMockSentryServer()
128128
.expect({ event: ANR_EVENT_WITH_DEBUG_META('basic-multiple') })

packages/node-native/src/common.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ export interface ThreadBlockedIntegrationOptions {
1010
*/
1111
threshold: number;
1212
/**
13-
* Maximum number of blocked events to send.
13+
* Maximum number of blocked events to send per clock hour.
1414
*
1515
* Defaults to 1.
1616
*/
17-
maxBlockedEvents: number;
17+
maxEventsPerHour: number;
1818
/**
1919
* Tags to include with blocked events.
2020
*/

packages/node-native/src/event-loop-block-integration.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import { POLL_RATIO } from './common';
99

1010
const { isPromise } = types;
1111

12-
const DEFAULT_THRESHOLD = 1_000;
12+
const DEFAULT_THRESHOLD_MS = 1_000;
1313

1414
function log(message: string, ...args: unknown[]): void {
15-
logger.log(`[Thread Blocked] ${message}`, ...args);
15+
logger.log(`[Sentry Block Event Loop] ${message}`, ...args);
1616
}
1717

1818
/**
@@ -32,15 +32,15 @@ async function getContexts(client: NodeClient): Promise<Contexts> {
3232

3333
const INTEGRATION_NAME = 'ThreadBlocked';
3434

35-
type ThreadBlockedInternal = { startWorker: () => void; stopWorker: () => void };
35+
type ThreadBlockedInternal = { start: () => void; stop: () => void };
3636

3737
const _eventLoopBlockIntegration = ((options: Partial<ThreadBlockedIntegrationOptions> = {}) => {
3838
let worker: Promise<() => void> | undefined;
3939
let client: NodeClient | undefined;
4040

4141
return {
4242
name: INTEGRATION_NAME,
43-
startWorker: () => {
43+
start: () => {
4444
if (worker) {
4545
return;
4646
}
@@ -49,7 +49,7 @@ const _eventLoopBlockIntegration = ((options: Partial<ThreadBlockedIntegrationOp
4949
worker = _startWorker(client, options);
5050
}
5151
},
52-
stopWorker: () => {
52+
stop: () => {
5353
if (worker) {
5454
// eslint-disable-next-line @typescript-eslint/no-floating-promises
5555
worker.then(stop => {
@@ -58,12 +58,11 @@ const _eventLoopBlockIntegration = ((options: Partial<ThreadBlockedIntegrationOp
5858
});
5959
}
6060
},
61-
async afterAllSetup(initClient: NodeClient) {
61+
afterAllSetup(initClient: NodeClient) {
6262
client = initClient;
6363

6464
registerThread();
65-
66-
this.startWorker();
65+
this.start();
6766
},
6867
} as Integration & ThreadBlockedInternal;
6968
}) satisfies IntegrationFn;
@@ -140,8 +139,8 @@ async function _startWorker(
140139
dist: initOptions.dist,
141140
sdkMetadata,
142141
appRootPath: integrationOptions.appRootPath,
143-
threshold: integrationOptions.threshold || DEFAULT_THRESHOLD,
144-
maxBlockedEvents: integrationOptions.maxBlockedEvents || 1,
142+
threshold: integrationOptions.threshold || DEFAULT_THRESHOLD_MS,
143+
maxEventsPerHour: integrationOptions.maxEventsPerHour || 1,
145144
staticTags: integrationOptions.staticTags || {},
146145
contexts,
147146
};
@@ -207,13 +206,13 @@ export function disableBlockedDetectionForCallback<T>(callback: () => T | Promis
207206
return callback();
208207
}
209208

210-
integration.stopWorker();
209+
integration.stop();
211210

212211
const result = callback();
213212
if (isPromise(result)) {
214-
return result.finally(() => integration.startWorker());
213+
return result.finally(() => integration.start());
215214
}
216215

217-
integration.startWorker();
216+
integration.start();
218217
return result;
219218
}

packages/node-native/src/event-loop-block-watchdog.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const {
2424
dist,
2525
dsn,
2626
environment,
27-
maxBlockedEvents,
27+
maxEventsPerHour,
2828
release,
2929
sdkMetadata,
3030
staticTags: tags,
@@ -33,22 +33,48 @@ const {
3333

3434
const pollInterval = threshold / POLL_RATIO
3535
const triggeredThreads = new Set<string>();
36-
let sentAnrEvents = 0;
3736

3837
function log(...msg: unknown[]): void {
3938
if (debug) {
4039
// eslint-disable-next-line no-console
41-
console.log('[Sentry Blocked Watchdog]', ...msg);
40+
console.log('[Sentry Block Event Loop Watchdog]', ...msg);
4241
}
4342
}
4443

44+
function createRateLimiter(maxEventsPerHour: number): () => boolean {
45+
let currentHour = 0;
46+
let currentCount = 0;
47+
48+
return function isRateLimited(): boolean {
49+
const hour = new Date().getHours();
50+
51+
if (hour !== currentHour) {
52+
currentHour = hour;
53+
currentCount = 0;
54+
}
55+
56+
if (currentCount >= maxEventsPerHour) {
57+
if (currentCount === maxEventsPerHour) {
58+
currentCount += 1;
59+
log(`Rate limit reached: ${currentCount} events in this hour`);
60+
}
61+
return true;
62+
}
63+
64+
currentCount += 1;
65+
return false;
66+
};
67+
68+
}
69+
4570
const url = getEnvelopeEndpointWithUrlEncodedAuth(dsn, tunnel, sdkMetadata.sdk);
4671
const transport = makeNodeTransport({
4772
url,
4873
recordDroppedEvent: () => {
4974
//
5075
},
5176
});
77+
const isRateLimited = createRateLimiter(maxEventsPerHour);
5278

5379
async function sendAbnormalSession(serializedSession: Session | undefined): Promise<void> {
5480
if (!serializedSession) {
@@ -193,12 +219,10 @@ function getExceptionAndThreads(
193219
}
194220

195221
async function sendAnrEvent(crashedThreadId: string): Promise<void> {
196-
if (sentAnrEvents >= maxBlockedEvents) {
222+
if (isRateLimited()) {
197223
return;
198224
}
199225

200-
sentAnrEvents += 1;
201-
202226
const threads = captureStackTrace<ThreadState>();
203227
const crashedThread = threads[crashedThreadId];
204228

@@ -235,14 +259,6 @@ async function sendAnrEvent(crashedThreadId: string): Promise<void> {
235259

236260
await transport.send(envelope);
237261
await transport.flush(2000);
238-
239-
if (sentAnrEvents >= maxBlockedEvents) {
240-
// Delay for 5 seconds so that stdio can flush if the main event loop ever restarts.
241-
// This is mainly for the benefit of logging or debugging.
242-
setTimeout(() => {
243-
process.exit(0);
244-
}, 5_000);
245-
}
246262
}
247263

248264
setInterval(async () => {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy