Skip to content

Commit dbc78ff

Browse files
joyeecheungaduh95
authored andcommitted
debugger,test: deflake resume failure test and add debug logs
On slow CI, the outer Debugger.resume can be picked up in the same drain pass as the Debugger.evaluateOnCallFrame, while V8 still considers the context paused. In this case both resume calls may succeed and the process can continue running from the setInterval until the timeout. Accept both probe failure and timeout as valid to accommodate this flakiness. This patch also adds more debug logs to the probe mode to show more information in case it flakes again in the CI Signed-off-by: Joyee Cheung <joyeec9h3@gmail.com> PR-URL: #63524Fixes: #63505 Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day> Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent 051a215 commit dbc78ff

3 files changed

Lines changed: 49 additions & 8 deletions

File tree

‎lib/internal/debugger/inspect_probe.js‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const {
2525

2626
const{ clearTimeout, setTimeout }=require('timers');
2727
const{ SideEffectFreeRegExpPrototypeSymbolReplace }=require('internal/util');
28+
constdebug=require('internal/util/debuglog').debuglog('inspect_probe');
2829

2930
constInspectClient=require('internal/debugger/inspect_client');
3031
const{
@@ -477,6 +478,7 @@ class ProbeInspectorSession {
477478

478479
finish(exitCode,terminal){
479480
if(this.finished){return;}
481+
debug('finish: exitCode=%d, terminal=%s',exitCode,terminal?.event);
480482
this.finished=true;
481483
if(this.timeout!==null){
482484
clearTimeout(this.timeout);
@@ -523,6 +525,8 @@ class ProbeInspectorSession {
523525
}
524526

525527
onChildExit(code,signal){
528+
debug('child exit: code=%s signal=%s connected=%s started=%s finished=%s inFlight=%j',
529+
code,signal,this.connected,this.started,this.finished,this.inFlight);
526530
// Pre-connect exits are deliberately silent: the target never reached
527531
// a state where probes could be set, so any report would be empty.
528532
if(!this.connected){return;}
@@ -543,6 +547,8 @@ class ProbeInspectorSession {
543547
}
544548

545549
onClientClose(){
550+
debug('client close: disconnectRequested=%s finished=%s inFlight=%j',
551+
this.disconnectRequested,this.finished,this.inFlight);
546552
if(!this.connected){return;}
547553
if(this.disconnectRequested){return;}
548554
if(this.finished){return;}
@@ -664,13 +670,21 @@ class ProbeInspectorSession {
664670
asynccallCdp(method,params,probe=null){
665671
if(this.finished){throwkInspectorFailedSentinel;}
666672
this.inFlight={__proto__: null, method, probe };
673+
debug('CDP -> %s%s',method,probe!==null ? `, probe=${probe.index}` : '');
667674
try{
668675
constresult=awaitthis.client.callMethod(method,params);
669676
// A timeout or process exit can finish the report while the CDP request
670677
// is still outstanding. Ignore the late reply in that case.
671-
if(this.finished){throwkInspectorFailedSentinel;}
678+
if(this.finished){
679+
debug('CDP <- %s discarded (already finished)',method);
680+
throwkInspectorFailedSentinel;
681+
}
682+
debug('CDP <- %s (success)',method);
672683
returnresult;
673684
}catch(err){
685+
if(err!==kInspectorFailedSentinel){// Already handled.
686+
debug('CDP <- %s error: %s',method,err?.code);
687+
}
674688
if(this.disconnectRequested){
675689
// Only the in-flight evaluation gets attribution. Other rejections
676690
// under disconnect are downstream noise.
@@ -718,6 +732,8 @@ class ProbeInspectorSession {
718732
// Records the first inspector-side terminal for the session, later callers are ignored.
719733
recordInspectorFailure({ reason, advice, cdpError, internalError }){
720734
if(this.finished){return;}
735+
debug('recordInspectorFailure "%s": inFlight=%j, lastProbeIndex=%s, cdpError=%j',
736+
reason,this.inFlight,this.lastProbeIndex,cdpError);
721737
constchild=this.child;
722738
constexitedAbnormally=child!==null&&
723739
(child.signalCode!==null||(child.exitCode!==null&&child.exitCode!==0));
@@ -785,6 +801,8 @@ class ProbeInspectorSession {
785801

786802
startTimeout(){
787803
this.timeout=setTimeout(()=>{
804+
debug('timeout fired: finished=%s, inFlight=%j, lastProbeIndex=%s',
805+
this.finished,this.inFlight,this.lastProbeIndex);
788806
if(this.finished){return;}
789807
if(this.inFlight!==null){
790808
consthasProbeAttribution=

‎test/common/debugger-probe.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ function normalizeProbeReport(value) {
5252
}
5353

5454
functionassertProbeJson(output,expected){
55-
constnormalized=JSON.parse(output);
55+
constnormalized=typeofoutput==='string' ? JSON.parse(output) : output;
5656
constlastResult=normalized.results?.[normalized.results.length-1];
5757

5858
if(isProbeSegvTeardown(lastResult)){

‎test/parallel/test-debugger-probe-failure-resume.js‎

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// This tests that a probe expression resuming the target through its own
2-
// inspector.Session surfaces as probe_failure.
2+
// inspector.Session is surfaced as a probe-side failure. The terminal event
3+
// can be either probe_failure or probe_timeout depending on a race in V8's
4+
// nested pause-loop drain.
35
'use strict';
46

57
constcommon=require('../common');
@@ -21,11 +23,11 @@ spawnSyncAndExit(process.execPath, [
2123
'inspect','--json',
2224
'--probe',`${fixture}:12`,'--expr',probes[0].expr,
2325
fixture,
24-
],{ cwd },{
26+
],{ cwd,env: { ...process.env,NODE_DEBUG: 'inspect_probe'}},{
2527
status: 1,
2628
signal: null,
2729
stdout(output){
28-
assertProbeJson(output,{
30+
constexpected={
2931
v: 2,
3032
probes,
3133
results: [{
@@ -34,7 +36,14 @@ spawnSyncAndExit(process.execPath, [
3436
hit: 1,
3537
location,
3638
result: {type: 'number',value: 1,description: '1'},
37-
},{
39+
}]
40+
};
41+
42+
constactual=JSON.parse(output);
43+
44+
constcode=actual.results.at(-1)?.error?.code;
45+
if(code==='probe_failure'){
46+
expected.results.push({
3847
event: 'error',
3948
pending: [],
4049
error: {
@@ -50,8 +59,22 @@ spawnSyncAndExit(process.execPath, [
5059
protocolError: {message: 'Can only perform operation while paused.',code: -32000},
5160
},
5261
},
53-
}],
54-
});
62+
});
63+
}elseif(code==='probe_timeout'){
64+
// On slow CI, the outer Debugger.resume can be picked up in the same drain pass as
65+
// the Debugger.evaluateOnCallFrame, while V8 still considers the context paused.
66+
// In this case both resume calls may succeed and the process can continue running from
67+
// the setInterval until the timeout.
68+
expected.results.push({
69+
event: 'timeout',
70+
pending: [],
71+
error: {
72+
code: 'probe_timeout',
73+
message: 'Timed out after 30000ms waiting for target completion'
74+
},
75+
});
76+
}
77+
assertProbeJson(actual,expected);
5578
},
5679
trim: true,
5780
});

0 commit comments

Comments
 (0)