Skip to content

Commit a1dab3b

Browse files
authored
ref(node): parallelize disk io when reading source files for context lines (#7374)
1 parent 0957989 commit a1dab3b

File tree

1 file changed

+46
-6
lines changed

1 file changed

+46
-6
lines changed

packages/node/src/integrations/contextlines.ts

+46-6
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,48 @@ export class ContextLines implements Integration {
6262

6363
/** Processes an event and adds context lines */
6464
public async addSourceContext(event: Event): Promise<Event> {
65+
// keep a lookup map of which files we've already enqueued to read,
66+
// so we don't enqueue the same file multiple times which would cause multiple i/o reads
67+
const enqueuedReadSourceFileTasks: Record<string, number> = {};
68+
const readSourceFileTasks: Promise<string | null>[] = [];
69+
70+
if (this._contextLines > 0 && event.exception?.values) {
71+
for (const exception of event.exception.values) {
72+
if (!exception.stacktrace?.frames) {
73+
continue;
74+
}
75+
76+
// We want to iterate in reverse order as calling cache.get will bump the file in our LRU cache.
77+
// This ends up prioritizes source context for frames at the top of the stack instead of the bottom.
78+
for (let i = exception.stacktrace.frames.length - 1; i >= 0; i--) {
79+
const frame = exception.stacktrace.frames[i];
80+
// Call cache.get to bump the file to the top of the cache and ensure we have not already
81+
// enqueued a read operation for this filename
82+
if (
83+
frame.filename &&
84+
!enqueuedReadSourceFileTasks[frame.filename] &&
85+
!FILE_CONTENT_CACHE.get(frame.filename)
86+
) {
87+
readSourceFileTasks.push(_readSourceFile(frame.filename));
88+
enqueuedReadSourceFileTasks[frame.filename] = 1;
89+
}
90+
}
91+
}
92+
}
93+
94+
// check if files to read > 0, if so, await all of them to be read before adding source contexts.
95+
// Normally, Promise.all here could be short circuited if one of the promises rejects, but we
96+
// are guarding from that by wrapping the i/o read operation in a try/catch.
97+
if (readSourceFileTasks.length > 0) {
98+
await Promise.all(readSourceFileTasks);
99+
}
100+
101+
// Perform the same loop as above, but this time we can assume all files are in the cache
102+
// and attempt to add source context to frames.
65103
if (this._contextLines > 0 && event.exception?.values) {
66104
for (const exception of event.exception.values) {
67105
if (exception.stacktrace?.frames) {
68-
await this.addSourceContextToFrames(exception.stacktrace.frames);
106+
this.addSourceContextToFrames(exception.stacktrace.frames);
69107
}
70108
}
71109
}
@@ -74,18 +112,16 @@ export class ContextLines implements Integration {
74112
}
75113

76114
/** Adds context lines to frames */
77-
public async addSourceContextToFrames(frames: StackFrame[]): Promise<void> {
78-
const contextLines = this._contextLines;
79-
115+
public addSourceContextToFrames(frames: StackFrame[]): void {
80116
for (const frame of frames) {
81117
// Only add context if we have a filename and it hasn't already been added
82118
if (frame.filename && frame.context_line === undefined) {
83-
const sourceFile = await _readSourceFile(frame.filename);
119+
const sourceFile = FILE_CONTENT_CACHE.get(frame.filename);
84120

85121
if (sourceFile) {
86122
try {
87123
const lines = sourceFile.split('\n');
88-
addContextToFrame(lines, frame, contextLines);
124+
addContextToFrame(lines, frame, this._contextLines);
89125
} catch (e) {
90126
// anomaly, being defensive in case
91127
// unlikely to ever happen in practice but can definitely happen in theory
@@ -109,6 +145,10 @@ async function _readSourceFile(filename: string): Promise<string | null> {
109145
}
110146

111147
let content: string | null = null;
148+
149+
// Guard from throwing if readFile fails, this enables us to use Promise.all and
150+
// not have it short circuiting if one of the promises rejects + since context lines are added
151+
// on a best effort basis, we want to throw here anyways.
112152
try {
113153
content = await readTextFileAsync(filename);
114154
} catch (_) {

0 commit comments

Comments
 (0)