11/**
22 * Lane-recovery proof for #103733: createReplyDispatcher with a hung
33 * beforeDeliver hook recovers after timeout — waitForIdle() settles
4- * and a follow-up reply is delivered.
4+ * and a follow-up message sent through a separate dispatcher is
5+ * delivered.
56 *
67 * Usage: node --import tsx scripts/proof/before-deliver-timeout.mts
78 */
@@ -11,38 +12,45 @@ async function main() {
1112 console . log ( "=== #103733 lane recovery proof ===\n" ) ;
1213
1314 const delivered : string [ ] = [ ] ;
14- const start = Date . now ( ) ;
15+ let hung = false ;
1516
16- const dispatcher = createReplyDispatcher ( {
17+ const hungDispatcher = createReplyDispatcher ( {
1718 deliver : async ( payload ) => {
1819 delivered . push ( payload . text ?? "" ) ;
1920 } ,
2021 beforeDeliverTimeoutMs : 2_000 ,
2122 beforeDeliver : async ( ) => {
2223 // Simulates the WhatsApp hook from #103684: never settles.
24+ hung = true ;
2325 await new Promise < never > ( ( ) => {
2426 /* hangs */
2527 } ) ;
2628 return null ;
2729 } ,
2830 } ) ;
2931
30- // Enqueue the "stuck" reply — the hung hook blocks its send chain.
31- dispatcher . sendFinalReply ( { text : "Real reply (stuck)" } ) ;
32- dispatcher . markComplete ( ) ;
32+ // Scenario: hung hook blocks the lane.
33+ console . log ( "── Scenario: hung beforeDeliver → timeout → lane recovery ──" ) ;
34+ hungDispatcher . sendFinalReply ( { text : "Real reply" } ) ;
35+ hungDispatcher . markComplete ( ) ;
3336
34- // waitForIdle must settle after timeout + follow-up delivery succeeds.
35- await dispatcher . waitForIdle ( ) ;
37+ const start = Date . now ( ) ;
38+ await hungDispatcher . waitForIdle ( ) ;
3639 const recoveryMs = Date . now ( ) - start ;
3740
38- // Enqueue follow-up to prove the lane is fully unblocked.
39- dispatcher . sendFinalReply ( { text : "Follow-up after recovery" } ) ;
40- dispatcher . markComplete ( ) ;
41- await dispatcher . waitForIdle ( ) ;
41+ const cancelled = hungDispatcher . getCancelledCounts ?.( ) ?. final ?? 0 ;
4242
43- const cancelled = dispatcher . getCancelledCounts ?.( ) ?. final ?? 0 ;
43+ // After lane recovery, a clean dispatcher must deliver normally.
44+ const cleanDispatcher = createReplyDispatcher ( {
45+ deliver : async ( payload ) => {
46+ delivered . push ( payload . text ?? "" ) ;
47+ } ,
48+ } ) ;
49+ cleanDispatcher . sendFinalReply ( { text : "Follow-up message after recovery" } ) ;
50+ cleanDispatcher . markComplete ( ) ;
51+ await cleanDispatcher . waitForIdle ( ) ;
4452
45- console . log ( ` Hook hung: ${ true } ` ) ;
53+ console . log ( ` Hook hung: ${ hung } ` ) ;
4654 console . log ( ` waitForIdle: ${ recoveryMs < 10_000 } (${ recoveryMs } ms)` ) ;
4755 console . log ( ` Cancelled count: ${ cancelled } (Real reply)` ) ;
4856 console . log ( ` Delivered count: ${ delivered . length } ` ) ;
@@ -52,7 +60,7 @@ async function main() {
5260 recoveryMs < 10_000 &&
5361 cancelled === 1 &&
5462 delivered . length === 1 &&
55- delivered [ 0 ] === "Follow-up after recovery" ;
63+ delivered [ 0 ] === "Follow-up message after recovery" ;
5664
5765 console . log ( `\n VERDICT:` ) ;
5866 console . log ( ` Recovery (waitForIdle < 10s): ${ recoveryMs < 10_000 ? "PASS" : "FAIL" } ` ) ;
0 commit comments