@@ -5,9 +5,11 @@ main or worker threads from any other thread, even if event loops are blocked.
5
5
6
6
The module also provides a means to create a watchdog system to track event loop
7
7
blocking via periodic heartbeats. When the time from the last heartbeat crosses
8
- a threshold, JavaScript stack traces can be captured. The heartbeats can
9
- optionally include state information which is included with the corresponding
10
- stack trace.
8
+ a threshold, JavaScript stack traces can be captured.
9
+
10
+ For Node.js >= v24, this module can also capture state from ` AsyncLocalStorage `
11
+ at the time of stack trace capture, which can help provide context on what the
12
+ thread was working on when it became blocked.
11
13
12
14
This native module is used for Sentry's
13
15
[ Event Loop Blocked Detection] ( https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/event-loop-block/ )
@@ -70,7 +72,7 @@ Stack traces show where each thread is currently executing:
70
72
}
71
73
]
72
74
},
73
- ' 2' : { // Worker thread
75
+ ' 2' : { // Worker thread
74
76
frames : [
75
77
{
76
78
function: ' from' ,
@@ -107,23 +109,26 @@ Set up automatic detection of blocked event loops:
107
109
108
110
### 1. Set up thread heartbeats
109
111
110
- Send regular heartbeats with optional state information :
112
+ Send regular heartbeats:
111
113
112
114
``` ts
113
115
import {
114
116
registerThread ,
115
117
threadPoll ,
116
118
} from " @sentry-internal/node-native-stacktrace" ;
119
+ import { AsyncLocalStorage } from " node:async_hooks" ;
117
120
118
- // Register this thread
119
- registerThread ();
121
+ // Create async local storage for state tracking
122
+ const asyncLocalStorage = new AsyncLocalStorage ();
123
+ // Set some state in the async local storage
124
+ asyncLocalStorage .enterWith ({ someState: " value" });
120
125
121
- // Send heartbeats every 200ms with optional state
126
+ // Register this thread with async local storage
127
+ registerThread ({ asyncLocalStorage });
128
+
129
+ // Send heartbeats every 200ms
122
130
setInterval (() => {
123
- threadPoll ({
124
- endpoint: " /api/current-request" ,
125
- userId: getCurrentUserId (),
126
- });
131
+ threadPoll ();
127
132
}, 200 );
128
133
```
129
134
@@ -150,7 +155,7 @@ setInterval(() => {
150
155
151
156
console .error (` 🚨 Thread ${threadId } blocked for ${timeSinceLastSeen }ms ` );
152
157
console .error (" Stack trace:" , blockedThread .frames );
153
- console .error (" Last known state:" , blockedThread .state );
158
+ console .error (" Async state:" , blockedThread .asyncState );
154
159
}
155
160
}
156
161
}, 500 ); // Check every 500ms
@@ -162,21 +167,35 @@ setInterval(() => {
162
167
163
168
#### ` registerThread(threadName?: string): void `
164
169
165
- Registers the current thread for monitoring. Must be called from each thread you
166
- want to capture stack traces from.
170
+ #### ` registerThread(asyncStorage: AsyncStorageArgs, threadName?: string): void `
171
+
172
+ Registers the current thread for stack trace capture. Must be called from each
173
+ thread you want to capture stack traces from.
167
174
168
175
- ` threadName ` (optional): Name for the thread. Defaults to the current thread
169
176
ID.
177
+ - ` asyncStorage ` : ` AsyncStorageArgs ` to fetch state from ` AsyncLocalStorage ` on
178
+ stack trace capture.
179
+
180
+ ``` ts
181
+ type AsyncStorageArgs = {
182
+ // AsyncLocalStorage instance to fetch state from
183
+ asyncLocalStorage: AsyncLocalStorage <unknown >;
184
+ // Optional key to fetch specific property from the store object
185
+ storageKey? : string | symbol ;
186
+ };
187
+ ```
170
188
171
- #### ` captureStackTrace<State>(): Record<string, Thread<State >> `
189
+ #### ` captureStackTrace<State>(): Record<string, Thread<A, P >> `
172
190
173
191
Captures stack traces from all registered threads. Can be called from any thread
174
192
but will not capture the stack trace of the calling thread itself.
175
193
176
194
``` ts
177
- type Thread <S > = {
195
+ type Thread <A = unknown , P = unknown > = {
178
196
frames: StackFrame [];
179
- state? : S ;
197
+ asyncState? : A ;
198
+ pollState? : P ;
180
199
};
181
200
182
201
type StackFrame = {
@@ -187,16 +206,15 @@ type StackFrame = {
187
206
};
188
207
```
189
208
190
- #### ` threadPoll<State>(state ?: State, disableLastSeen ?: boolean ): void `
209
+ #### ` threadPoll<State>(disableLastSeen ?: boolean, pollState ?: object ): void `
191
210
192
- Sends a heartbeat from the current thread with optional state information. The
193
- state object will be serialized and included as a JavaScript object with the
194
- corresponding stack trace.
211
+ Sends a heartbeat from the current thread.
195
212
196
- - ` state ` (optional): An object containing state information to include with the
197
- stack trace.
198
213
- ` disableLastSeen ` (optional): If ` true ` , disables the tracking of the last
199
214
seen time for this thread.
215
+ - ` pollState ` (optional): An object containing state to include with the next
216
+ stack trace capture. This can be used instead of or in addition to
217
+ ` AsyncLocalStorage ` based state tracking.
200
218
201
219
#### ` getThreadsLastSeen(): Record<string, number> `
202
220
0 commit comments