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 ) {
9399 if ( typeof value !== 'string' || RegExpPrototypeExec ( kDigitsRegex , value ) === null ) {
94100 throw new ERR_DEBUGGER_STARTUP_ERROR ( `Invalid ${ name } : ${ value } ` ) ;
@@ -371,6 +377,20 @@ function parseProbeTokens(tokens, args) {
371377 break ;
372378 case 'expr' :
373379 throw 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+ }
374394 default :
375395 if ( probes . length > 0 ) {
376396 throw new ERR_DEBUGGER_STARTUP_ERROR (
@@ -458,7 +478,9 @@ class ProbeInspectorSession {
458478 this . completionPromise = promise ;
459479 this . 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 } ) ) ;
462484 this . onChildOutput = FunctionPrototypeBind ( this . onChildOutput , this ) ;
463485 this . onChildExit = FunctionPrototypeBind ( this . onChildExit , this ) ;
464486 this . onClientClose = FunctionPrototypeBind ( this . onClientClose , this ) ;
@@ -642,6 +664,15 @@ class ProbeInspectorSession {
642664 }
643665 }
644666
667+ // Finish proactively as soon as any probe reaches its hit limit. All probes
668+ // hit in this pause are recorded first, then the session ends.
669+ // TODO(joyeecheung): When we implement attach mode, this teardown must
670+ // resume-and-detach rather than kill, since the target is not ours.
671+ if ( ! this . finished && ArrayPrototypeSome ( this . probes , probeReachedLimit ) ) {
672+ this . finishWithTrustedResult ( { event : 'completed' } ) ;
673+ return ;
674+ }
675+
645676 await this . resume ( ) ;
646677 }
647678
@@ -912,7 +943,12 @@ class ProbeInspectorSession {
912943 code : exitCode ,
913944 report : {
914945 v : kProbeVersion ,
915- probes : ArrayPrototypeMap ( this . probes , ( { expr, target } ) => ( { expr, target } ) ) ,
946+ probes : ArrayPrototypeMap ( this . probes , ( { expr, target, maxHit } ) => {
947+ // Omit an unlimited maxHit, as Infinity would serialize to null in JSON.
948+ const probe = { expr, target } ;
949+ if ( maxHit !== Infinity ) { probe . maxHit = maxHit ; }
950+ return probe ;
951+ } ) ,
916952 results,
917953 } ,
918954 } ;
@@ -931,6 +967,8 @@ class ProbeInspectorSession {
931967
932968 if ( this . child === null ) { return ; }
933969
970+ // TODO(joyeecheung): When we implement attach mode, this teardown must
971+ // resume-and-detach rather than kill, since the target is not ours.
934972 if ( this . child . exitCode === null && this . child . signalCode === null ) {
935973 this . child . kill ( ) ;
936974 }
0 commit comments