77 ArrayPrototypeMap,
88 ArrayPrototypePush,
99 ArrayPrototypeSlice,
10+ ArrayPrototypeSome,
1011 FunctionPrototypeBind,
1112 JSONStringify,
1213 NumberIsNaN,
@@ -86,9 +87,14 @@ const kInspectPortRegex = /^--inspect-port=(\d+)$/;
8687 * @typedef {object } Probe
8788 * @property {string } expr Expression to evaluate on hit.
8889 * @property {ProbeTarget } target User's original --probe request shape.
90+ * @property {number } maxHit Per-probe hit limit from --max-hit. Infinity when unlimited.
8991 * @property {number } hits Count of hits observed.
9092 */
9193
94+ function probeReachedLimit ( probe ) {
95+ return probe . hits >= probe . maxHit ;
96+ }
97+
9298function parseUnsignedInteger ( value , name , allowZero = false ) {
9399if ( typeof value !== 'string' || RegExpPrototypeExec ( kDigitsRegex , value ) === null ) {
94100throw new ERR_DEBUGGER_STARTUP_ERROR ( `Invalid ${ name } : ${ value } ` ) ;
@@ -371,6 +377,20 @@ function parseProbeTokens(tokens, args) {
371377break ;
372378case 'expr' :
373379throw new ERR_DEBUGGER_STARTUP_ERROR ( 'Unexpected --expr before --probe' ) ;
380+ case 'max-hit' : {
381+ if ( probes . length === 0 ) {
382+ throw new ERR_DEBUGGER_STARTUP_ERROR ( 'Unexpected --max-hit before --probe' ) ;
383+ }
384+ if ( token . value === undefined ) {
385+ throw new ERR_DEBUGGER_STARTUP_ERROR ( `Missing value for ${ token . rawName } ` ) ;
386+ }
387+ const probe = probes [ probes . length - 1 ] ;
388+ if ( probe . maxHit !== undefined ) {
389+ throw new ERR_DEBUGGER_STARTUP_ERROR ( 'Duplicate --max-hit for a single --probe' ) ;
390+ }
391+ probe . maxHit = parseUnsignedInteger ( token . value , 'max-hit' ) ;
392+ break ;
393+ }
374394default :
375395if ( probes . length > 0 ) {
376396throw new ERR_DEBUGGER_STARTUP_ERROR (
@@ -458,7 +478,9 @@ class ProbeInspectorSession {
458478this . completionPromise = promise ;
459479this . resolveCompletion = resolve ;
460480/** @type {Probe[] } */
461- this . probes = ArrayPrototypeMap ( options . probes , ( { expr, target } ) => ( { expr, target, hits : 0 } ) ) ;
481+ this . probes = ArrayPrototypeMap ( options . probes ,
482+ ( { expr, target, maxHit } ) =>
483+ ( { expr, target, maxHit : maxHit ?? Infinity , hits : 0 } ) ) ;
462484this . onChildOutput = FunctionPrototypeBind ( this . onChildOutput , this ) ;
463485this . onChildExit = FunctionPrototypeBind ( this . onChildExit , this ) ;
464486this . onClientClose = FunctionPrototypeBind ( this . onClientClose , this ) ;
@@ -638,6 +660,15 @@ class ProbeInspectorSession {
638660}
639661}
640662
663+ // Finish proactively as soon as any probe reaches its hit limit. All probes
664+ // hit in this pause are recorded first, then the session ends.
665+ // TODO(joyeecheung): When we implement attach mode, this teardown must
666+ // resume-and-detach rather than kill, since the target is not ours.
667+ if ( ! this . finished && ArrayPrototypeSome ( this . probes , probeReachedLimit ) ) {
668+ this . finishWithTrustedResult ( { event : 'completed' } ) ;
669+ return ;
670+ }
671+
641672await this . resume ( ) ;
642673}
643674
@@ -908,7 +939,12 @@ class ProbeInspectorSession {
908939code : exitCode ,
909940report : {
910941v : kProbeVersion ,
911- probes : ArrayPrototypeMap ( this . probes , ( { expr, target } ) => ( { expr, target } ) ) ,
942+ probes : ArrayPrototypeMap ( this . probes , ( { expr, target, maxHit } ) => {
943+ // Omit an unlimited maxHit, as Infinity would serialize to null in JSON.
944+ const probe = { expr, target } ;
945+ if ( maxHit !== Infinity ) { probe . maxHit = maxHit ; }
946+ return probe ;
947+ } ) ,
912948 results,
913949} ,
914950} ;
@@ -927,6 +963,8 @@ class ProbeInspectorSession {
927963
928964if ( this . child === null ) { return ; }
929965
966+ // TODO(joyeecheung): When we implement attach mode, this teardown must
967+ // resume-and-detach rather than kill, since the target is not ours.
930968if ( this . child . exitCode === null && this . child . signalCode === null ) {
931969this . child . kill ( ) ;
932970}
0 commit comments