Skip to content
This repository was archived by the owner on Apr 3, 2024. It is now read-only.

Commit 031a7ad

Browse files
Louis-YeJustinBeckwithmctavish
authored
fix: attach to v8 debugger session only when having active breakpoints (#975)
* fix: attach to v8 debugger session only when having active breakpoints * This PR also refactos the inspector-debugapi class so that the V8 related code is moved to v8inspector class. * Update comments to be more accurate Co-authored-by: Justin Beckwith <[email protected]> Co-authored-by: James McTavish <[email protected]>
1 parent abf4b70 commit 031a7ad

File tree

4 files changed

+198
-120
lines changed

4 files changed

+198
-120
lines changed

src/agent/config.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -359,10 +359,10 @@ export interface ResolvedDebugAgentConfig extends GoogleAuthOptions {
359359
apiUrl?: string;
360360

361361
/**
362-
* Number of times of the V8 breakpoint hits events before resetting the
363-
* breakpoints. This is to release the memory usage held by V8 engine for each
364-
* breakpoint hit to prevent the memory leak. The default value is specified
365-
* in defaultConfig.
362+
* Number of times the V8 pauses (could be breakpoint hits) before the
363+
* debugging session is reset. This is to release the memory usage held by V8
364+
* engine for each breakpoint hit to prevent the memory leak. The default
365+
* value is specified in defaultConfig.
366366
*/
367367
resetV8DebuggerThreshold: number;
368368
}

src/agent/v8/inspector-debugapi.ts

Lines changed: 29 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -34,32 +34,6 @@ import * as utils from '../util/utils';
3434
import * as debugapi from './debugapi';
3535
import {V8Inspector} from './v8inspector';
3636

37-
/**
38-
* An interface that describes options that set behavior when interacting with
39-
* the V8 Inspector API.
40-
*/
41-
interface InspectorOptions {
42-
/**
43-
* Whether to add a 'file://' prefix to a URL when setting breakpoints.
44-
*/
45-
useWellFormattedUrl: boolean;
46-
}
47-
48-
/** Data related to the v8 inspector. */
49-
interface V8Data {
50-
session: inspector.Session;
51-
// Options for behavior when interfacing with the Inspector API.
52-
inspectorOptions: InspectorOptions;
53-
inspector: V8Inspector;
54-
// Store the v8 setBreakpoint parameters for each v8 breakpoint so that later
55-
// the recorded parameters can be used to reset the breakpoints.
56-
setBreakpointsParams: {
57-
[
58-
v8BreakpointId: string
59-
]: inspector.Debugger.SetBreakpointByUrlParameterType;
60-
};
61-
}
62-
6337
/**
6438
* In older versions of Node, the script source as seen by the Inspector
6539
* backend is wrapped in `require('module').wrapper`, and in new versions
@@ -99,8 +73,9 @@ export class InspectorDebugApi implements debugapi.DebugApi {
9973
// stackdriver breakpoint id.
10074
breakpointMapper: {[id: string]: stackdriver.BreakpointId[]} = {};
10175
numBreakpoints = 0;
102-
numBreakpointHitsBeforeReset = 0;
103-
v8: V8Data;
76+
77+
// The wrapper class that interacts with the V8 debugger.
78+
inspector: V8Inspector;
10479

10580
constructor(
10681
logger: consoleLogLevel.Logger,
@@ -112,36 +87,28 @@ export class InspectorDebugApi implements debugapi.DebugApi {
11287
this.config = config;
11388
this.fileStats = jsFiles;
11489
this.sourcemapper = sourcemapper;
115-
this.scriptMapper = {};
116-
this.v8 = this.createV8Data();
117-
}
118-
119-
/** Creates a new V8 Debugging session and the related data. */
120-
private createV8Data(): V8Data {
121-
const session = new inspector.Session();
122-
session.connect();
123-
session.on('Debugger.scriptParsed', script => {
124-
this.scriptMapper[script.params.scriptId] = script.params;
125-
});
126-
session.post('Debugger.enable');
127-
session.post('Debugger.setBreakpointsActive', {active: true});
128-
session.on('Debugger.paused', message => {
129-
try {
130-
this.handleDebugPausedEvent(message.params);
131-
} catch (error) {
132-
this.logger.error(error);
90+
this.inspector = new V8Inspector(
91+
/* logger=*/ logger,
92+
/*useWellFormattedUrl=*/ utils.satisfies(process.version, '>10.11.0'),
93+
/*resetV8DebuggerThreshold=*/ this.config.resetV8DebuggerThreshold,
94+
/*onScriptParsed=*/
95+
scriptParams => {
96+
this.scriptMapper[scriptParams.scriptId] = scriptParams;
97+
},
98+
/*onPaused=*/
99+
messageParams => {
100+
try {
101+
this.handleDebugPausedEvent(messageParams);
102+
} catch (error) {
103+
this.logger.error(error);
104+
}
133105
}
134-
});
106+
);
107+
}
135108

136-
return {
137-
session,
138-
inspectorOptions: {
139-
// Well-Formatted URL is required in Node 10.11.1+.
140-
useWellFormattedUrl: utils.satisfies(process.version, '>10.11.0'),
141-
},
142-
inspector: new V8Inspector(session),
143-
setBreakpointsParams: {},
144-
};
109+
/** Disconnects and marks the current V8 data as not connected. */
110+
disconnect(): void {
111+
this.inspector.detach();
145112
}
146113

147114
set(
@@ -268,8 +235,7 @@ export class InspectorDebugApi implements debugapi.DebugApi {
268235
if (!this.breakpointMapper[breakpointData.id]) {
269236
// When breakpointmapper does not countain current v8/inspector breakpoint
270237
// id, we should remove this breakpoint from v8.
271-
result = this.v8.inspector.removeBreakpoint(breakpointData.id);
272-
delete this.v8.setBreakpointsParams[breakpointData.id];
238+
result = this.inspector.removeBreakpoint(breakpointData.id);
273239
}
274240
delete this.breakpoints[breakpoint.id];
275241
delete this.listeners[breakpoint.id];
@@ -346,10 +312,6 @@ export class InspectorDebugApi implements debugapi.DebugApi {
346312
this.listeners[breakpoint.id] = {enabled: true, listener};
347313
}
348314

349-
disconnect(): void {
350-
this.v8.session.disconnect();
351-
}
352-
353315
numBreakpoints_(): number {
354316
// Tracks the number of stackdriver breakpoints.
355317
return Object.keys(this.breakpoints).length;
@@ -537,7 +499,7 @@ export class InspectorDebugApi implements debugapi.DebugApi {
537499
let v8BreakpointId; // v8/inspector breakpoint id
538500
if (!this.locationMapper[locationStr]) {
539501
// The first time when a breakpoint was set to this location.
540-
const rawUrl = this.v8.inspectorOptions.useWellFormattedUrl
502+
const rawUrl = this.inspector.shouldUseWellFormattedUrl()
541503
? `file://${matchingScript}`
542504
: matchingScript;
543505
// on windows on Node 11+, the url must start with file:///
@@ -546,19 +508,17 @@ export class InspectorDebugApi implements debugapi.DebugApi {
546508
process.platform === 'win32' && utils.satisfies(process.version, '>=11')
547509
? rawUrl.replace(/^file:\/\//, 'file:///').replace(/\\/g, '/')
548510
: rawUrl;
549-
const params = {
511+
const res = this.inspector.setBreakpointByUrl({
550512
lineNumber: line - 1,
551513
url,
552514
columnNumber: column - 1,
553515
condition: breakpoint.condition || undefined,
554-
};
555-
const res = this.v8.inspector.setBreakpointByUrl(params);
516+
});
556517
if (res.error || !res.response) {
557518
// Error case.
558519
return null;
559520
}
560521
v8BreakpointId = res.response.breakpointId;
561-
this.v8.setBreakpointsParams[v8BreakpointId] = params;
562522

563523
this.locationMapper[locationStr] = [];
564524
this.breakpointMapper[v8BreakpointId] = [];
@@ -646,7 +606,7 @@ export class InspectorDebugApi implements debugapi.DebugApi {
646606
const evaluatedExpressions = breakpoint.expressions.map(exp => {
647607
// returnByValue is set to true here so that the JSON string of the
648608
// value will be returned to log.
649-
const result = state.evaluate(exp, frame, that.v8.inspector, true);
609+
const result = state.evaluate(exp, frame, that.inspector, true);
650610
if (result.error) {
651611
return result.error;
652612
} else {
@@ -661,7 +621,7 @@ export class InspectorDebugApi implements debugapi.DebugApi {
661621
breakpoint,
662622
this.config,
663623
this.scriptMapper,
664-
this.v8.inspector
624+
this.inspector
665625
);
666626
if (
667627
breakpoint.location &&
@@ -695,37 +655,5 @@ export class InspectorDebugApi implements debugapi.DebugApi {
695655
} catch (e) {
696656
this.logger.warn('Internal V8 error on breakpoint event: ' + e);
697657
}
698-
699-
this.tryResetV8Debugger();
700-
}
701-
702-
/**
703-
* Periodically resets breakpoints to prevent memory leaks in V8 (for holding
704-
* contexts of previous breakpoint hits).
705-
*/
706-
private tryResetV8Debugger() {
707-
this.numBreakpointHitsBeforeReset += 1;
708-
if (
709-
this.numBreakpointHitsBeforeReset < this.config.resetV8DebuggerThreshold
710-
) {
711-
return;
712-
}
713-
this.numBreakpointHitsBeforeReset = 0;
714-
715-
const storedParams = this.v8.setBreakpointsParams;
716-
717-
// Re-connect the session to clean the memory usage.
718-
this.disconnect();
719-
this.scriptMapper = {};
720-
this.v8 = this.createV8Data();
721-
this.v8.setBreakpointsParams = storedParams;
722-
723-
// Setting the v8 breakpoints again according to the stored parameters.
724-
for (const params of Object.values(storedParams)) {
725-
const res = this.v8.inspector.setBreakpointByUrl(params);
726-
if (res.error || !res.response) {
727-
this.logger.error('Error upon re-setting breakpoint: ' + res);
728-
}
729-
}
730658
}
731659
}

0 commit comments

Comments
 (0)