@@ -41,6 +41,21 @@ interface InspectorOptions {
4141 useWellFormattedUrl : boolean ;
4242}
4343
44+ /** Data related to the v8 inspector. */
45+ interface V8Data {
46+ session : inspector . Session ;
47+ // Options for behavior when interfacing with the Inspector API.
48+ inspectorOptions : InspectorOptions ;
49+ inspector : V8Inspector ;
50+ // Store the v8 setBreakpoint parameters for each v8 breakpoint so that later
51+ // the recorded parameters can be used to reset the breakpoints.
52+ setBreakpointsParams : {
53+ [
54+ v8BreakpointId : string
55+ ] : inspector . Debugger . SetBreakpointByUrlParameterType ;
56+ } ;
57+ }
58+
4459/**
4560 * In older versions of Node, the script source as seen by the Inspector
4661 * backend is wrapped in `require('module').wrapper`, and in new versions
@@ -68,7 +83,6 @@ export class InspectorDebugApi implements debugapi.DebugApi {
6883 fileStats : ScanStats ;
6984 breakpoints : { [ id : string ] : BreakpointData } = { } ;
7085 sourcemapper : SourceMapper ;
71- session : inspector . Session ;
7286 // TODO: listeners, scrpitmapper, location mapper and breakpointmapper can use
7387 // Map in the future after resolving Map.prototype.get(key) returns V or
7488 // undefined.
@@ -81,9 +95,9 @@ export class InspectorDebugApi implements debugapi.DebugApi {
8195 // stackdriver breakpoint id.
8296 breakpointMapper : { [ id : string ] : stackdriver . BreakpointId [ ] } = { } ;
8397 numBreakpoints = 0 ;
84- // Options for behavior when interfacing with the Inspector API.
85- private inspectorOptions : InspectorOptions ;
86- v8Inspector : V8Inspector ;
98+ numBreakpointHitsBeforeReset = 0 ;
99+ v8 : V8Data ;
100+
87101 constructor (
88102 logger : consoleLogLevel . Logger ,
89103 config : ResolvedDebugAgentConfig ,
@@ -94,25 +108,36 @@ export class InspectorDebugApi implements debugapi.DebugApi {
94108 this . config = config ;
95109 this . fileStats = jsFiles ;
96110 this . sourcemapper = sourcemapper ;
97- this . session = new inspector . Session ( ) ;
98- this . session . connect ( ) ;
99- this . session . on ( 'Debugger.scriptParsed' , script => {
111+ this . scriptMapper = { } ;
112+ this . v8 = this . createV8Data ( ) ;
113+ }
114+
115+ /** Creates a new V8 Debugging session and the related data. */
116+ private createV8Data ( ) : V8Data {
117+ const session = new inspector . Session ( ) ;
118+ session . connect ( ) ;
119+ session . on ( 'Debugger.scriptParsed' , script => {
100120 this . scriptMapper [ script . params . scriptId ] = script . params ;
101121 } ) ;
102- this . session . post ( 'Debugger.enable' ) ;
103- this . session . post ( 'Debugger.setBreakpointsActive' , { active : true } ) ;
104- this . session . on ( 'Debugger.paused' , message => {
122+ session . post ( 'Debugger.enable' ) ;
123+ session . post ( 'Debugger.setBreakpointsActive' , { active : true } ) ;
124+ session . on ( 'Debugger.paused' , message => {
105125 try {
106126 this . handleDebugPausedEvent ( message . params ) ;
107127 } catch ( error ) {
108128 this . logger . error ( error ) ;
109129 }
110130 } ) ;
111- this . inspectorOptions = {
112- // Well-Formatted URL is required in Node 10.11.1+.
113- useWellFormattedUrl : utils . satisfies ( process . version , '>10.11.0' ) ,
131+
132+ return {
133+ session,
134+ inspectorOptions : {
135+ // Well-Formatted URL is required in Node 10.11.1+.
136+ useWellFormattedUrl : utils . satisfies ( process . version , '>10.11.0' ) ,
137+ } ,
138+ inspector : new V8Inspector ( session ) ,
139+ setBreakpointsParams : { } ,
114140 } ;
115- this . v8Inspector = new V8Inspector ( this . session ) ;
116141 }
117142
118143 set (
@@ -219,7 +244,8 @@ export class InspectorDebugApi implements debugapi.DebugApi {
219244 if ( ! this . breakpointMapper [ breakpointData . id ] ) {
220245 // When breakpointmapper does not countain current v8/inspector breakpoint
221246 // id, we should remove this breakpoint from v8.
222- result = this . v8Inspector . removeBreakpoint ( breakpointData . id ) ;
247+ result = this . v8 . inspector . removeBreakpoint ( breakpointData . id ) ;
248+ delete this . v8 . setBreakpointsParams [ breakpointData . id ] ;
223249 }
224250 delete this . breakpoints [ breakpoint . id ] ;
225251 delete this . listeners [ breakpoint . id ] ;
@@ -297,7 +323,7 @@ export class InspectorDebugApi implements debugapi.DebugApi {
297323 }
298324
299325 disconnect ( ) : void {
300- this . session . disconnect ( ) ;
326+ this . v8 . session . disconnect ( ) ;
301327 }
302328
303329 numBreakpoints_ ( ) : number {
@@ -487,7 +513,7 @@ export class InspectorDebugApi implements debugapi.DebugApi {
487513 let v8BreakpointId ; // v8/inspector breakpoint id
488514 if ( ! this . locationMapper [ locationStr ] ) {
489515 // The first time when a breakpoint was set to this location.
490- const rawUrl = this . inspectorOptions . useWellFormattedUrl
516+ const rawUrl = this . v8 . inspectorOptions . useWellFormattedUrl
491517 ? `file://${ matchingScript } `
492518 : matchingScript ;
493519 // on windows on Node 11+, the url must start with file:///
@@ -496,17 +522,20 @@ export class InspectorDebugApi implements debugapi.DebugApi {
496522 process . platform === 'win32' && utils . satisfies ( process . version , '>=11' )
497523 ? rawUrl . replace ( / ^ f i l e : \/ \/ / , 'file:///' ) . replace ( / \\ / g, '/' )
498524 : rawUrl ;
499- const res = this . v8Inspector . setBreakpointByUrl ( {
525+ const params = {
500526 lineNumber : line - 1 ,
501527 url,
502528 columnNumber : column - 1 ,
503529 condition : breakpoint . condition || undefined ,
504- } ) ;
530+ } ;
531+ const res = this . v8 . inspector . setBreakpointByUrl ( params ) ;
505532 if ( res . error || ! res . response ) {
506533 // Error case.
507534 return null ;
508535 }
509536 v8BreakpointId = res . response . breakpointId ;
537+ this . v8 . setBreakpointsParams [ v8BreakpointId ] = params ;
538+
510539 this . locationMapper [ locationStr ] = [ ] ;
511540 this . breakpointMapper [ v8BreakpointId ] = [ ] ;
512541 } else {
@@ -593,7 +622,7 @@ export class InspectorDebugApi implements debugapi.DebugApi {
593622 const evaluatedExpressions = breakpoint . expressions . map ( exp => {
594623 // returnByValue is set to true here so that the JSON string of the
595624 // value will be returned to log.
596- const result = state . evaluate ( exp , frame , that . v8Inspector , true ) ;
625+ const result = state . evaluate ( exp , frame , that . v8 . inspector , true ) ;
597626 if ( result . error ) {
598627 return result . error ;
599628 } else {
@@ -608,7 +637,7 @@ export class InspectorDebugApi implements debugapi.DebugApi {
608637 breakpoint ,
609638 this . config ,
610639 this . scriptMapper ,
611- this . v8Inspector
640+ this . v8 . inspector
612641 ) ;
613642 if (
614643 breakpoint . location &&
@@ -642,5 +671,37 @@ export class InspectorDebugApi implements debugapi.DebugApi {
642671 } catch ( e ) {
643672 this . logger . warn ( 'Internal V8 error on breakpoint event: ' + e ) ;
644673 }
674+
675+ this . tryResetV8Debugger ( ) ;
676+ }
677+
678+ /**
679+ * Periodically resets breakpoints to prevent memory leaks in V8 (for holding
680+ * contexts of previous breakpoint hits).
681+ */
682+ private tryResetV8Debugger ( ) {
683+ this . numBreakpointHitsBeforeReset += 1 ;
684+ if (
685+ this . numBreakpointHitsBeforeReset < this . config . resetV8DebuggerThreshold
686+ ) {
687+ return ;
688+ }
689+ this . numBreakpointHitsBeforeReset = 0 ;
690+
691+ const storedParams = this . v8 . setBreakpointsParams ;
692+
693+ // Re-connect the session to clean the memory usage.
694+ this . disconnect ( ) ;
695+ this . scriptMapper = { } ;
696+ this . v8 = this . createV8Data ( ) ;
697+ this . v8 . setBreakpointsParams = storedParams ;
698+
699+ // Setting the v8 breakpoints again according to the stored parameters.
700+ for ( const params of Object . values ( storedParams ) ) {
701+ const res = this . v8 . inspector . setBreakpointByUrl ( params ) ;
702+ if ( res . error || ! res . response ) {
703+ this . logger . error ( 'Error upon re-setting breakpoint: ' + res ) ;
704+ }
705+ }
645706 }
646707}
0 commit comments