Skip to content

Commit 8a91d18

Browse files
[core] Add the wake-loop scenario to the event log race repro (vercel#4017)
1 parent 9a9af61 commit 8a91d18

7 files changed

Lines changed: 596 additions & 6 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

‎.github/scripts/render-event-log-race-repro-results.js‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ function renderConfigScale(config) {
283283
config.blockedBranchAttempts
284284
? `blocked-branch ${config.blockedBranchAttempts}`
285285
: '',
286+
config.wakeLoopAttempts ? `wake-loop ${config.wakeLoopAttempts}` : '',
286287
config.hookSleepAttempts ? `hook-sleep ${config.hookSleepAttempts}` : '',
287288
// Historical entries from the pre-storm harness, kept so an old sticky
288289
// comment still renders its own configuration rather than a blank line.
@@ -312,6 +313,9 @@ function renderConfigTiming(config) {
312313
? `step ${config.stepDelayMs}±${config.stepDelayJitterMs ?? 0}ms`
313314
: '',
314315
config.hookResumeStaggerMs ? `stagger ${config.hookResumeStaggerMs}ms` : '',
316+
config.wakeLoopAttempts && config.wakeLoopHeartbeatMs
317+
? `heartbeat ${config.wakeLoopHeartbeatMs}ms`
318+
: '',
315319
config.resumeBurstOffsetMs && config.blockedBranchAttempts
316320
? `burst ${config.resumeBurstOffsetMs}+${config.resumeBurstJitterMs ?? 0}ms`
317321
: '',
@@ -342,6 +346,7 @@ function compactConfig(config = {}) {
342346
stepStormAttempts: config.stepStormAttempts,
343347
hookStormAttempts: config.hookStormAttempts,
344348
blockedBranchAttempts: config.blockedBranchAttempts,
349+
wakeLoopAttempts: config.wakeLoopAttempts,
345350
hookSleepAttempts: config.hookSleepAttempts,
346351
concurrency: config.concurrency,
347352
rounds: config.rounds,
@@ -357,6 +362,7 @@ function compactConfig(config = {}) {
357362
hookResumeStaggerMs: config.hookResumeStaggerMs,
358363
launchStaggerMs: config.launchStaggerMs,
359364
resumeBurstOffsetMs: config.resumeBurstOffsetMs,
365+
wakeLoopHeartbeatMs: config.wakeLoopHeartbeatMs,
360366
resumeBurstJitterMs: config.resumeBurstJitterMs,
361367
blockedBranchWatchdogMs: config.blockedBranchWatchdogMs,
362368
runTimeoutMs: config.runTimeoutMs,

‎.github/workflows/event-log-race-repro.yml‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ on:
5858
blocked_branch_watchdog_ms:
5959
description: 'blocked-branch: hook-race watchdog. Size it to normally lose to the burst; it exists to enter a wait into the race'
6060
required: false
61+
# GitHub caps workflow_dispatch at 25 inputs, so only the wake-loop knobs a
62+
# soak is likely to turn are exposed here; the rest are env-only (see
63+
# `EVENT_LOG_RACE_REPRO_WAKE_LOOP_*` in the harness).
64+
wake_loop_attempts:
65+
description: 'Number of wake-loop runs (one sequential loop racing a reusable hook read against a heartbeat sleep; concurrency comes from the driver resumes)'
66+
required: false
67+
wake_loop_heartbeat_ms:
68+
description: 'wake-loop: heartbeat sleep raced against the hook read; part of the wakes are aimed at this deadline'
69+
required: false
70+
wake_loop_fresh_ratio:
71+
description: 'wake-loop: share of wakes that carry work (a stale wake emits no step, a fresh one emits four)'
72+
required: false
6173
poke_interval_ms:
6274
description: 'step-storm: cadence of out-of-band resumes to the never-read poke hook'
6375
required: false
@@ -156,6 +168,9 @@ jobs:
156168
EVENT_LOG_RACE_REPRO_RESUME_BURST_OFFSET_MS: ${{ inputs.resume_burst_offset_ms }}
157169
EVENT_LOG_RACE_REPRO_RESUME_BURST_JITTER_MS: ${{ inputs.resume_burst_jitter_ms }}
158170
EVENT_LOG_RACE_REPRO_BLOCKED_BRANCH_WATCHDOG_MS: ${{ inputs.blocked_branch_watchdog_ms }}
171+
EVENT_LOG_RACE_REPRO_WAKE_LOOP_ATTEMPTS: ${{ inputs.wake_loop_attempts }}
172+
EVENT_LOG_RACE_REPRO_WAKE_LOOP_HEARTBEAT_MS: ${{ inputs.wake_loop_heartbeat_ms }}
173+
EVENT_LOG_RACE_REPRO_WAKE_LOOP_FRESH_RATIO: ${{ inputs.wake_loop_fresh_ratio }}
159174
EVENT_LOG_RACE_REPRO_POKE_INTERVAL_MS: ${{ inputs.poke_interval_ms }}
160175
EVENT_LOG_RACE_REPRO_POKE_MAX: ${{ inputs.poke_max }}
161176
EVENT_LOG_RACE_REPRO_POKE_DECAY_FACTOR: ${{ inputs.poke_decay_factor }}
@@ -264,6 +279,9 @@ jobs:
264279
EVENT_LOG_RACE_REPRO_RESUME_BURST_OFFSET_MS: ${{ inputs.resume_burst_offset_ms }}
265280
EVENT_LOG_RACE_REPRO_RESUME_BURST_JITTER_MS: ${{ inputs.resume_burst_jitter_ms }}
266281
EVENT_LOG_RACE_REPRO_BLOCKED_BRANCH_WATCHDOG_MS: ${{ inputs.blocked_branch_watchdog_ms }}
282+
EVENT_LOG_RACE_REPRO_WAKE_LOOP_ATTEMPTS: ${{ inputs.wake_loop_attempts }}
283+
EVENT_LOG_RACE_REPRO_WAKE_LOOP_HEARTBEAT_MS: ${{ inputs.wake_loop_heartbeat_ms }}
284+
EVENT_LOG_RACE_REPRO_WAKE_LOOP_FRESH_RATIO: ${{ inputs.wake_loop_fresh_ratio }}
267285
EVENT_LOG_RACE_REPRO_POKE_INTERVAL_MS: ${{ inputs.poke_interval_ms }}
268286
EVENT_LOG_RACE_REPRO_POKE_MAX: ${{ inputs.poke_max }}
269287
EVENT_LOG_RACE_REPRO_POKE_DECAY_FACTOR: ${{ inputs.poke_decay_factor }}

‎AGENTS.md‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,18 @@ pnpm run test:e2e
130130
### Event log race repro
131131

132132
`packages/core/e2e/event-log-race-repro.test.ts` is a dedicated harness for
133-
`CORRUPTED_EVENT_LOG`. It drives four scenarios against one deployment:
133+
`CORRUPTED_EVENT_LOG`. It drives five scenarios against one deployment:
134134
`step-storm` and `hook-storm` (concurrent replays of a single run racing the
135-
per-branch watchdog; `hook-storm` is the production shape), `blocked-branch`
135+
per-branch watchdog; `hook-storm` is a production shape), `blocked-branch`
136136
(each branch parks on a launch step before its hook race, so a woken replay can
137137
hold a log that predates a sibling's launch completion and take the ordinal that
138138
sibling's wait is about to get; it covers the class the wake-order fixes miss),
139-
plus a `hook-sleep` control that provides the calibration baseline. Any outcome
139+
`wake-loop` (one sequential loop racing a reusable hook read against a
140+
heartbeat sleep, no fan-out; the driver supplies the concurrency with bursty
141+
resumes and resumes aimed at the heartbeat deadline, the shape of a production
142+
run whose replays of one immutable prefix diverged non-deterministically on an
143+
unconsumable `wait_created`), plus a `hook-sleep` control that provides the
144+
calibration baseline. Any outcome
140145
other than `completed` fails the run, except `infra`, which means the harness
141146
could not reach the deployment.
142147

0 commit comments

Comments
 (0)