@@ -34,6 +34,32 @@ import * as utils from '../util/utils';
3434import * as debugapi from './debugapi' ;
3535import { 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+
3763/**
3864 * In older versions of Node, the script source as seen by the Inspector
3965 * backend is wrapped in `require('module').wrapper`, and in new versions
@@ -73,9 +99,8 @@ export class InspectorDebugApi implements debugapi.DebugApi {
7399 // stackdriver breakpoint id.
74100 breakpointMapper : { [ id : string ] : stackdriver . BreakpointId [ ] } = { } ;
75101 numBreakpoints = 0 ;
76-
77- // The wrapper class that interacts with the V8 debugger.
78- inspector : V8Inspector ;
102+ numBreakpointHitsBeforeReset = 0 ;
103+ v8 : V8Data ;
79104
80105 constructor (
81106 logger : consoleLogLevel . Logger ,
@@ -87,28 +112,36 @@ export class InspectorDebugApi implements debugapi.DebugApi {
87112 this . config = config ;
88113 this . fileStats = jsFiles ;
89114 this . sourcemapper = sourcemapper ;
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- }
105- }
106- ) ;
115+ this . scriptMapper = { } ;
116+ this . v8 = this . createV8Data ( ) ;
107117 }
108118
109- /** Disconnects and marks the current V8 data as not connected. */
110- disconnect ( ) : void {
111- this . inspector . detach ( ) ;
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 ) ;
133+ }
134+ } ) ;
135+
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+ } ;
112145 }
113146
114147 set (
@@ -235,7 +268,8 @@ export class InspectorDebugApi implements debugapi.DebugApi {
235268 if ( ! this . breakpointMapper [ breakpointData . id ] ) {
236269 // When breakpointmapper does not countain current v8/inspector breakpoint
237270 // id, we should remove this breakpoint from v8.
238- result = this . inspector . removeBreakpoint ( breakpointData . id ) ;
271+ result = this . v8 . inspector . removeBreakpoint ( breakpointData . id ) ;
272+ delete this . v8 . setBreakpointsParams [ breakpointData . id ] ;
239273 }
240274 delete this . breakpoints [ breakpoint . id ] ;
241275 delete this . listeners [ breakpoint . id ] ;
@@ -312,6 +346,10 @@ export class InspectorDebugApi implements debugapi.DebugApi {
312346 this . listeners [ breakpoint . id ] = { enabled : true , listener} ;
313347 }
314348
349+ disconnect ( ) : void {
350+ this . v8 . session . disconnect ( ) ;
351+ }
352+
315353 numBreakpoints_ ( ) : number {
316354 // Tracks the number of stackdriver breakpoints.
317355 return Object . keys ( this . breakpoints ) . length ;
@@ -499,7 +537,7 @@ export class InspectorDebugApi implements debugapi.DebugApi {
499537 let v8BreakpointId ; // v8/inspector breakpoint id
500538 if ( ! this . locationMapper [ locationStr ] ) {
501539 // The first time when a breakpoint was set to this location.
502- const rawUrl = this . inspector . shouldUseWellFormattedUrl ( )
540+ const rawUrl = this . v8 . inspectorOptions . useWellFormattedUrl
503541 ? `file://${ matchingScript } `
504542 : matchingScript ;
505543 // on windows on Node 11+, the url must start with file:///
@@ -508,17 +546,19 @@ export class InspectorDebugApi implements debugapi.DebugApi {
508546 process . platform === 'win32' && utils . satisfies ( process . version , '>=11' )
509547 ? rawUrl . replace ( / ^ f i l e : \/ \/ / , 'file:///' ) . replace ( / \\ / g, '/' )
510548 : rawUrl ;
511- const res = this . inspector . setBreakpointByUrl ( {
549+ const params = {
512550 lineNumber : line - 1 ,
513551 url,
514552 columnNumber : column - 1 ,
515553 condition : breakpoint . condition || undefined ,
516- } ) ;
554+ } ;
555+ const res = this . v8 . inspector . setBreakpointByUrl ( params ) ;
517556 if ( res . error || ! res . response ) {
518557 // Error case.
519558 return null ;
520559 }
521560 v8BreakpointId = res . response . breakpointId ;
561+ this . v8 . setBreakpointsParams [ v8BreakpointId ] = params ;
522562
523563 this . locationMapper [ locationStr ] = [ ] ;
524564 this . breakpointMapper [ v8BreakpointId ] = [ ] ;
@@ -606,7 +646,7 @@ export class InspectorDebugApi implements debugapi.DebugApi {
606646 const evaluatedExpressions = breakpoint . expressions . map ( exp => {
607647 // returnByValue is set to true here so that the JSON string of the
608648 // value will be returned to log.
609- const result = state . evaluate ( exp , frame , that . inspector , true ) ;
649+ const result = state . evaluate ( exp , frame , that . v8 . inspector , true ) ;
610650 if ( result . error ) {
611651 return result . error ;
612652 } else {
@@ -621,7 +661,7 @@ export class InspectorDebugApi implements debugapi.DebugApi {
621661 breakpoint ,
622662 this . config ,
623663 this . scriptMapper ,
624- this . inspector
664+ this . v8 . inspector
625665 ) ;
626666 if (
627667 breakpoint . location &&
@@ -655,5 +695,37 @@ export class InspectorDebugApi implements debugapi.DebugApi {
655695 } catch ( e ) {
656696 this . logger . warn ( 'Internal V8 error on breakpoint event: ' + e ) ;
657697 }
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+ }
658730 }
659731}
0 commit comments